Skip to content

Commit c121225

Browse files
committed
Add deoptimizer to JsCommonExecutor
1 parent bcc12fb commit c121225

File tree

2 files changed

+62
-61
lines changed

2 files changed

+62
-61
lines changed

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

Lines changed: 35 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ abstract class CommonSuitExecutor(
1919
benchmark.suite.setup(instance)
2020
var exception: Throwable? = null
2121
val samples = try {
22-
val cycles = warmup(benchmark, configuration, instance)
22+
val measurer = createIterationMeasurer(instance, benchmark, configuration)
23+
val cycles = warmup(benchmark, configuration, measurer)
2324
DoubleArray(configuration.iterations) { iteration ->
24-
val nanosecondsPerOperation = measure(instance, benchmark, cycles)
25+
val nanosecondsPerOperation = measure(cycles, measurer)
2526
val text = nanosecondsPerOperation.nanosToText(configuration.mode, configuration.outputTimeUnit)
2627
reporter.output(executionName, id, "Iteration #$iteration: $text")
2728
nanosecondsPerOperation
@@ -74,86 +75,60 @@ abstract class CommonSuitExecutor(
7475
complete()
7576
}
7677

77-
@Suppress("REDUNDANT_ELSE_IN_WHEN")
78-
private val BenchmarkTimeUnit.asDurationTimeUnit: DurationUnit get() = when(this) {
79-
BenchmarkTimeUnit.NANOSECONDS -> DurationUnit.NANOSECONDS
80-
BenchmarkTimeUnit.MICROSECONDS -> DurationUnit.MICROSECONDS
81-
BenchmarkTimeUnit.MILLISECONDS -> DurationUnit.MILLISECONDS
82-
BenchmarkTimeUnit.SECONDS -> DurationUnit.SECONDS
83-
BenchmarkTimeUnit.MINUTES -> DurationUnit.MINUTES
84-
else -> TODO("Implement ${this.name}")
85-
}
78+
private inline fun measure(cycles: Int, measurer: () -> Long): Double {
79+
var iterations = 0
80+
var elapsedTime = 0L
81+
do {
82+
val subIterationDuration = measurer()
83+
elapsedTime += subIterationDuration
84+
iterations++
85+
} while (iterations < cycles)
8686

87-
@OptIn(ExperimentalTime::class)
88-
private inline fun measure(cycles: Int, payload: () -> Unit): Double {
89-
var counter = cycles
90-
val time = TimeSource.Monotonic.measureTime {
91-
while (counter-- > 0) {
92-
payload()
93-
}
94-
}
95-
return time.inWholeNanoseconds.toDouble() / cycles
87+
return elapsedTime.toDouble() / cycles
9688
}
9789

98-
@OptIn(ExperimentalTime::class)
99-
private inline fun <T> measureWarmup(benchmark: BenchmarkDescriptor<T>, configuration: BenchmarkConfiguration, payload: () -> Unit): Int {
90+
private fun <T> warmup(benchmark: BenchmarkDescriptor<T>, configuration: BenchmarkConfiguration, measurer: () -> Long): Int {
10091
var iterations = 0
101-
val benchmarkIterationTimeAsDuration =
102-
configuration.iterationTime.toDuration(configuration.iterationTimeUnit.asDurationTimeUnit)
103-
92+
val benchmarkIterationTime = configuration.iterationTime * configuration.iterationTimeUnit.toMultiplier()
10493
var currentIteration = 0
10594

106-
val timeSource: TimeSource = TimeSource.Monotonic
10795
while(currentIteration < configuration.warmups) {
10896
iterations = 0
109-
var elapsedTime: Duration
110-
val startTime = timeSource.markNow()
111-
val maxTime = startTime + benchmarkIterationTimeAsDuration
97+
var elapsedTime = 0L
11298
do {
113-
payload()
114-
elapsedTime = startTime.elapsedNow()
99+
val subIterationDuration = measurer()
100+
elapsedTime += subIterationDuration
115101
iterations++
116-
} while (maxTime.hasNotPassedNow())
102+
} while (elapsedTime < benchmarkIterationTime)
117103

118-
val metricInNanos = elapsedTime.inWholeNanoseconds.toDouble() / iterations
104+
val metricInNanos = elapsedTime.toDouble() / iterations
119105
val sample = metricInNanos.nanosToText(configuration.mode, configuration.outputTimeUnit)
120106
reporter.output(executionName, benchmark.name, "Warm-up #$currentIteration: $sample")
121107
currentIteration++
122108
}
123109
return iterations
124110
}
125111

126-
private fun <T> warmup(benchmark: BenchmarkDescriptor<T>, configuration: BenchmarkConfiguration, instance: T): Int = when(benchmark) {
127-
is BenchmarkDescriptorWithBlackholeParameter -> {
128-
val blackhole = benchmark.blackhole
129-
val delegate = benchmark.function
130-
measureWarmup(benchmark, configuration) {
131-
blackhole.consume(instance.delegate(blackhole))
132-
}
133-
}
134-
is BenchmarkDescriptorWithNoBlackholeParameter -> {
135-
val blackhole = benchmark.blackhole
136-
val delegate = benchmark.function
137-
measureWarmup(benchmark, configuration) {
138-
blackhole.consume(instance.delegate())
139-
}
140-
}
141-
else -> error("Unexpected ${benchmark::class.simpleName}")
142-
}
143-
144-
private fun <T> measure(instance: T, benchmark: BenchmarkDescriptor<T>, cycles: Int): Double = when(benchmark) {
112+
@OptIn(ExperimentalTime::class)
113+
protected open fun <T> createIterationMeasurer(instance: T, benchmark: BenchmarkDescriptor<T>, configuration: BenchmarkConfiguration): () -> Long = when(benchmark) {
145114
is BenchmarkDescriptorWithBlackholeParameter -> {
146-
val blackhole = benchmark.blackhole
147-
val delegate = benchmark.function
148-
measure(cycles) {
149-
blackhole.consume(instance.delegate(blackhole))
115+
{
116+
val localBlackhole = benchmark.blackhole
117+
val localDelegate = benchmark.function
118+
val localInstance = instance
119+
TimeSource.Monotonic.measureTime {
120+
localBlackhole.consume(localInstance.localDelegate(localBlackhole))
121+
}.inWholeNanoseconds
150122
}
151123
}
152124
is BenchmarkDescriptorWithNoBlackholeParameter -> {
153-
val blackhole = benchmark.blackhole
154-
val delegate = benchmark.function
155-
measure(cycles) {
156-
blackhole.consume(instance.delegate())
125+
{
126+
val localBlackhole = benchmark.blackhole
127+
val localDelegate = benchmark.function
128+
val localInstance = instance
129+
TimeSource.Monotonic.measureTime {
130+
localBlackhole.consume(localInstance.localDelegate())
131+
}.inWholeNanoseconds
157132
}
158133
}
159134
else -> error("Unexpected ${benchmark::class.simpleName}")
Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,35 @@
11
package kotlinx.benchmark.js
22

3+
import kotlinx.benchmark.BenchmarkConfiguration
4+
import kotlinx.benchmark.BenchmarkDescriptor
35
import kotlinx.benchmark.CommonSuitExecutor
46
import kotlinx.benchmark.jsEngineSupport
7+
import kotlin.math.sin
8+
9+
@JsName("Function")
10+
private external fun functionCtor(params: String, code: String): (dynamic) -> Long
511

612
class JsSimpleExecutor(
713
name: String,
814
@Suppress("UNUSED_PARAMETER") dummy_args: Array<out String>
9-
) : CommonSuitExecutor(name, jsEngineSupport.arguments()[0])
15+
) : CommonSuitExecutor(name, jsEngineSupport.arguments()[0]) {
16+
17+
private val BenchmarkConfiguration.jsUseBridge: Boolean
18+
get() = "true".equals(advanced["jsUseBridge"], ignoreCase = true)
19+
20+
private fun createJsMeasurerBridge(originalMeasurer: () -> Long): () -> Long {
21+
val bridgeObject = object {
22+
fun invoke(): Long = originalMeasurer.invoke()
23+
}
24+
val measurerString = bridgeObject::invoke.toString()
25+
val measurerBody = measurerString.substringAfter("{").substringBeforeLast("}")
26+
return {
27+
functionCtor("\$boundThis", measurerBody)(bridgeObject)
28+
}
29+
}
30+
31+
override fun <T> createIterationMeasurer(instance: T, benchmark: BenchmarkDescriptor<T>, configuration: BenchmarkConfiguration): () -> Long {
32+
val measurer = super.createIterationMeasurer(instance, benchmark, configuration)
33+
return if (configuration.jsUseBridge) createJsMeasurerBridge(measurer) else measurer
34+
}
35+
}

0 commit comments

Comments
 (0)