From ab16bd16acf2569728a223d934b4785c77cc88cc Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Mon, 26 Aug 2024 13:37:49 +0300 Subject: [PATCH] Fix benchmark results script --- benchmarks/README.md | 117 +++++++++++++++++- benchmarks/build.gradle.kts | 68 ++++------ benchmarks/docs/README-TEMPLATE.md | 5 + .../kmath/stat/DistributionBenchmark.kt | 22 ++++ .../kscience/kmath/linear/MatrixBuilder.kt | 9 +- ...orkingEvent.kt => RandomForkingSampler.kt} | 0 6 files changed, 170 insertions(+), 51 deletions(-) create mode 100644 benchmarks/docs/README-TEMPLATE.md rename kmath-stat/src/commonMain/kotlin/space/kscience/kmath/samplers/{RandomForkingEvent.kt => RandomForkingSampler.kt} (100%) diff --git a/benchmarks/README.md b/benchmarks/README.md index cd8fbafd3..1535d55e6 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -1,4 +1,119 @@ -# Module benchmarks +# BenchmarksResult + +## Report for benchmark configuration main + +* Run on Java HotSpot(TM) 64-Bit Server VM (build 21.0.4+8-LTS-jvmci-23.1-b41) with Java process: + +``` +C:\Users\altavir\scoop\apps\graalvm-oracle-21jdk\current\bin\java.exe -XX:ThreadPriorityPolicy=1 -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCIProduct -XX:-UnlockExperimentalVMOptions -Dfile.encoding=UTF-8 -Duser.country=US -Duser.language=en -Duser.variant +``` +* JMH 1.21 was used in `thrpt` mode with 5 warmup iterations by 10 s and 5 measurement iterations by 10 s. +### [ArrayBenchmark](src/jvmMain/kotlin/space/kscience/kmath/benchmarks/ArrayBenchmark.kt) + +| Benchmark | Score | +|:---------:|:-----:| +|`benchmarkArrayRead`|1.9E+07 ± 2.3E+05 ops/s| +|`benchmarkBufferRead`|1.4E+07 ± 8.7E+05 ops/s| +|`nativeBufferRead`|1.4E+07 ± 1.3E+06 ops/s| +### [BigIntBenchmark](src/jvmMain/kotlin/space/kscience/kmath/benchmarks/BigIntBenchmark.kt) + +| Benchmark | Score | +|:---------:|:-----:| +|`jvmAdd`|5.1E+07 ± 1.3E+06 ops/s| +|`jvmAddLarge`|5.1E+04 ± 8.2E+02 ops/s| +|`jvmMultiply`|8.5E+07 ± 9.7E+06 ops/s| +|`jvmMultiplyLarge`|2.5E+02 ± 15 ops/s| +|`jvmParsing10`|8.7E+06 ± 5.1E+05 ops/s| +|`jvmParsing16`|6.4E+06 ± 1.8E+05 ops/s| +|`jvmPower`|28 ± 0.79 ops/s| +|`jvmSmallAdd`|7.0E+07 ± 4.3E+06 ops/s| +|`kmAdd`|4.8E+07 ± 2.2E+06 ops/s| +|`kmAddLarge`|3.5E+04 ± 3.7E+03 ops/s| +|`kmMultiply`|6.7E+07 ± 1.5E+07 ops/s| +|`kmMultiplyLarge`|54 ± 4.2 ops/s| +|`kmParsing10`|4.5E+06 ± 8.3E+04 ops/s| +|`kmParsing16`|4.9E+06 ± 1.1E+05 ops/s| +|`kmPower`|10 ± 0.96 ops/s| +|`kmSmallAdd`|4.1E+07 ± 5.9E+05 ops/s| +### [BufferBenchmark](src/jvmMain/kotlin/space/kscience/kmath/benchmarks/BufferBenchmark.kt) + +| Benchmark | Score | +|:---------:|:-----:| +|`bufferViewReadWrite`|5.8E+06 ± 1.6E+05 ops/s| +|`bufferViewReadWriteSpecialized`|5.6E+06 ± 2.6E+05 ops/s| +|`complexBufferReadWrite`|6.6E+06 ± 2.7E+05 ops/s| +|`doubleArrayReadWrite`|7.5E+06 ± 1.0E+06 ops/s| +|`doubleBufferReadWrite`|8.0E+06 ± 6.7E+05 ops/s| +### [DotBenchmark](src/jvmMain/kotlin/space/kscience/kmath/benchmarks/DotBenchmark.kt) + +| Benchmark | Score | +|:---------:|:-----:| +|`bufferedDot`|1.3 ± 0.020 ops/s| +|`cmDot`|0.47 ± 0.42 ops/s| +|`cmDotWithConversion`|0.76 ± 0.13 ops/s| +|`ejmlDot`|6.7 ± 0.091 ops/s| +|`ejmlDotWithConversion`|6.4 ± 0.82 ops/s| +|`multikDot`|40 ± 6.7 ops/s| +|`parallelDot`|12 ± 1.8 ops/s| +|`tensorDot`|1.2 ± 0.041 ops/s| +|`tfDot`|5.9 ± 0.49 ops/s| +### [ExpressionsInterpretersBenchmark](src/jvmMain/kotlin/space/kscience/kmath/benchmarks/ExpressionsInterpretersBenchmark.kt) + +| Benchmark | Score | +|:---------:|:-----:| +|`asmGenericExpression`|29 ± 1.2 ops/s| +|`asmPrimitiveExpression`|43 ± 1.3 ops/s| +|`asmPrimitiveExpressionArray`|71 ± 0.38 ops/s| +|`functionalExpression`|5.6 ± 0.11 ops/s| +|`justCalculate`|69 ± 9.0 ops/s| +|`mstExpression`|7.1 ± 0.020 ops/s| +|`rawExpression`|41 ± 1.5 ops/s| +### [IntegrationBenchmark](src/jvmMain/kotlin/space/kscience/kmath/benchmarks/IntegrationBenchmark.kt) + +| Benchmark | Score | +|:---------:|:-----:| +|`complexIntegration`|3.6E+03 ± 1.9E+02 ops/s| +|`doubleIntegration`|3.7E+03 ± 12 ops/s| +### [JafamaBenchmark](src/jvmMain/kotlin/space/kscience/kmath/benchmarks/JafamaBenchmark.kt) + +| Benchmark | Score | +|:---------:|:-----:| +|`core`|38 ± 0.64 ops/s| +|`jafama`|52 ± 0.36 ops/s| +|`strictJafama`|52 ± 4.0 ops/s| +### [MatrixInverseBenchmark](src/jvmMain/kotlin/space/kscience/kmath/benchmarks/MatrixInverseBenchmark.kt) + +| Benchmark | Score | +|:---------:|:-----:| +|`cmLUPInversion`|2.2E+03 ± 76 ops/s| +|`ejmlInverse`|1.3E+03 ± 5.7 ops/s| +|`kmathLupInversion`|9.5E+02 ± 1.8E+02 ops/s| +|`kmathParallelLupInversion`|9.1E+02 ± 1.4E+02 ops/s| +### [NDFieldBenchmark](src/jvmMain/kotlin/space/kscience/kmath/benchmarks/NDFieldBenchmark.kt) + +| Benchmark | Score | +|:---------:|:-----:| +|`boxingFieldAdd`|7.7 ± 0.79 ops/s| +|`multikAdd`|6.5 ± 0.33 ops/s| +|`multikInPlaceAdd`|64 ± 0.79 ops/s| +|`specializedFieldAdd`|8.0 ± 0.090 ops/s| +|`tensorAdd`|9.2 ± 0.053 ops/s| +|`tensorInPlaceAdd`|17 ± 10 ops/s| +|`viktorAdd`|7.6 ± 1.2 ops/s| +### [ViktorBenchmark](src/jvmMain/kotlin/space/kscience/kmath/benchmarks/ViktorBenchmark.kt) + +| Benchmark | Score | +|:---------:|:-----:| +|`doubleFieldAddition`|7.7 ± 0.34 ops/s| +|`rawViktor`|5.9 ± 1.1 ops/s| +|`viktorFieldAddition`|7.3 ± 1.1 ops/s| +### [ViktorLogBenchmark](src/jvmMain/kotlin/space/kscience/kmath/benchmarks/ViktorLogBenchmark.kt) + +| Benchmark | Score | +|:---------:|:-----:| +|`rawViktorLog`|1.4 ± 0.076 ops/s| +|`realFieldLog`|1.3 ± 0.069 ops/s| +|`viktorFieldLog`|1.3 ± 0.032 ops/s| diff --git a/benchmarks/build.gradle.kts b/benchmarks/build.gradle.kts index ccbb99927..2bdab89fb 100644 --- a/benchmarks/build.gradle.kts +++ b/benchmarks/build.gradle.kts @@ -3,10 +3,6 @@ import com.fasterxml.jackson.module.kotlin.readValue import kotlinx.benchmark.gradle.BenchmarksExtension import java.time.LocalDateTime import java.time.ZoneId -import java.time.format.DateTimeFormatter -import java.time.format.DateTimeFormatterBuilder -import java.time.format.SignStyle -import java.time.temporal.ChronoField.* import java.util.* plugins { @@ -220,41 +216,20 @@ readme { val jsonMapper = jacksonObjectMapper() - - val ISO_DATE_TIME: DateTimeFormatter = DateTimeFormatterBuilder().run { - parseCaseInsensitive() - appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD) - appendLiteral('-') - appendValue(MONTH_OF_YEAR, 2) - appendLiteral('-') - appendValue(DAY_OF_MONTH, 2) - appendLiteral('T') - appendValue(HOUR_OF_DAY, 2) - appendLiteral('.') - appendValue(MINUTE_OF_HOUR, 2) - optionalStart() - appendLiteral('.') - appendValue(SECOND_OF_MINUTE, 2) - optionalStart() - appendFraction(NANO_OF_SECOND, 0, 9, true) - optionalStart() - appendOffsetId() - optionalStart() - appendLiteral('[') - parseCaseSensitive() - appendZoneRegionId() - appendLiteral(']') - toFormatter() - } - fun noun(number: Number, singular: String, plural: String) = if (number.toLong() == 1L) singular else plural extensions.findByType(BenchmarksExtension::class.java)?.configurations?.forEach { cfg -> - property("benchmark${cfg.name.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }}") { - val launches = layout.buildDirectory.dir("reports/benchmarks/${cfg.name}").get() + val propertyName = + "benchmark${cfg.name.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }}" - val resDirectory = launches.files().maxByOrNull { - LocalDateTime.parse(it.name, ISO_DATE_TIME).atZone(ZoneId.systemDefault()).toInstant() + logger.info("Processing benchmark data from benchmark ${cfg.name} into readme property $propertyName") + + val launches = layout.buildDirectory.dir("reports/benchmarks/${cfg.name}").get().asFile + if (!launches.exists()) return@forEach + + property(propertyName) { + val resDirectory = launches.listFiles()?.maxByOrNull { + LocalDateTime.parse(it.name).atZone(ZoneId.systemDefault()).toInstant() } if (resDirectory == null || !(resDirectory.resolve("jvm.json")).exists()) { @@ -264,10 +239,7 @@ readme { jsonMapper.readValue>(resDirectory.resolve("jvm.json")) buildString { - appendLine("
") - appendLine("") - appendLine("Report for benchmark configuration ${cfg.name}") - appendLine("") + appendLine("## Report for benchmark configuration ${cfg.name}") appendLine() val first = reports.first() @@ -289,15 +261,19 @@ readme { } by ${first.measurementTime}." ) - appendLine() - appendLine("| Benchmark | Score |") - appendLine("|:---------:|:-----:|") + reports.groupBy { it.benchmark.substringBeforeLast(".") }.forEach { (cl, compare) -> + appendLine("### [${cl.substringAfterLast(".")}](src/jvmMain/kotlin/${cl.replace(".","/")}.kt)") + appendLine() + appendLine("| Benchmark | Score |") + appendLine("|:---------:|:-----:|") + compare.forEach { report -> + val benchmarkName = report.benchmark.substringAfterLast(".") + val score = String.format("%.2G", report.primaryMetric.score) + val error = String.format("%.2G", report.primaryMetric.scoreError) - reports.forEach { report -> - appendLine("|`${report.benchmark}`|${report.primaryMetric.score} ± ${report.primaryMetric.scoreError} ${report.primaryMetric.scoreUnit}|") + appendLine("|`$benchmarkName`|$score ± $error ${report.primaryMetric.scoreUnit}|") + } } - - appendLine("
") } } } diff --git a/benchmarks/docs/README-TEMPLATE.md b/benchmarks/docs/README-TEMPLATE.md new file mode 100644 index 000000000..eaac18cd8 --- /dev/null +++ b/benchmarks/docs/README-TEMPLATE.md @@ -0,0 +1,5 @@ +# BenchmarksResult + +${benchmarkMain} + + diff --git a/examples/src/main/kotlin/space/kscience/kmath/stat/DistributionBenchmark.kt b/examples/src/main/kotlin/space/kscience/kmath/stat/DistributionBenchmark.kt index 89a1cdc28..062057ae7 100644 --- a/examples/src/main/kotlin/space/kscience/kmath/stat/DistributionBenchmark.kt +++ b/examples/src/main/kotlin/space/kscience/kmath/stat/DistributionBenchmark.kt @@ -36,6 +36,26 @@ private suspend fun runKMathChained(): Duration { return Duration.between(startTime, Instant.now()) } +private fun runKMathBlocking(): Duration { + val generator = RandomGenerator.fromSource(RandomSource.MT, 123L) + val normal = GaussianSampler(7.0, 2.0) + val chain = normal.sample(generator) + val startTime = Instant.now() + var sum = 0.0 + + repeat(10000001) { counter -> + sum += chain.nextBlocking() + + if (counter % 100000 == 0) { + val duration = Duration.between(startTime, Instant.now()) + val meanValue = sum / counter + println("Chain sampler completed $counter elements in $duration: $meanValue") + } + } + + return Duration.between(startTime, Instant.now()) +} + private fun runCMDirect(): Duration { val rng = RandomSource.MT.create(123L) @@ -67,6 +87,8 @@ private fun runCMDirect(): Duration { fun main(): Unit = runBlocking(Dispatchers.Default) { val directJob = async { runCMDirect() } val chainJob = async { runKMathChained() } + val blockingJob = async { runKMathBlocking() } println("KMath Chained: ${chainJob.await()}") + println("KMath Blocking: ${blockingJob.await()}") println("Apache Direct: ${directJob.await()}") } diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/MatrixBuilder.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/MatrixBuilder.kt index 18e1edb56..0b3ac56fd 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/MatrixBuilder.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/MatrixBuilder.kt @@ -67,13 +67,14 @@ public fun > MatrixBuilder.symmetric( ): Matrix { require(columns == rows) { "In order to build symmetric matrix, number of rows $rows should be equal to number of columns $columns" } return with(BufferAccessor2D(rows, rows, MutableBufferFactory(type))) { - val cache = factory(rows * rows) { null } + val cache = HashMap() linearSpace.buildMatrix(rows, rows) { i, j -> - val cached = cache[i, j] + val index = intArrayOf(i, j) + val cached = cache[index] if (cached == null) { val value = if (i <= j) builder(i, j) else builder(j, i) - cache[i, j] = value - cache[j, i] = value + cache[index] = value + cache[index] = value value } else { cached diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/samplers/RandomForkingEvent.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/samplers/RandomForkingSampler.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/samplers/RandomForkingEvent.kt rename to kmath-stat/src/commonMain/kotlin/space/kscience/kmath/samplers/RandomForkingSampler.kt