From cf13952e94e565d8157d2f5a1e9e6c4e6a11c993 Mon Sep 17 00:00:00 2001 From: Tim Nielens Date: Wed, 25 May 2022 23:34:57 +0200 Subject: [PATCH] add specialization and loop optis benchmark suites --- .../scala/org/saddle/LoopOptimizations.scala | 134 ++++++++++++++++++ .../scala/org/saddle/Specialization.scala | 47 ++++++ 2 files changed, 181 insertions(+) create mode 100644 saddle-jmh/src/main/scala/org/saddle/LoopOptimizations.scala create mode 100644 saddle-jmh/src/main/scala/org/saddle/Specialization.scala diff --git a/saddle-jmh/src/main/scala/org/saddle/LoopOptimizations.scala b/saddle-jmh/src/main/scala/org/saddle/LoopOptimizations.scala new file mode 100644 index 00000000..b51aab97 --- /dev/null +++ b/saddle-jmh/src/main/scala/org/saddle/LoopOptimizations.scala @@ -0,0 +1,134 @@ +package org.saddle + +import org.openjdk.jmh.annotations._ + +/** Benchmark suite comparing different loop patterns and the impact of loop optimizations. + * https://wiki.openjdk.java.net/pages/viewpage.action?pageId=20415918 + */ +@State(Scope.Benchmark) +@Warmup(iterations = 10) +@Measurement(iterations = 10) +@Fork(1) +@Threads(1) +class LoopOptimisations { + @Param(Array("10000")) + var size: Int = _ + var arr: Array[Double] = _ + var vec1: Vec[Double] = _ + var b: Double = _ + + @Setup(Level.Iteration) + def setup() = { + arr = vec.rand(size).toArray + vec1 = Vec(arr) + b = scala.util.Random.nextDouble() + } + + @Benchmark + /** Simplest variant, should trigger vectorization and other optimisations. + */ + def arrayAdd(): Array[Double] = { + var i = 0 + while (i < arr.length) { + arr(i) += b + i += 1 + } + arr + } + + @Benchmark + def arrayAddExpandedAssignmentOperator(): Array[Double] = { + var i = 0 + while (i < arr.length) { + // assignement operator seems to perform worse + arr(i) = arr(i) + b + i += 1 + } + arr + } + + + /** hand-unrolled loops prevent vectorization and potentially other + * optimisations + */ + @Benchmark + def arrayAddHandUnrolled2(): Array[Double] = { + var i = 0 + val length = arr.length + val unrolledStride = 2 + val preloopIterations = length % unrolledStride + while (i < preloopIterations) { + arr(i) += b + i += 1 + } + while (i < length) { + arr(i + 0) += b + arr(i + 1) += b + i += unrolledStride + } + arr + } + + @Benchmark + def arrayAddHandUnrolled5(): Array[Double] = { + var i = 0 + val length = arr.length + val unrolledStride = 5 + val preloopIterations = length % unrolledStride + while (i < preloopIterations) { + arr(i) += b + i += 1 + } + while (i < length) { + arr(i + 0) += b + arr(i + 1) += b + arr(i + 2) += b + arr(i + 3) += b + arr(i + 4) += b + i += unrolledStride + } + arr + } + + @Benchmark + def arrayDiv(): Array[Double] = { + var i = 0 + while (i < arr.length) { + arr(i) /= b + i += 1 + } + arr + } + + @Benchmark + def arrayDivHandUnrolled2(): Array[Double] = { + var i = 0 + val length = arr.length + val unrolledStride = 2 + val preloopIterations = length % unrolledStride + while (i < preloopIterations) { + arr(i) /= b + i += 1 + } + while (i < length) { + arr(i + 0) /= b + arr(i + 1) /= b + i += unrolledStride + } + arr + } + + @Benchmark + def vecBinOps(): Vec[Double] = { + import ops.BinOps._ + vec1 += b + vec1 + } + + @Benchmark + def vecBinOpsMacros(): Vec[Double] = { + import macros.BinOps._ + vec1 += b + vec1 + } +} \ No newline at end of file diff --git a/saddle-jmh/src/main/scala/org/saddle/Specialization.scala b/saddle-jmh/src/main/scala/org/saddle/Specialization.scala new file mode 100644 index 00000000..a4650b0e --- /dev/null +++ b/saddle-jmh/src/main/scala/org/saddle/Specialization.scala @@ -0,0 +1,47 @@ +package org.saddle + +import org.saddle.scalar.ScalarTag +import org.saddle.vec.VecDefault + +import org.openjdk.jmh.annotations._ + +class VecBoxed[T: ScalarTag](values: Array[T]) + extends VecDefault[T](values, implicitly[ScalarTag[T]]) + +/** Benchmark suite illustrating the performance boost of specialization. + */ +@State(Scope.Benchmark) +@Warmup(iterations = 10) +@Measurement(iterations = 10) +@Fork(1) +@Threads(1) +class Specialization { + @Param(Array("10", "10000")) + var size: Int = _ + + var v1: Vec[Double] = _ + var v2: VecBoxed[Double] = _ + var b: Double = _ + + @Setup(Level.Iteration) + def setup() = { + v1 = vec.rand(size) + v2 = new VecBoxed(v1.toArray) + b = scala.util.Random.nextDouble() + } + + @Benchmark + def vecSpecialized(): Vec[Double] = { + import org.saddle.ops.BinOps._ + v1 += b + v1 + } + + @Benchmark + def vecBoxed(): Vec[Double] = { + import org.saddle.ops.BinOps._ + v2 += b + v2 + } + +}