Skip to content

Commit

Permalink
add specialization and loop optis benchmark suites
Browse files Browse the repository at this point in the history
  • Loading branch information
tnielens committed Jun 1, 2022
1 parent 830564b commit cf13952
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 0 deletions.
134 changes: 134 additions & 0 deletions saddle-jmh/src/main/scala/org/saddle/LoopOptimizations.scala
Original file line number Diff line number Diff line change
@@ -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
}
}
47 changes: 47 additions & 0 deletions saddle-jmh/src/main/scala/org/saddle/Specialization.scala
Original file line number Diff line number Diff line change
@@ -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
}

}

0 comments on commit cf13952

Please sign in to comment.