bf97e28b6d
benchmark_harness's `measureFor` returns in microseconds, but we currently show the numbers with the unit "ns". Convert the microseconds reported by benchmark_harness to nanoseconds when reporting. We could also report in microseconds, but the numbers would be very small, with a few zeros before fractional digits. So keep reporting in nanoseconds. Change-Id: I189190b2139c8e2d39b8bef84159585ab0967980 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/436904 Commit-Queue: Ömer Ağacan <omersa@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com>
347 lines
9.2 KiB
Dart
347 lines
9.2 KiB
Dart
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
// Benchmarks passing small and large integers to JS via `js_interop`.
|
|
//
|
|
// In Wasm, integers that fit into 31 bits can be passed without allocation by
|
|
// by passing them as `i31ref`. To take advantage of this, dart2wasm checks the
|
|
// size of the integer before passing to JS and passes the integer as `i31ref`
|
|
// when possible.
|
|
//
|
|
// This benchmark compares performance of `int` passing for integers that fit
|
|
// into 31 bits and those that don't.
|
|
|
|
import 'dart:js_interop';
|
|
|
|
import 'package:benchmark_harness/benchmark_harness.dart';
|
|
|
|
@JS()
|
|
external void eval(String code);
|
|
|
|
// These return `void` to avoid adding `dartify` overheads to the benchmark
|
|
// results.
|
|
// V8 can't figure out that these don't do anything so the loops and JS calls
|
|
// aren't eliminated.
|
|
@JS()
|
|
external void intFun(int i);
|
|
|
|
@JS()
|
|
external void doubleFun(double d);
|
|
|
|
// Run benchmarked code for at least 2 seconds.
|
|
const int minimumMeasureDurationMillis = 2000;
|
|
|
|
class IntPassingBenchmark {
|
|
final int start;
|
|
final int end;
|
|
|
|
IntPassingBenchmark(this.start, this.end);
|
|
|
|
double measure() =>
|
|
BenchmarkBase.measureFor(() {
|
|
for (int i = start; i < end; i += 1) {
|
|
intFun(i);
|
|
}
|
|
}, minimumMeasureDurationMillis) /
|
|
(end - start);
|
|
}
|
|
|
|
class DoublePassingBenchmark {
|
|
final double start;
|
|
final double step;
|
|
final int calls;
|
|
|
|
DoublePassingBenchmark(this.start, this.step, this.calls);
|
|
|
|
double measure() =>
|
|
BenchmarkBase.measureFor(() {
|
|
double d = start;
|
|
for (int i = 0; i < calls; i += 1) {
|
|
doubleFun(d);
|
|
d *= step;
|
|
}
|
|
}, minimumMeasureDurationMillis) /
|
|
calls;
|
|
}
|
|
|
|
@JS()
|
|
external bool dartifyBool();
|
|
|
|
@JS()
|
|
external bool? dartifyNullableBool();
|
|
|
|
@JS()
|
|
external num dartifyNum();
|
|
|
|
@JS()
|
|
external num? dartifyNullableNum();
|
|
|
|
@JS()
|
|
external double dartifyDouble();
|
|
|
|
@JS()
|
|
external double? dartifyNullableDouble();
|
|
|
|
@JS()
|
|
external int dartifyInt();
|
|
|
|
@JS()
|
|
external int? dartifyNullableInt();
|
|
|
|
@JS()
|
|
external String dartifyString();
|
|
|
|
@JS()
|
|
external String? dartifyNullableString();
|
|
|
|
@JS()
|
|
external JSArray dartifyJSArray();
|
|
|
|
@JS()
|
|
external JSArray? dartifyNullableJSArray();
|
|
|
|
// `dart:typed_data` types are all boxed the same way, so no need to test each
|
|
// one of them separately.
|
|
@JS()
|
|
external JSUint8Array dartifyJSUint8Array();
|
|
|
|
@JS()
|
|
external JSUint8Array? dartifyNullableJSUint8Array();
|
|
|
|
const int ITERATIONS = 10000;
|
|
|
|
bool boolSink = false;
|
|
|
|
double dartifyBoolBenchmark() =>
|
|
BenchmarkBase.measureFor(() {
|
|
for (int i = 0; i < ITERATIONS; i += 1) {
|
|
boolSink = dartifyBool();
|
|
}
|
|
}, minimumMeasureDurationMillis) /
|
|
ITERATIONS;
|
|
|
|
bool? nullableBoolSink;
|
|
|
|
double dartifyNullableBoolBenchmark() =>
|
|
BenchmarkBase.measureFor(() {
|
|
for (int i = 0; i < ITERATIONS; i += 1) {
|
|
nullableBoolSink = dartifyNullableBool();
|
|
}
|
|
}, minimumMeasureDurationMillis) /
|
|
ITERATIONS;
|
|
|
|
num numSink = 1;
|
|
|
|
double dartifyNumBenchmark() =>
|
|
BenchmarkBase.measureFor(() {
|
|
for (int i = 0; i < ITERATIONS; i += 1) {
|
|
numSink = dartifyNum();
|
|
}
|
|
}, minimumMeasureDurationMillis) /
|
|
ITERATIONS;
|
|
|
|
num? nullableNumSink;
|
|
|
|
double dartifyNullableNumBenchmark() =>
|
|
BenchmarkBase.measureFor(() {
|
|
for (int i = 0; i < ITERATIONS; i += 1) {
|
|
nullableNumSink = dartifyNullableNum();
|
|
}
|
|
}, minimumMeasureDurationMillis) /
|
|
ITERATIONS;
|
|
|
|
double doubleSink = 0.0;
|
|
|
|
double dartifyDoubleBenchmark() =>
|
|
BenchmarkBase.measureFor(() {
|
|
for (int i = 0; i < ITERATIONS; i += 1) {
|
|
doubleSink = dartifyDouble();
|
|
}
|
|
}, minimumMeasureDurationMillis) /
|
|
ITERATIONS;
|
|
|
|
double? nullableDoubleSink;
|
|
|
|
double dartifyNullableDoubleBenchmark() =>
|
|
BenchmarkBase.measureFor(() {
|
|
for (int i = 0; i < ITERATIONS; i += 1) {
|
|
nullableDoubleSink = dartifyNullableDouble();
|
|
}
|
|
}, minimumMeasureDurationMillis) /
|
|
ITERATIONS;
|
|
|
|
int intSink = 0;
|
|
|
|
double dartifyIntBenchmark() =>
|
|
BenchmarkBase.measureFor(() {
|
|
for (int i = 0; i < ITERATIONS; i += 1) {
|
|
intSink = dartifyInt();
|
|
}
|
|
}, minimumMeasureDurationMillis) /
|
|
ITERATIONS;
|
|
|
|
int? nullableIntSink;
|
|
|
|
double dartifyNullableIntBenchmark() =>
|
|
BenchmarkBase.measureFor(() {
|
|
for (int i = 0; i < ITERATIONS; i += 1) {
|
|
nullableIntSink = dartifyNullableInt();
|
|
}
|
|
}, minimumMeasureDurationMillis) /
|
|
ITERATIONS;
|
|
|
|
String stringSink = '';
|
|
|
|
double dartifyStringBenchmark() =>
|
|
BenchmarkBase.measureFor(() {
|
|
for (int i = 0; i < ITERATIONS; i += 1) {
|
|
stringSink = dartifyString();
|
|
}
|
|
}, minimumMeasureDurationMillis) /
|
|
ITERATIONS;
|
|
|
|
String? nullableStringSink;
|
|
|
|
double dartifyNullableStringBenchmark() =>
|
|
BenchmarkBase.measureFor(() {
|
|
for (int i = 0; i < ITERATIONS; i += 1) {
|
|
nullableStringSink = dartifyNullableString();
|
|
}
|
|
}, minimumMeasureDurationMillis) /
|
|
ITERATIONS;
|
|
|
|
JSArray jsArraySink = JSArray();
|
|
|
|
double dartifyJSArrayBenchmark() =>
|
|
BenchmarkBase.measureFor(() {
|
|
for (int i = 0; i < ITERATIONS; i += 1) {
|
|
jsArraySink = dartifyJSArray();
|
|
}
|
|
}, minimumMeasureDurationMillis) /
|
|
ITERATIONS;
|
|
|
|
JSArray? nullableJsArraySink;
|
|
|
|
double dartifyNullableJSArrayBenchmark() =>
|
|
BenchmarkBase.measureFor(() {
|
|
for (int i = 0; i < ITERATIONS; i += 1) {
|
|
nullableJsArraySink = dartifyNullableJSArray();
|
|
}
|
|
}, minimumMeasureDurationMillis) /
|
|
ITERATIONS;
|
|
|
|
JSUint8Array jsUint8ArraySink = JSUint8Array();
|
|
|
|
double dartifyJSUint8ArrayBenchmark() =>
|
|
BenchmarkBase.measureFor(() {
|
|
for (int i = 0; i < ITERATIONS; i += 1) {
|
|
jsUint8ArraySink = dartifyJSUint8Array();
|
|
}
|
|
}, minimumMeasureDurationMillis) /
|
|
ITERATIONS;
|
|
|
|
JSUint8Array? nullableJsUint8ArraySink;
|
|
|
|
double dartifyNullableJSUint8ArrayBenchmark() =>
|
|
BenchmarkBase.measureFor(() {
|
|
for (int i = 0; i < ITERATIONS; i += 1) {
|
|
nullableJsUint8ArraySink = dartifyNullableJSUint8Array();
|
|
}
|
|
}, minimumMeasureDurationMillis) /
|
|
ITERATIONS;
|
|
|
|
void main() {
|
|
eval('''
|
|
self.intFun = (i) => i;
|
|
self.doubleFun = (d) => d;
|
|
self.dartifyBool = () => true;
|
|
self.dartifyNullableBool = () => false;
|
|
self.dartifyNum = () => 12.34;
|
|
self.dartifyNullableNum = () => 56.78;
|
|
self.dartifyDouble = () => 10.20;
|
|
self.dartifyNullableDouble = () => 30.40;
|
|
self.dartifyInt = () => 123;
|
|
self.dartifyNullableInt = () => 456;
|
|
self.dartifyString = () => "abc";
|
|
self.dartifyNullableString = () => "def";
|
|
|
|
// Arrays are all empty to not add element conversion overheads to the
|
|
// benchmark results.
|
|
self.dartifyJSArray = () => new Array();
|
|
self.dartifyNullableJSArray = () => new Array();
|
|
self.dartifyJSUint8Array = () => new Uint8Array(0);
|
|
self.dartifyNullableJSUint8Array = () => new Uint8Array(0);
|
|
''');
|
|
|
|
final maxI31 = (1 << 30) - 1;
|
|
|
|
final small = IntPassingBenchmark(maxI31 - 1000000, maxI31).measure();
|
|
report('WasmJSInterop.call.void.1ArgsSmi', small);
|
|
|
|
final large = IntPassingBenchmark(maxI31 + 1, maxI31 + 1000001).measure();
|
|
report('WasmJSInterop.call.void.1ArgsInt', large);
|
|
|
|
// Have more than one call site to the `double` benchmark to avoid inlining
|
|
// too much, and for fair comparison with the `int` benchmark above.
|
|
DoublePassingBenchmark(1.0, 1.0, 10).measure();
|
|
final double = DoublePassingBenchmark(1.0, 12.34, 1000000).measure();
|
|
report('WasmJSInterop.call.void.1ArgsDouble', double);
|
|
|
|
report('WasmJSInterop.call.bool.0Args', dartifyBoolBenchmark());
|
|
report(
|
|
'WasmJSInterop.call.nullableBool.0Args',
|
|
dartifyNullableBoolBenchmark(),
|
|
);
|
|
report('WasmJSInterop.call.num.0Args', dartifyNumBenchmark());
|
|
report('WasmJSInterop.call.nullableNum.0Args', dartifyNullableNumBenchmark());
|
|
report('WasmJSInterop.call.double.0Args', dartifyDoubleBenchmark());
|
|
report(
|
|
'WasmJSInterop.call.nullableDouble.0Args',
|
|
dartifyNullableDoubleBenchmark(),
|
|
);
|
|
report('WasmJSInterop.call.int.0Args', dartifyIntBenchmark());
|
|
report('WasmJSInterop.call.nullableInt.0Args', dartifyNullableIntBenchmark());
|
|
report('WasmJSInterop.call.string.0Args', dartifyStringBenchmark());
|
|
report(
|
|
'WasmJSInterop.call.nullableString.0Args',
|
|
dartifyNullableStringBenchmark(),
|
|
);
|
|
report('WasmJSInterop.call.JSArray.0Args', dartifyJSArrayBenchmark());
|
|
report(
|
|
'WasmJSInterop.call.nullableJSArray.0Args',
|
|
dartifyNullableJSArrayBenchmark(),
|
|
);
|
|
report(
|
|
'WasmJSInterop.call.JSUint8Array.0Args',
|
|
dartifyJSUint8ArrayBenchmark(),
|
|
);
|
|
report(
|
|
'WasmJSInterop.call.nullableJSUint8Array.0Args',
|
|
dartifyNullableJSUint8ArrayBenchmark(),
|
|
);
|
|
|
|
// To keep the sinks alive
|
|
if (int.parse('1') == 0) {
|
|
print(boolSink);
|
|
print(nullableBoolSink);
|
|
print(numSink);
|
|
print(nullableNumSink);
|
|
print(doubleSink);
|
|
print(nullableDoubleSink);
|
|
print(intSink);
|
|
print(nullableIntSink);
|
|
print(stringSink);
|
|
print(nullableStringSink);
|
|
print(jsArraySink);
|
|
print(nullableJsArraySink);
|
|
print(jsUint8ArraySink);
|
|
print(nullableJsUint8ArraySink);
|
|
}
|
|
}
|
|
|
|
/// Reports in Golem-specific format.
|
|
void report(String name, double usPerCall) {
|
|
print('$name(RunTimeRaw): ${usPerCall * 1000} ns.');
|
|
}
|