Skip to content

Commit 5102412

Browse files
committed
benchmarks are breeze comparable
1 parent 2cd57dc commit 5102412

9 files changed

Lines changed: 229 additions & 92 deletions

File tree

.scalafmt.conf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ rewrite.scala3.countEndMarkerLines = lastBlockOnly
1111
rewrite.scala3.insertEndMarkerMinLines = 1
1212
indent.main = 2
1313
maxColumn = 120
14-
project.excludeFilters = [ ".*/build\\.mill"]
14+
project.excludeFilters = [ ".*/build\\.mill", ".*/package\\.mill"]

benchmark_vs_breeze/package.mill

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ object `package` extends RootModule with JmhModule with ScalaModule {
1010
def jmhCoreVersion = "1.37"
1111
override def forkArgs: T[Seq[String]] = super.forkArgs() ++ build.vecIncubatorFlag
1212
override def moduleDeps: Seq[JavaModule] = Seq(build.vecxt.jvm)
13-
override def ivyDeps: T[Agg[ModuleDependency]] = super.ivyDeps() ++ Agg(
13+
override def ivyDeps = super.ivyDeps() ++ Seq(
1414
ivy"org.scalanlp::breeze:2.1.0"
1515
)
1616

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package vecxt.benchmark
2+
3+
import dev.ludovic.netlib.blas.*
4+
5+
import org.openjdk.jmh.annotations.*
6+
7+
import java.util.Random
8+
import java.util.concurrent.TimeUnit
9+
import jdk.incubator.vector.DoubleVector
10+
import jdk.incubator.vector.IntVector
11+
import scala.compiletime.uninitialized
12+
13+
@BenchmarkMode(Array(Mode.Throughput))
14+
@OutputTimeUnit(TimeUnit.SECONDS)
15+
@State(Scope.Thread)
16+
@Fork(value = 1)
17+
@Warmup(iterations = 1)
18+
@Measurement(iterations = 3)
19+
abstract class BLASBenchmark:
20+
21+
final val spd = DoubleVector.SPECIES_PREFERRED
22+
final val spi = IntVector.SPECIES_PREFERRED
23+
24+
var blas: BLAS = uninitialized;
25+
26+
@Setup
27+
def setupImplementation: Unit =
28+
blas = JavaBLAS.getInstance();
29+
()
30+
31+
// System.out.println("implementation = " + blas.getClass().getName());
32+
end setupImplementation
33+
34+
private final val rand: Random = new Random(0);
35+
36+
protected def randomDouble(): Double =
37+
return rand.nextDouble();
38+
39+
protected def randomInt(): Double =
40+
return rand.nextInt();
41+
42+
protected def randomDoubleArray(n: Int): Array[Double] =
43+
val res = new Array[Double](n);
44+
45+
for i <- 0 until n do res(i) = rand.nextDouble();
46+
end for
47+
return res;
48+
end randomDoubleArray
49+
50+
protected def randomIntArray(n: Int): Array[Int] =
51+
val res = new Array[Int](n);
52+
53+
for i <- 0 until n do res(i) = rand.nextInt();
54+
end for
55+
return res;
56+
end randomIntArray
57+
58+
protected def randomBooleanArray(n: Int): Array[Boolean] =
59+
val res = new Array[Boolean](n);
60+
61+
for i <- 0 until n do res(i) = rand.nextDouble() < 0.5;
62+
end for
63+
return res;
64+
end randomBooleanArray
65+
66+
protected def randomFloat(): Float =
67+
return rand.nextFloat();
68+
69+
protected def randomFloatArray(n: Int): Array[Float] =
70+
val res = new Array[Float](n);
71+
for i <- 0 until n do res(i) = rand.nextFloat();
72+
end for
73+
return res;
74+
end randomFloatArray
75+
end BLASBenchmark
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package check
2+
3+
import breeze.linalg.*
4+
import vecxt.BoundsCheck.DoBoundsCheck.no
5+
import vecxt.all.*
6+
import breeze.numerics.step
7+
8+
@main def breezey =
9+
// Example data
10+
val dim = 5
11+
val dataA = DenseMatrix.rand[Double](dim, dim)
12+
val dataB = DenseMatrix.rand[Double](dim, dim)
13+
val vectorData = DenseVector.rand[Double](dim)
14+
15+
println("Breeze Data:")
16+
println(dataA)
17+
println(dataB)
18+
println(vectorData)
19+
20+
// Representative linear algebra workload
21+
val step1 = dataA + dataB // Element-wise addition
22+
23+
val step2 = step1 *:* dataA // Hadamard product
24+
println("Step 2 (Hadamard product):")
25+
println(step2)
26+
val step3 = step2 * vectorData // Matrix-vector multiply
27+
val step4 = step3.map(_ * 2.0 + 1.0) // Element-wise transform
28+
val step5 = breeze.linalg.norm(step4) // L2 norm
29+
val step6 = step2.t // Transpose
30+
val step7 = breeze.linalg.sum(step6) // Sum reduction
31+
val step8 = (step7 > 0.5) // Comparison
32+
33+
// Combine results to prevent dead code elimination
34+
val result = step5 + (if step8 then 1.0 else 0.0)
35+
36+
println("Breeze Result:")
37+
println(result)
38+
39+
val matA = vecxt.matrix.Matrix(dataA.toArray, (dim, dim))
40+
val matB = vecxt.matrix.Matrix(dataB.toArray, (dim, dim))
41+
val vec = vectorData.toArray
42+
43+
println(matA.printMat)
44+
println(matB.printMat)
45+
println(vec.mkString("Array(", ", ", ")"))
46+
47+
// Same representative linear algebra workload
48+
val step1Vecxt = matA + matB // Element-wise addition
49+
50+
val step2Vecxt = step1Vecxt.hadamard(matA) // Hadamard product
51+
println("Step 2 (Hadamard product):")
52+
println(step2Vecxt.printMat)
53+
val step3Vecxt = step2Vecxt * vec // Matrix-vector multiply
54+
val step4Vecxt = step3Vecxt.fma(2.0, 1.0) // Element-wise transform
55+
val step5Vecxt = vecxt.all.norm(step4Vecxt) // L2 norm
56+
val step6Vecxt = step2Vecxt.transpose // Transpose
57+
val step7Vecxt = step6Vecxt.sum // Sum reduction
58+
val step8Vecxt = (step7Vecxt > 0.5) // Comparison
59+
// Combine results to prevent dead code elimination
60+
val resultVecxt = step5Vecxt + (if step8Vecxt then 1.0 else 0.0)
61+
62+
println("Vecxt Result:")
63+
println(resultVecxt)
64+
65+
end breezey
Lines changed: 58 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,106 +1,78 @@
1-
/*
2-
* Copyright 2020, 2021, Ludovic Henry
3-
*
4-
* Permission is hereby granted, free of charge, to any person obtaining a copy
5-
* of this software and associated documentation files (the "Software"), to deal
6-
* in the Software without restriction, including without limitation the rights
7-
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8-
* copies of the Software, and to permit persons to whom the Software is
9-
* furnished to do so, subject to the following conditions:
10-
*
11-
* The above copyright notice and this permission notice shall be included in
12-
* all copies or substantial portions of the Software.
13-
*
14-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15-
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16-
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17-
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18-
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19-
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20-
* SOFTWARE.
21-
*
22-
* Please contact git@ludovic.dev or visit ludovic.dev if you need additional
23-
* information or have any questions.
24-
*/
25-
261
package vecxt.benchmark
272

283
import org.openjdk.jmh.annotations.*
294
import org.openjdk.jmh.infra.Blackhole
30-
// import vecxt.Matrix.*
315
import vecxt.BoundsCheck
326
import scala.compiletime.uninitialized
337
import vecxt.all.*
348
import jdk.incubator.vector.VectorSpecies
359
import jdk.incubator.vector.VectorOperators
3610
import jdk.incubator.vector.DoubleVector
11+
import breeze.linalg.*
12+
import vecxt.BoundsCheck.DoBoundsCheck.no
3713

14+
//% mill benchmark_vs_breeze.runJmh -jvmArgs --add-modules=jdk.incubator.vector
3815
@State(Scope.Thread)
39-
class SumBenchmark extends BLASBenchmark:
40-
41-
@Param(Array("10000"))
42-
var len: String = uninitialized;
16+
class LinearAlgebraWorkloadBenchmark extends BLASBenchmark:
4317

44-
var arr: Array[Double] = uninitialized
18+
@Param(Array("500"))
19+
var matDim: String = uninitialized
4520

46-
// format: off
47-
@Setup(Level.Trial)
48-
def setup: Unit =
49-
50-
arr = randomDoubleArray(len.toInt);
51-
()
21+
var dataA: Array[Double] = uninitialized
22+
var dataB: Array[Double] = uninitialized
23+
var vectorData: Array[Double] = uninitialized
5224

25+
@Setup(Level.Invocation)
26+
def setup(): Unit =
27+
val dim = matDim.toInt
28+
dataA = randomDoubleArray(dim * dim)
29+
dataB = randomDoubleArray(dim * dim)
30+
vectorData = randomDoubleArray(dim)
5331
end setup
5432

55-
extension (vec: Array[Double])
56-
57-
inline def sum2 =
58-
var sum: Double = 0.0
59-
var i: Int = 0
60-
val l = spd.length()
61-
62-
while i < spd.loopBound(vec.length) do
63-
sum = sum + DoubleVector.fromArray(spd, vec, i).reduceLanes(VectorOperators.ADD)
64-
i += l
65-
end while
66-
while i < vec.length do
67-
sum += vec(i)
68-
i += 1
69-
end while
70-
sum
71-
end sum2
72-
73-
inline def sum3 =
74-
var sum: Double = 0.0
75-
var i: Int = 0
76-
while i < vec.length do
77-
sum = sum + vec(i)
78-
i = i + 1
79-
end while
80-
sum
81-
end sum3
82-
83-
end extension
84-
85-
86-
8733
@Benchmark
88-
def sum_loop(bh: Blackhole) =
89-
val r = arr.sum3
90-
bh.consume(r);
91-
end sum_loop
34+
def breezeWorkload(bh: Blackhole): Unit =
35+
val dim = matDim.toInt
36+
// Create matrices and vector from the same data
37+
val matA = new DenseMatrix(dim, dim, dataA)
38+
val matB = new DenseMatrix(dim, dim, dataB)
39+
val vec = new DenseVector(vectorData)
40+
41+
// Representative linear algebra workload
42+
val step1 = matA + matB // Element-wise addition
43+
val step2 = step1 *:* matA // Hadamard product
44+
val step3 = step2 * vec // Matrix-vector multiply
45+
val step4 = step3.map(_ * 2.0 + 1.0) // Element-wise transform
46+
val step5 = breeze.linalg.norm(step4) // L2 norm
47+
// val step6 = step2.t // Transpose
48+
val step7 = breeze.linalg.sum(step2) // Sum reduction
49+
val step8 = (step7 > 0.5) // Comparison
50+
51+
// Combine results to prevent dead code elimination
52+
val result = step5 + (if step8 then 1.0 else 0.0)
53+
bh.consume(result)
54+
end breezeWorkload
9255

9356
@Benchmark
94-
def sum_vec(bh: Blackhole) =
95-
val r = arr.sum2
96-
bh.consume(r);
97-
end sum_vec
98-
99-
@Benchmark
100-
def sum_vec_alt(bh: Blackhole) =
101-
val r = arr.sum
102-
bh.consume(r);
103-
end sum_vec_alt
104-
105-
106-
end SumBenchmark
57+
def vecxtWorkload(bh: Blackhole): Unit =
58+
val dim = matDim.toInt
59+
// Create matrices and vector from the same data
60+
val matA = vecxt.matrix.Matrix(dataA, (dim, dim))
61+
val matB = vecxt.matrix.Matrix(dataB, (dim, dim))
62+
63+
// Same representative linear algebra workload
64+
val step1 = matA + matB // Element-wise addition
65+
val step2 = step1.hadamard(matA) // Hadamard product
66+
val step3 = step2 * vectorData // Matrix-vector multiply
67+
val step4 = step3.fma(2.0, 1.0) // Element-wise transform
68+
val step5 = step4.norm // L2 norm
69+
// val step6 = step2.transpose // Transpose
70+
val step7 = step2.sum // Sum reduction
71+
val step8 = (step7 > 0.5) // Comparison
72+
73+
// Combine results to prevent dead code elimination
74+
val result = step5 + (if step8 then 1.0 else 0.0)
75+
bh.consume(result)
76+
end vecxtWorkload
77+
78+
end LinearAlgebraWorkloadBenchmark

build.mill

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ trait CommonNative extends ScalaNativeModule {
7676

7777
trait CommonTests extends TestModule.Munit {
7878
def ivyDeps = super.ivyDeps() ++ Agg(
79-
ivy"org.scalameta::munit:${V.munitVersion}",
79+
ivy"org.scalameta::munit::${V.munitVersion}",
8080
)
8181
}
8282

vecxt/jvm/src/doublematrix.scala

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,24 @@ object JvmDoubleMatrix:
4040

4141
// inline def *:*=(d: Double): Unit = m.raw.multInPlace(d)
4242

43+
inline def *(vec: Array[Double])(using inline boundsCheck: BoundsCheck): Array[Double] =
44+
val newArr = Array.ofDim[Double](m.rows)
45+
blas.dgemv(
46+
"N",
47+
m.rows,
48+
m.cols,
49+
1.0,
50+
m.raw,
51+
m.rows,
52+
vec,
53+
1,
54+
0.0,
55+
newArr,
56+
1
57+
)
58+
newArr
59+
end *
60+
4361
inline def >=(d: Double): Matrix[Boolean] =
4462
Matrix[Boolean](vecxt.arrays.>=(m.raw)(d), m.shape)(using BoundsCheck.DoBoundsCheck.no)
4563

vecxt/src/doublematrix.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,11 @@ object DoubleMatrix:
4242
Matrix[Double](newArr, m.shape)(using BoundsCheck.DoBoundsCheck.no)
4343
end +
4444

45-
inline def *(m2: Matrix[Double])(using inline boundsCheck: BoundsCheck): Matrix[Double] =
45+
inline def hadamard(m2: Matrix[Double])(using inline boundsCheck: BoundsCheck): Matrix[Double] =
4646
sameDimMatCheck(m, m2)
4747
val newArr = vecxt.arrays.*(m.raw)(m2.raw)
4848
Matrix[Double](newArr, m.shape)(using BoundsCheck.DoBoundsCheck.no)
49-
end *
49+
end hadamard
5050

5151
inline def /(m2: Matrix[Double])(using inline boundsCheck: BoundsCheck): Matrix[Double] =
5252
sameDimMatCheck(m, m2)

vecxt/test/src/matrix.test.scala

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@ class MatrixExtensionSuite extends FunSuite:
419419
val mat2 = Matrix.eye[Double](3) + Matrix.eye[Double](3) // addition
420420
assertVecEquals(mat1.raw, mat2.raw)
421421

422-
val bah = mat1 * mat2
422+
val bah = mat1.hadamard(mat2)
423423

424424
assertVecEquals(mat1.raw, mat2.raw)
425425

@@ -483,6 +483,13 @@ class MatrixExtensionSuite extends FunSuite:
483483

484484
}
485485

486+
test("hadamard product") {
487+
val mat1 = Matrix.eye[Double](3)
488+
val mat2 = Matrix.eye[Double](3) * 2
489+
val result = mat1.hadamard(mat2)
490+
assertVecEquals(result.raw, NArray[Double](2.0, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 2.0))
491+
}
492+
486493
test("map rows") {
487494
val mapped = mat1to9.mapRows(row => row * 2)
488495
assertVecEquals(mapped.raw, mat1to9.raw * 2)

0 commit comments

Comments
 (0)