Skip to content

Commit 732460a

Browse files
committed
Change CommonExecutor strategy
before this commit strategy was repeat(cycles) { timer { cycle() } } after the commit is timer { repeat { cycle() } } New strategy should be better for microbenchmarking (because of low timer resolution)
1 parent 6b10a1d commit 732460a

File tree

3 files changed

+68
-42
lines changed

3 files changed

+68
-42
lines changed

runtime/commonMain/src/kotlinx/benchmark/CommonSuiteExecutor.kt

Lines changed: 60 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,10 @@ abstract class CommonSuiteExecutor(
1717
benchmark.suite.setup(instance)
1818
var exception: Throwable? = null
1919
val samples = try {
20-
val measurer = createIterationMeasurer(instance, benchmark, configuration)
21-
val cycles = warmup(benchmark, configuration, measurer)
22-
DoubleArray(configuration.iterations) { iteration ->
23-
val nanosecondsPerOperation = measure(cycles, measurer)
24-
val text = nanosecondsPerOperation.nanosToText(configuration.mode, configuration.outputTimeUnit)
25-
reporter.output(executionName, id, "Iteration #$iteration: $text")
26-
nanosecondsPerOperation
27-
}
20+
val cycles = estimateCycles(instance, benchmark, configuration)
21+
val measurer = createIterationMeasurer(instance, benchmark, configuration, cycles)
22+
warmup(id, configuration, cycles, measurer)
23+
measure(id, configuration, cycles, measurer)
2824
} catch (e: Throwable) {
2925
exception = e
3026
doubleArrayOf()
@@ -73,47 +69,63 @@ abstract class CommonSuiteExecutor(
7369
complete()
7470
}
7571

76-
private inline fun measure(cycles: Int, measurer: () -> Long): Double {
77-
var iterations = 0
78-
var elapsedTime = 0L
79-
do {
80-
val subIterationDuration = measurer()
81-
elapsedTime += subIterationDuration
82-
iterations++
83-
} while (iterations < cycles)
84-
85-
return elapsedTime.toDouble() / cycles
86-
}
87-
88-
private fun <T> warmup(benchmark: BenchmarkDescriptor<T>, configuration: BenchmarkConfiguration, measurer: () -> Long): Int {
89-
var iterations = 0
90-
val benchmarkIterationTime = configuration.iterationTime * configuration.iterationTimeUnit.toMultiplier()
91-
var currentIteration = 0
92-
93-
while(currentIteration < configuration.warmups) {
94-
iterations = 0
72+
private fun <T> estimateCycles(
73+
instance: T,
74+
benchmark: BenchmarkDescriptor<T>,
75+
configuration: BenchmarkConfiguration
76+
): Int {
77+
val estimator = wrapBenchmarkFunction(instance, benchmark) { body ->
78+
var iterations = 0
9579
var elapsedTime = 0L
80+
val benchmarkIterationTime = configuration.iterationTime * configuration.iterationTimeUnit.toMultiplier()
9681
do {
97-
val subIterationDuration = measurer()
82+
val subIterationDuration = measureTime(body)
9883
elapsedTime += subIterationDuration
9984
iterations++
10085
} while (elapsedTime < benchmarkIterationTime)
86+
iterations
87+
}
88+
return estimator()
89+
}
10190

102-
val metricInNanos = elapsedTime.toDouble() / iterations
91+
private fun warmup(
92+
id: String,
93+
configuration: BenchmarkConfiguration,
94+
cycles: Int,
95+
measurer: () -> Long
96+
) {
97+
var currentIteration = 0
98+
while(currentIteration < configuration.warmups) {
99+
val elapsedTime = measurer()
100+
val metricInNanos = elapsedTime.toDouble() / cycles
103101
val sample = metricInNanos.nanosToText(configuration.mode, configuration.outputTimeUnit)
104-
reporter.output(executionName, benchmark.name, "Warm-up #$currentIteration: $sample")
102+
reporter.output(executionName, id, "Warm-up #$currentIteration: $sample")
105103
currentIteration++
106104
}
107-
return iterations
108105
}
109106

110-
protected open fun <T> createIterationMeasurer(instance: T, benchmark: BenchmarkDescriptor<T>, configuration: BenchmarkConfiguration): () -> Long = when(benchmark) {
107+
private fun measure(
108+
id: String,
109+
configuration: BenchmarkConfiguration,
110+
cycles: Int,
111+
measurer: () -> Long
112+
) : DoubleArray = DoubleArray(configuration.iterations) { iteration ->
113+
val nanosecondsPerOperation = measurer().toDouble() / cycles
114+
val text = nanosecondsPerOperation.nanosToText(configuration.mode, configuration.outputTimeUnit)
115+
reporter.output(executionName, id, "Iteration #$iteration: $text")
116+
nanosecondsPerOperation
117+
}
118+
119+
private inline fun <T, R> wrapBenchmarkFunction(
120+
instance: T,
121+
benchmark: BenchmarkDescriptor<T>,
122+
crossinline wrapper: (() -> Unit) -> R): () -> R = when(benchmark) {
111123
is BenchmarkDescriptorWithBlackholeParameter -> {
112124
{
113125
val localBlackhole = benchmark.blackhole
114126
val localDelegate = benchmark.function
115127
val localInstance = instance
116-
measureTime {
128+
wrapper {
117129
localBlackhole.consume(localInstance.localDelegate(localBlackhole))
118130
}
119131
}
@@ -123,12 +135,26 @@ abstract class CommonSuiteExecutor(
123135
val localBlackhole = benchmark.blackhole
124136
val localDelegate = benchmark.function
125137
val localInstance = instance
126-
measureTime {
138+
wrapper {
127139
localBlackhole.consume(localInstance.localDelegate())
128140
}
129141
}
130142
}
131143
else -> error("Unexpected ${benchmark::class.simpleName}")
132144
}
145+
146+
protected open fun <T> createIterationMeasurer(
147+
instance: T,
148+
benchmark: BenchmarkDescriptor<T>,
149+
configuration: BenchmarkConfiguration,
150+
cycles: Int
151+
): () -> Long = wrapBenchmarkFunction(instance, benchmark) { payload ->
152+
var cycle = cycles
153+
measureTime {
154+
while(cycle-- > 0) {
155+
payload()
156+
}
157+
}
158+
}
133159
}
134160

runtime/jsMain/src/kotlinx/benchmark/js/JsBuiltInExecutor.kt

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
package kotlinx.benchmark.js
22

3-
import kotlinx.benchmark.BenchmarkConfiguration
4-
import kotlinx.benchmark.BenchmarkDescriptor
5-
import kotlinx.benchmark.CommonSuitExecutor
3+
import kotlinx.benchmark.*
64
import kotlinx.benchmark.jsEngineSupport
7-
import kotlin.math.sin
85

96
@JsName("Function")
107
private external fun functionCtor(params: String, code: String): (dynamic) -> Long
@@ -28,8 +25,13 @@ class JsBuiltInExecutor(
2825
}
2926
}
3027

31-
override fun <T> createIterationMeasurer(instance: T, benchmark: BenchmarkDescriptor<T>, configuration: BenchmarkConfiguration): () -> Long {
32-
val measurer = super.createIterationMeasurer(instance, benchmark, configuration)
28+
override fun <T> createIterationMeasurer(
29+
instance: T,
30+
benchmark: BenchmarkDescriptor<T>,
31+
configuration: BenchmarkConfiguration,
32+
cycles: Int
33+
): () -> Long {
34+
val measurer = super.createIterationMeasurer(instance, benchmark, configuration, cycles)
3335
return if (configuration.jsUseBridge) createJsMeasurerBridge(measurer) else measurer
3436
}
3537
}

runtime/wasmMain/src/kotlinx/benchmark/Utils.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package kotlinx.benchmark
22

3-
import kotlin.time.Duration.Companion.milliseconds
4-
53
@JsFun("(d, precision, useGrouping) => d.toLocaleString(undefined, { maximumFractionDigits: precision, minimumFractionDigits: precision, useGrouping: useGrouping } )")
64
private external fun format(d: Double, precision: Int, useGrouping: Boolean): String
75

0 commit comments

Comments
 (0)