Skip to content

Commit 0f4e034

Browse files
committed
Add common time measurer
1) which is used by CommonSuitExecutor 2) that is implemented for JS and WASM only with performance.now()
1 parent c121225 commit 0f4e034

File tree

12 files changed

+122
-31
lines changed

12 files changed

+122
-31
lines changed

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

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

3-
import kotlin.time.*
4-
53
abstract class CommonSuitExecutor(
64
executionName: String,
75
configPath: String,
@@ -109,26 +107,25 @@ abstract class CommonSuitExecutor(
109107
return iterations
110108
}
111109

112-
@OptIn(ExperimentalTime::class)
113110
protected open fun <T> createIterationMeasurer(instance: T, benchmark: BenchmarkDescriptor<T>, configuration: BenchmarkConfiguration): () -> Long = when(benchmark) {
114111
is BenchmarkDescriptorWithBlackholeParameter -> {
115112
{
116113
val localBlackhole = benchmark.blackhole
117114
val localDelegate = benchmark.function
118115
val localInstance = instance
119-
TimeSource.Monotonic.measureTime {
116+
measureTime {
120117
localBlackhole.consume(localInstance.localDelegate(localBlackhole))
121-
}.inWholeNanoseconds
118+
}
122119
}
123120
}
124121
is BenchmarkDescriptorWithNoBlackholeParameter -> {
125122
{
126123
val localBlackhole = benchmark.blackhole
127124
val localDelegate = benchmark.function
128125
val localInstance = instance
129-
TimeSource.Monotonic.measureTime {
126+
measureTime {
130127
localBlackhole.consume(localInstance.localDelegate())
131-
}.inWholeNanoseconds
128+
}
132129
}
133130
}
134131
else -> error("Unexpected ${benchmark::class.simpleName}")

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ class ReportBenchmarksStatistics(values: DoubleArray) {
8686
}
8787
}
8888

89-
expect fun Double.format(precision: Int, useGrouping: Boolean = true): String
89+
internal expect fun Double.format(precision: Int, useGrouping: Boolean = true): String
9090

9191
fun Double.formatSignificant(precision: Int): String {
9292
val d = (precision - ceil(log10(this)).toInt()).coerceAtLeast(0) // display 4 significant digits
Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
package kotlinx.benchmark
22

3-
expect fun String.readFile(): String
3+
internal expect fun String.readFile(): String
44

5-
expect fun String.writeFile(text: String)
5+
internal expect fun String.writeFile(text: String)
6+
7+
/*
8+
* Measure time in nanoseconds for given body
9+
*/
10+
internal expect inline fun measureTime(block: () -> Unit): Long

runtime/jsMain/src/kotlinx/benchmark/D8EngineSupport.kt

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

3+
import kotlin.time.Duration.Companion.milliseconds
4+
35
private external fun read(path: String): String
46
private external fun write(text: String)
57

@@ -16,4 +18,14 @@ internal object D8EngineSupport : JsEngineSupport() {
1618
val arguments = js("globalThis.arguments.join(' ')") as String
1719
return arguments.split(' ').toTypedArray()
1820
}
21+
}
22+
23+
internal inline fun d8MeasureTime(block: () -> Unit): Long {
24+
val performance = js("(typeof self !== 'undefined' ? self : globalThis).performance")
25+
val start = performance.now()
26+
block()
27+
val end = performance.now()
28+
val startInNs = (start as Double).milliseconds.inWholeNanoseconds
29+
val endInNs = (end as Double).milliseconds.inWholeNanoseconds
30+
return endInNs - startInNs
1931
}

runtime/jsMain/src/kotlinx/benchmark/NodeJsEngineSupport.kt

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

3+
import kotlin.time.DurationUnit
4+
import kotlin.time.toDuration
5+
36
internal external fun require(module: String): dynamic
47

58
internal object NodeJsEngineSupport : JsEngineSupport() {
@@ -13,4 +16,15 @@ internal object NodeJsEngineSupport : JsEngineSupport() {
1316
val arguments = js("process.argv.slice(2).join(' ')") as String
1417
return arguments.split(' ').toTypedArray()
1518
}
19+
}
20+
21+
private fun hrTimeToNs(hrTime: dynamic): Long = (hrTime as Array<Double>).let { (seconds, nanos) ->
22+
seconds.toDuration(DurationUnit.SECONDS) + nanos.toDuration(DurationUnit.NANOSECONDS) }.inWholeNanoseconds
23+
24+
internal inline fun nodeJsMeasureTime(block: () -> Unit): Long {
25+
val process = js("process")
26+
val start = process.hrtime()
27+
block()
28+
val end = process.hrtime()
29+
return hrTimeToNs(end) - hrTimeToNs(start)
1630
}
Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
package kotlinx.benchmark
22

3-
actual fun Double.format(precision: Int, useGrouping: Boolean): String {
3+
internal actual fun Double.format(precision: Int, useGrouping: Boolean): String {
44
val options = js("{maximumFractionDigits:2, minimumFractionDigits:2, useGrouping:true}")
55
options.minimumFractionDigits = precision
66
options.maximumFractionDigits = precision
77
options.useGrouping = useGrouping
88
return this.asDynamic().toLocaleString(undefined, options) as String
99
}
1010

11-
actual fun String.writeFile(text: String): Unit = jsEngineSupport.writeFile(this, text)
11+
internal actual fun String.writeFile(text: String): Unit = jsEngineSupport.writeFile(this, text)
1212

13-
actual fun String.readFile(): String = jsEngineSupport.readFile(this)
13+
internal actual fun String.readFile(): String = jsEngineSupport.readFile(this)
1414

1515
internal abstract class JsEngineSupport {
1616
abstract fun writeFile(path: String, text: String)
@@ -20,9 +20,13 @@ internal abstract class JsEngineSupport {
2020
abstract fun arguments(): Array<out String>
2121
}
2222

23-
internal fun isD8(): Boolean =
23+
internal val isD8: Boolean by lazy {
2424
js("typeof d8 !== 'undefined'") as Boolean
25+
}
2526

2627
internal val jsEngineSupport: JsEngineSupport by lazy {
27-
if (isD8()) D8EngineSupport else NodeJsEngineSupport
28-
}
28+
if (isD8) D8EngineSupport else NodeJsEngineSupport
29+
}
30+
31+
internal actual inline fun measureTime(block: () -> Unit): Long =
32+
if (isD8) d8MeasureTime(block) else nodeJsMeasureTime(block)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class JsExecutor(name: String, @Suppress("UNUSED_PARAMETER") dummy_args: Array<o
77
SuiteExecutor(name, jsEngineSupport.arguments()[0]) {
88

99
init {
10-
check(!isD8()) { "${JsExecutor::class.simpleName} does not supports d8 engine" }
10+
check(!isD8) { "${JsExecutor::class.simpleName} does not supports d8 engine" }
1111
}
1212

1313
private val benchmarkJs: dynamic = require("benchmark")

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,17 @@ package kotlinx.benchmark
22

33
import java.io.*
44

5-
actual fun Double.format(precision: Int, useGrouping: Boolean): String {
5+
internal actual fun Double.format(precision: Int, useGrouping: Boolean): String {
66
return if (useGrouping) "%,.0${precision}f".format(this)
77
else "%.0${precision}f".format(this)
88
}
99

10-
actual fun String.readFile(): String {
10+
internal actual fun String.readFile(): String {
1111
return File(this).readText()
1212
}
1313

14-
actual fun String.writeFile(text: String) {
14+
internal actual fun String.writeFile(text: String) {
1515
File(this).writeText(text)
16-
}
16+
}
17+
18+
internal actual inline fun measureTime(block: () -> Unit): Long = TODO("Not implemented for this platform")

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import kotlinx.benchmark.native.NativeExecutor
44
import kotlinx.cinterop.*
55
import platform.posix.*
66

7-
actual fun Double.format(precision: Int, useGrouping: Boolean): String {
7+
internal actual fun Double.format(precision: Int, useGrouping: Boolean): String {
88
val longPart = toLong()
99
val fractional = this - longPart
1010
val thousands =
@@ -21,7 +21,7 @@ actual fun Double.format(precision: Int, useGrouping: Boolean): String {
2121
}
2222
}
2323

24-
actual fun String.writeFile(text: String) {
24+
internal actual fun String.writeFile(text: String) {
2525
val file = fopen(this, "w")
2626
try {
2727
if (fputs(text, file) == EOF) throw Error("File write error")
@@ -30,7 +30,7 @@ actual fun String.writeFile(text: String) {
3030
}
3131
}
3232

33-
actual fun String.readFile(): String = buildString {
33+
internal actual fun String.readFile(): String = buildString {
3434
val file = fopen(this@readFile, "rb")
3535
try {
3636
memScoped {
@@ -60,4 +60,6 @@ internal fun String.parseBenchmarkConfig(): NativeExecutor.BenchmarkRun {
6060
val configuration = BenchmarkConfiguration.parse(lines[1].getElement("configuration"))
6161
val parameters = lines[2].getElement("parameters").parseMap()
6262
return NativeExecutor.BenchmarkRun(name, configuration, parameters)
63-
}
63+
}
64+
65+
internal actual inline fun measureTime(block: () -> Unit): Long = TODO("Not implemented for this platform")

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

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

3+
import kotlin.time.Duration.Companion.milliseconds
4+
35
@JsFun("(path) => globalThis.read(path)")
46
private external fun d8ReadFile(path: String): String
57

@@ -23,4 +25,20 @@ internal object D8EngineSupport : JsEngineSupport() {
2325

2426
override fun arguments(): Array<out String> =
2527
d8Arguments().split(' ').toTypedArray()
28+
}
29+
30+
@JsFun("() => (typeof self !== 'undefined' ? self : globalThis).performance")
31+
private external fun getPerformance(): ExternalInterfaceType
32+
33+
@JsFun("(performance) => performance.now()")
34+
private external fun performanceNow(performance: ExternalInterfaceType): Double
35+
36+
internal inline fun d8MeasureTime(block: () -> Unit): Long {
37+
val performance = getPerformance()
38+
val start = performanceNow(performance)
39+
block()
40+
val end = performanceNow(performance)
41+
val startInNs = start.milliseconds.inWholeNanoseconds
42+
val endInNs = end.milliseconds.inWholeNanoseconds
43+
return endInNs - startInNs
2644
}

0 commit comments

Comments
 (0)