Skip to content

Commit cc54cc2

Browse files
committed
Detect benchmarks that return promise and handle them as deferred benchmarks
1 parent f47d242 commit cc54cc2

File tree

3 files changed

+78
-8
lines changed

3 files changed

+78
-8
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package test
2+
3+
import org.jetbrains.gradle.benchmarks.*
4+
import kotlin.js.*
5+
import kotlin.math.*
6+
7+
@State(Scope.Benchmark)
8+
class JsAsyncBenchmarks {
9+
10+
private var data = 0.0
11+
12+
@Setup
13+
fun setUp() {
14+
data = 42.0
15+
}
16+
17+
@Benchmark
18+
fun baseline(): Double {
19+
// This benchmarks shows that benchmarks.js fails a baseline test :)
20+
return data
21+
}
22+
23+
@Benchmark
24+
fun promiseResolve(): Promise<Double> {
25+
return Promise.resolve(data)
26+
}
27+
28+
@Benchmark
29+
fun promiseResolveDoubleChain(): Promise<Double> {
30+
return Promise.resolve(Unit).then { data }
31+
}
32+
33+
@Benchmark
34+
fun promiseResolveTripleChain(): Promise<Double> {
35+
return Promise.resolve(Unit).then { Unit }.then { data }
36+
}
37+
38+
@Benchmark
39+
fun promiseDelayedBaseline(): Promise<Double> {
40+
// Score of this benchmark cannot be greater than 10 ops/sec
41+
return Promise { resolve, reject ->
42+
setTimeout({
43+
resolve(data)
44+
}, 100)
45+
}
46+
}
47+
48+
}
49+
50+
private external fun setTimeout(handler: dynamic, timeout: Int = definedExternally): Int

plugin/main/src/org/jetbrains/gradle/benchmarks/SuiteSourceGenerator.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,10 @@ class SuiteSourceGenerator(val title: String, val module: ModuleDescriptor, val
121121
function(addFunctionName) {
122122
addParameter("suite", suiteType)
123123
for (fn in benchmarkFunctions) {
124+
val returnsPromise = fn.returnsPromise()
124125
val functionName = fn.name.toString()
125126
addStatement(
126-
"suite.add(%P, _instance::%N, this::$setupFunctionName, this::$teardownFunctionName)",
127+
"suite.add(%P, _instance::%N, this::$setupFunctionName, this::$teardownFunctionName, $returnsPromise)",
127128
"${originalClass.canonicalName}.$functionName",
128129
functionName
129130
)
@@ -136,6 +137,9 @@ class SuiteSourceGenerator(val title: String, val module: ModuleDescriptor, val
136137

137138
file.writeTo(output)
138139
}
140+
141+
private fun FunctionDescriptor.returnsPromise() =
142+
returnType?.constructor?.declarationDescriptor?.fqNameOrNull()?.asString() == "kotlin.js.Promise"
139143
}
140144

141145
inline fun codeBlock(builderAction: CodeBlock.Builder.() -> Unit): CodeBlock {

runtime/jsMain/src/org/jetbrains/gradle/benchmarks/js/JsBenchmarkSuite.kt

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ package org.jetbrains.gradle.benchmarks.js
22

33
import kotlinx.cli.*
44
import org.jetbrains.gradle.benchmarks.*
5+
import kotlin.js.*
56
import kotlin.math.*
67

78
external fun require(module: String): dynamic
89
private val benchmarkJs: dynamic = require("benchmark")
9-
private val fs = require("fs");
10-
private val process = require("process");
10+
private val fs = require("fs")
11+
private val process = require("process")
1112

1213
class Suite(val title: String, @Suppress("UNUSED_PARAMETER") dummy_args: Array<out String>) {
1314
private val args = RunnerCommandLine().also { it.parse((process["argv"] as Array<String>).drop(2)) }
@@ -34,12 +35,26 @@ class Suite(val title: String, @Suppress("UNUSED_PARAMETER") dummy_args: Array<o
3435
}
3536
else -> throw UnsupportedOperationException("Format ${args.traceFormat} is not supported.")
3637
}
37-
suite.run()
38-
fs.writeFile(args.reportFile, results.toJson()) { err -> if (err) throw err }
38+
39+
suite.run().on("complete") {
40+
fs.writeFile(args.reportFile, results.toJson()) { err -> if (err != null) throw err }
41+
}
3942
}
4043

41-
fun add(name: String, function: () -> Any?, setup: () -> Unit, teardown: () -> Unit) {
42-
suite.add(name, function)
44+
fun add(name: String, function: () -> Any?, setup: () -> Unit, teardown: () -> Unit, isAsynchronous: Boolean = false) {
45+
/*
46+
* `isAsynchronous` is set to true iff benchmark return type is `Promise`.
47+
* In that case, special treatment by benchmark.js is required
48+
*/
49+
if (isAsynchronous) {
50+
suite.add(name) { deferred: Promise<Unit> ->
51+
// Mind asDynamic: this is **not** a regular promise
52+
(function() as Promise<*>).then { (deferred.asDynamic()).resolve() }
53+
}
54+
} else {
55+
suite.add(name, function)
56+
}
57+
4358
val benchmark = suite[suite.length - 1] // take back last added benchmark and subscribe to events
4459

4560
// TODO: Configure properly
@@ -49,6 +64,8 @@ class Suite(val title: String, @Suppress("UNUSED_PARAMETER") dummy_args: Array<o
4964

5065
benchmark.options.minTime = args.iterationTime / 1000.0
5166
benchmark.options.maxTime = args.iterationTime * args.iterations / 1000.0
67+
benchmark.options.async = isAsynchronous
68+
benchmark.options.defer = isAsynchronous
5269

5370
benchmark.on("start") { event ->
5471
val benchmarkFQN = event.target.name
@@ -87,7 +104,6 @@ class Suite(val title: String, @Suppress("UNUSED_PARAMETER") dummy_args: Array<o
87104
else -> throw UnsupportedOperationException("Format ${args.traceFormat} is not supported.")
88105
}
89106

90-
91107
results.add(result)
92108
}
93109
}

0 commit comments

Comments
 (0)