Skip to content

Commit 777fcc9

Browse files
committed
Blackhole implementation
1 parent 20fb95f commit 777fcc9

File tree

15 files changed

+279
-180
lines changed

15 files changed

+279
-180
lines changed

plugin/main/src/kotlinx/benchmark/gradle/SuiteSourceGenerator.kt

Lines changed: 31 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,35 @@ import org.jetbrains.kotlin.resolve.scopes.*
1212
import org.jetbrains.kotlin.types.*
1313
import java.io.*
1414

15-
enum class Platform(val executorClass: String, val suiteDescriptorClass: String, val benchmarkDescriptorClass: String) {
15+
enum class Platform(
16+
val executorClass: String,
17+
val suiteDescriptorClass: String,
18+
val benchmarkDescriptorClass: String,
19+
val benchmarkDescriptorWithBlackholeParameterClass: String
20+
) {
1621
JsBuiltIn(
1722
executorClass = "kotlinx.benchmark.js.JsSimpleExecutor",
1823
suiteDescriptorClass = "kotlinx.benchmark.SuiteDescriptor",
19-
benchmarkDescriptorClass = "kotlinx.benchmark.js.JsBenchmarkDescriptor",
24+
benchmarkDescriptorClass = "kotlinx.benchmark.js.JsBenchmarkDescriptorWithNoBlackholeParameter",
25+
benchmarkDescriptorWithBlackholeParameterClass = "kotlinx.benchmark.js.JsBenchmarkDescriptorWithBlackholeParameter",
2026
),
2127
JsBenchmarkJs(
2228
executorClass = "kotlinx.benchmark.js.JsExecutor",
2329
suiteDescriptorClass = "kotlinx.benchmark.SuiteDescriptor",
24-
benchmarkDescriptorClass = "kotlinx.benchmark.js.JsBenchmarkDescriptor",
30+
benchmarkDescriptorClass = "kotlinx.benchmark.js.JsBenchmarkDescriptorWithNoBlackholeParameter",
31+
benchmarkDescriptorWithBlackholeParameterClass = "kotlinx.benchmark.js.JsBenchmarkDescriptorWithBlackholeParameter",
2532
),
2633
NativeBuiltIn(
2734
executorClass = "kotlinx.benchmark.native.NativeExecutor",
2835
suiteDescriptorClass = "kotlinx.benchmark.SuiteDescriptor",
29-
benchmarkDescriptorClass = "kotlinx.benchmark.native.NativeBenchmarkDescriptor",
36+
benchmarkDescriptorClass = "kotlinx.benchmark.BenchmarkDescriptorWithNoBlackholeParameter",
37+
benchmarkDescriptorWithBlackholeParameterClass = "kotlinx.benchmark.BenchmarkDescriptorWithBlackholeParameter",
3038
),
3139
WasmBuiltIn(
3240
executorClass = "kotlinx.benchmark.wasm.WasmExecutor",
3341
suiteDescriptorClass = "kotlinx.benchmark.SuiteDescriptor",
34-
benchmarkDescriptorClass = "kotlinx.benchmark.wasm.WasmBenchmarkDescriptor",
42+
benchmarkDescriptorClass = "kotlinx.benchmark.BenchmarkDescriptorWithNoBlackholeParameter",
43+
benchmarkDescriptorWithBlackholeParameterClass = "kotlinx.benchmark.BenchmarkDescriptorWithBlackholeParameter",
3544
)
3645
}
3746

@@ -40,7 +49,6 @@ class SuiteSourceGenerator(val title: String, val module: ModuleDescriptor, val
4049
val setupFunctionName = "setUp"
4150
val teardownFunctionName = "tearDown"
4251
val parametersFunctionName = "parametrize"
43-
val bindBlackholeFunctionName = "bind"
4452

4553
val externalConfigurationFQN = "kotlinx.benchmark.ExternalConfiguration"
4654
val benchmarkAnnotationFQN = "kotlinx.benchmark.Benchmark"
@@ -65,7 +73,6 @@ class SuiteSourceGenerator(val title: String, val module: ModuleDescriptor, val
6573

6674
private val executorType = ClassName.bestGuess(platform.executorClass)
6775
private val suiteDescriptorType = ClassName.bestGuess(platform.suiteDescriptorClass)
68-
private val benchmarkDescriptorType = ClassName.bestGuess(platform.benchmarkDescriptorClass)
6976

7077
val benchmarks = mutableListOf<ClassName>()
7178

@@ -130,6 +137,7 @@ class SuiteSourceGenerator(val title: String, val module: ModuleDescriptor, val
130137
val outputTimeUnitValue = outputTimeAnnotation?.argumentValue("value") as? EnumValue
131138
val outputTimeUnit = outputTimeUnitValue?.enumEntryName?.toString()
132139

140+
@Suppress("UNCHECKED_CAST")
133141
val modesValue = modeAnnotation?.argumentValue("value")?.value as? List<EnumValue>
134142
val mode = modesValue?.single()?.enumEntryName?.toString()
135143

@@ -190,32 +198,11 @@ class SuiteSourceGenerator(val title: String, val module: ModuleDescriptor, val
190198
}
191199
}
192200

193-
val bhClass = ClassName.bestGuess(blackholeFQN)
194-
195-
/*
196-
private fun bind(function: CommonBenchmark.(Blackhole) -> Any?, bh: Blackhole): CommonBenchmark.() -> Any? {
197-
return { function(bh) }
198-
}
199-
*/
200-
function(bindBlackholeFunctionName) {
201-
addModifiers(KModifier.PRIVATE)
202-
203-
val bhParam = ParameterSpec.unnamed(bhClass)
204-
val bhBenchType = LambdaTypeName.get(originalClass, listOf(bhParam), ANY.copy(nullable = true))
205-
val boundBenchType = LambdaTypeName.get(originalClass, emptyList(), ANY.copy(nullable = true))
206-
207-
addParameter("function", bhBenchType)
208-
addParameter("bh", bhClass)
209-
returns(boundBenchType)
210-
211-
addStatement("return { function(bh) }")
212-
}
213-
214-
215201
val defaultParameters = parameterProperties.associateBy({ it.name }, {
216202
val annotation = it.annotations.findAnnotation(FqName(paramAnnotationFQN))!!
217203
val constant = annotation.argumentValue("value")
218204
?: error("@Param annotation should have at least one default value")
205+
@Suppress("UNCHECKED_CAST")
219206
val values = constant.value as? List<StringValue>
220207
?: error("@Param annotation should have at least one default value")
221208
values
@@ -271,31 +258,25 @@ class SuiteSourceGenerator(val title: String, val module: ModuleDescriptor, val
271258
)
272259
addCode(")\n»")
273260
addStatement("")
261+
262+
val bhClass = ClassName.bestGuess(blackholeFQN)
274263
for (fn in benchmarkFunctions) {
275264
val functionName = fn.name.toString()
276265

277266
val hasABlackholeParameter = fn.valueParameters.singleOrNull()?.type.toString() == "Blackhole"
278-
if (hasABlackholeParameter) {
279-
println("WARNING: Blackhole works incorrectly on JS")
280-
281-
addStatement(
282-
"descriptor.add(%T(%S, descriptor, %N(%T::%N, %T())))",
283-
benchmarkDescriptorType,
284-
"${originalClass.canonicalName}.$functionName",
285-
bindBlackholeFunctionName,
286-
originalClass,
287-
functionName,
288-
bhClass
289-
)
290-
}
291-
else
292-
addStatement(
293-
"descriptor.add(%T(%S, descriptor, %T::%N))",
294-
benchmarkDescriptorType,
295-
"${originalClass.canonicalName}.$functionName",
296-
originalClass,
297-
functionName
298-
)
267+
268+
val fqnDescriptorToCreate =
269+
if (hasABlackholeParameter) platform.benchmarkDescriptorWithBlackholeParameterClass
270+
else platform.benchmarkDescriptorClass
271+
272+
addStatement(
273+
"descriptor.add(%T(%S, descriptor, %T(), %T::%N))",
274+
ClassName.bestGuess(fqnDescriptorToCreate),
275+
"${originalClass.canonicalName}.$functionName",
276+
bhClass,
277+
originalClass,
278+
functionName
279+
)
299280
}
300281
addStatement("return descriptor")
301282
}
Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,21 @@
11
package kotlinx.benchmark
22

3-
open class BenchmarkDescriptor<T>(
3+
abstract class BenchmarkDescriptor<T>(
44
val name: String,
55
val suite: SuiteDescriptor<T>,
6-
val function: T.() -> Any?
7-
)
6+
val blackhole: Blackhole,
7+
)
8+
9+
open class BenchmarkDescriptorWithNoBlackholeParameter<T>(
10+
name: String,
11+
suite: SuiteDescriptor<T>,
12+
blackhole: Blackhole,
13+
val function: T.() -> Any?,
14+
) : BenchmarkDescriptor<T>(name, suite, blackhole)
15+
16+
open class BenchmarkDescriptorWithBlackholeParameter<T>(
17+
name: String,
18+
suite: SuiteDescriptor<T>,
19+
blackhole: Blackhole,
20+
val function: T.(Blackhole) -> Any?,
21+
) : BenchmarkDescriptor<T>(name, suite, blackhole)

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,6 @@ expect class Blackhole {
1010
fun consume(l: Long)
1111
fun consume(f: Float)
1212
fun consume(d: Double)
13-
}
13+
}
14+
15+
expect fun Blackhole.flush()
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package kotlinx.benchmark
2+
3+
private const val MAGIC_SIZE: Int = 13
4+
5+
open class CommonBlackhole {
6+
private val arrayOfAny: Array<Any?> = arrayOfNulls(MAGIC_SIZE)
7+
private var currentAnyPosition: Int = 0
8+
private fun consumeAny(obj: Any?) {
9+
arrayOfAny[currentAnyPosition] = obj
10+
currentAnyPosition = if (currentAnyPosition == MAGIC_SIZE - 1) 0 else currentAnyPosition + 1
11+
}
12+
13+
private val arrayOfInt: IntArray = IntArray(MAGIC_SIZE)
14+
private var currentIntPosition: Int = 0
15+
private fun consumeInt(i: Int) {
16+
arrayOfInt[currentIntPosition] = i
17+
currentIntPosition = if (currentIntPosition == MAGIC_SIZE - 1) 0 else currentIntPosition + 1
18+
}
19+
20+
fun flushMe() {
21+
val sums = arrayOfAny.sumOf { it.hashCode() } + arrayOfInt.sum()
22+
println("Consumed blackhole value: $sums")
23+
}
24+
25+
fun consume(obj: Any?) = consumeAny(obj)
26+
27+
fun consume(bool: Boolean) = consumeInt(bool.hashCode())
28+
29+
fun consume(c: Char) = consumeInt(c.hashCode())
30+
31+
fun consume(b: Byte) = consumeInt(b.hashCode())
32+
33+
fun consume(s: Short) = consumeInt(s.hashCode())
34+
35+
fun consume(i: Int) = consumeInt(i.hashCode())
36+
37+
fun consume(l: Long) = consumeInt(l.hashCode())
38+
39+
fun consume(f: Float) = consumeInt(f.hashCode())
40+
41+
fun consume(d: Double) = consumeInt(d.hashCode())
42+
}

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

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ abstract class CommonSuitExecutor(
3131
doubleArrayOf()
3232
} finally {
3333
benchmark.suite.teardown(instance)
34+
benchmark.blackhole.flush()
3435
}
3536
if (exception != null) {
3637
val error = exception.toString()
@@ -73,20 +74,6 @@ abstract class CommonSuitExecutor(
7374
complete()
7475
}
7576

76-
@OptIn(ExperimentalTime::class)
77-
private fun <T> measure(instance: T, benchmark: BenchmarkDescriptor<T>, cycles: Int): Double {
78-
val executeFunction = benchmark.function
79-
var counter = cycles
80-
81-
val time = TimeSource.Monotonic.measureTime {
82-
while (counter-- > 0) {
83-
instance.executeFunction()
84-
}
85-
}
86-
87-
return time.inWholeNanoseconds.toDouble() / cycles
88-
}
89-
9077
@Suppress("REDUNDANT_ELSE_IN_WHEN")
9178
private val BenchmarkTimeUnit.asDurationTimeUnit: DurationUnit get() = when(this) {
9279
BenchmarkTimeUnit.NANOSECONDS -> DurationUnit.NANOSECONDS
@@ -98,23 +85,32 @@ abstract class CommonSuitExecutor(
9885
}
9986

10087
@OptIn(ExperimentalTime::class)
101-
private fun <T> warmup(benchmark: BenchmarkDescriptor<T>, configuration: BenchmarkConfiguration, instance: T): Int {
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
96+
}
97+
98+
@OptIn(ExperimentalTime::class)
99+
private inline fun <T> measureWarmup(benchmark: BenchmarkDescriptor<T>, configuration: BenchmarkConfiguration, payload: () -> Unit): Int {
102100
var iterations = 0
103101
val benchmarkIterationTimeAsDuration =
104102
configuration.iterationTime.toDuration(configuration.iterationTimeUnit.asDurationTimeUnit)
105103

106-
val executeFunction = benchmark.function
107104
var currentIteration = 0
108-
val warmups = configuration.warmups
109105

110106
val timeSource: TimeSource = TimeSource.Monotonic
111-
while(currentIteration < warmups) {
107+
while(currentIteration < configuration.warmups) {
112108
iterations = 0
113109
var elapsedTime: Duration
114110
val startTime = timeSource.markNow()
115111
val maxTime = startTime + benchmarkIterationTimeAsDuration
116112
do {
117-
instance.executeFunction()
113+
payload()
118114
elapsedTime = startTime.elapsedNow()
119115
iterations++
120116
} while (maxTime.hasNotPassedNow())
@@ -126,5 +122,41 @@ abstract class CommonSuitExecutor(
126122
}
127123
return iterations
128124
}
125+
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) {
145+
is BenchmarkDescriptorWithBlackholeParameter -> {
146+
val blackhole = benchmark.blackhole
147+
val delegate = benchmark.function
148+
measure(cycles) {
149+
blackhole.consume(instance.delegate(blackhole))
150+
}
151+
}
152+
is BenchmarkDescriptorWithNoBlackholeParameter -> {
153+
val blackhole = benchmark.blackhole
154+
val delegate = benchmark.function
155+
measure(cycles) {
156+
blackhole.consume(instance.delegate())
157+
}
158+
}
159+
else -> error("Unexpected ${benchmark::class.simpleName}")
160+
}
129161
}
130162

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

3-
actual class Blackhole {
4-
actual fun consume(obj: Any?) {}
5-
actual fun consume(bool: Boolean) {}
6-
actual fun consume(c: Char) {}
7-
actual fun consume(b: Byte) {}
8-
actual fun consume(s: Short) {}
9-
actual fun consume(i: Int) {}
10-
actual fun consume(l: Long) {}
11-
actual fun consume(f: Float) {}
12-
actual fun consume(d: Double) {}
13-
}
3+
actual class Blackhole : CommonBlackhole()
4+
5+
actual fun Blackhole.flush() = flushMe()

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

Lines changed: 0 additions & 18 deletions
This file was deleted.

0 commit comments

Comments
 (0)