mirror of
https://github.com/dart-lang/sdk
synced 2024-10-14 10:48:25 +00:00
7ec9514093
Adds a benchmark to test converting Dart functions to and from JS as well as calling them. Specifically, tests some combinations of the following: - Converting vs calling functions - JS vs Dart functions - Calling instance methods vs static methods vs closure vs closures stored in fields Change-Id: I8f5b63781201042c4068437fe84c3043d6dfb446 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/368064 Reviewed-by: Stephen Adams <sra@google.com> Commit-Queue: Srujan Gaddam <srujzs@google.com>
508 lines
13 KiB
Dart
508 lines
13 KiB
Dart
// Copyright (c) 2024, 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.
|
|
|
|
import 'dart:js_interop';
|
|
import 'dart:math' as math;
|
|
|
|
import 'package:benchmark_harness/benchmark_harness.dart';
|
|
|
|
// Benchmark for converting Dart functions to JS and calling them.
|
|
//
|
|
// Contains the following benchmarks:
|
|
// - A series that determines how expensive calling `Function.toJS` is. These
|
|
// are prefixed with `Convert`. These contain two dimensions in their names:
|
|
// - Whether the function that is being wrapped is an `Instance` method,
|
|
// `Static` method, `Closure`, or a closure that is stored in a field
|
|
// (`ClosureField`).
|
|
// - The arity of the function: `Zero`, `Two`, and `Eight` arguments.
|
|
// - A series that determines how expensive calling the result of a
|
|
// `Function.toJS`-wrapped function using interop is. These are prefixed with
|
|
// `CallJS`. These contain the same dimensions as the `Convert` series, except
|
|
// there is no `ClosureField` series as it would be the same as `Closure`.
|
|
// - A series that determines how expensive calling a closure that's stored in a
|
|
// field in Dart is. These are prefixed with `CallDartClosure`. They only
|
|
// contain one dimension, which is the arity of the function. This is used to
|
|
// compare the performance of calling a wrapped function versus calling it
|
|
// directly.
|
|
|
|
// Caching the function to a global should help avoid V8 from optimizing the
|
|
// call away.
|
|
@JS()
|
|
external set cache(JSFunction jsFunction);
|
|
|
|
extension on JSFunction {
|
|
external int call(
|
|
[JSAny? thisArg,
|
|
int? arg1,
|
|
int? arg2,
|
|
int? arg3,
|
|
int? arg4,
|
|
int? arg5,
|
|
int? arg6,
|
|
int? arg7,
|
|
int? arg8]);
|
|
}
|
|
|
|
final random = math.Random();
|
|
|
|
class ConvertInstanceZeroBenchmark extends BenchmarkBase {
|
|
ConvertInstanceZeroBenchmark() : super('FunctionToJs.Convert.Instance.0');
|
|
|
|
final int randomInt = random.nextInt(10);
|
|
|
|
@pragma('dart2js:never-inline')
|
|
int zero() => randomInt;
|
|
|
|
@override
|
|
void run() {
|
|
cache = zero.toJS;
|
|
}
|
|
}
|
|
|
|
class ConvertInstanceTwoBenchmark extends BenchmarkBase {
|
|
ConvertInstanceTwoBenchmark() : super('FunctionToJs.Convert.Instance.2');
|
|
|
|
final int randomInt = random.nextInt(10);
|
|
|
|
@pragma('dart2js:never-inline')
|
|
int two(int arg1, int arg2) => randomInt + arg1 + arg2;
|
|
|
|
@override
|
|
void run() {
|
|
cache = two.toJS;
|
|
}
|
|
}
|
|
|
|
class ConvertInstanceEightBenchmark extends BenchmarkBase {
|
|
ConvertInstanceEightBenchmark() : super('FunctionToJs.Convert.Instance.8');
|
|
|
|
final int randomInt = random.nextInt(10);
|
|
|
|
@pragma('dart2js:never-inline')
|
|
int eight(int arg1, int arg2, int arg3, int arg4, int arg5, int arg6,
|
|
int arg7, int arg8) =>
|
|
randomInt + arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8;
|
|
|
|
@override
|
|
void run() {
|
|
cache = eight.toJS;
|
|
}
|
|
}
|
|
|
|
class ConvertStaticZeroBenchmark extends BenchmarkBase {
|
|
ConvertStaticZeroBenchmark() : super('FunctionToJs.Convert.Static.0');
|
|
|
|
static final int randomInt = random.nextInt(10);
|
|
|
|
static int zero() => randomInt;
|
|
|
|
@override
|
|
void run() {
|
|
cache = zero.toJS;
|
|
}
|
|
}
|
|
|
|
class ConvertStaticTwoBenchmark extends BenchmarkBase {
|
|
ConvertStaticTwoBenchmark() : super('FunctionToJs.Convert.Static.2');
|
|
|
|
static final int randomInt = random.nextInt(10);
|
|
|
|
static int two(int arg1, int arg2) => randomInt + arg1 + arg2;
|
|
|
|
@override
|
|
void run() {
|
|
cache = two.toJS;
|
|
}
|
|
}
|
|
|
|
class ConvertStaticEightBenchmark extends BenchmarkBase {
|
|
ConvertStaticEightBenchmark() : super('FunctionToJs.Convert.Static.8');
|
|
|
|
static final int randomInt = random.nextInt(10);
|
|
|
|
static int eight(int arg1, int arg2, int arg3, int arg4, int arg5, int arg6,
|
|
int arg7, int arg8) =>
|
|
randomInt + arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8;
|
|
|
|
@override
|
|
void run() {
|
|
cache = eight.toJS;
|
|
}
|
|
}
|
|
|
|
class ConvertClosureZeroBenchmark extends BenchmarkBase {
|
|
ConvertClosureZeroBenchmark() : super('FunctionToJs.Convert.Closure.0');
|
|
|
|
final int randomInt = random.nextInt(10);
|
|
|
|
@override
|
|
void run() {
|
|
cache = (() => randomInt).toJS;
|
|
}
|
|
}
|
|
|
|
class ConvertClosureTwoBenchmark extends BenchmarkBase {
|
|
ConvertClosureTwoBenchmark() : super('FunctionToJs.Convert.Closure.2');
|
|
|
|
final int randomInt = random.nextInt(10);
|
|
|
|
@override
|
|
void run() {
|
|
cache = ((int arg1, int arg2) => randomInt + arg1 + arg2).toJS;
|
|
}
|
|
}
|
|
|
|
class ConvertClosureEightBenchmark extends BenchmarkBase {
|
|
ConvertClosureEightBenchmark() : super('FunctionToJs.Convert.Closure.8');
|
|
|
|
final int randomInt = random.nextInt(10);
|
|
|
|
@override
|
|
void run() {
|
|
cache = ((int arg1, int arg2, int arg3, int arg4, int arg5, int arg6,
|
|
int arg7, int arg8) =>
|
|
randomInt + arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8).toJS;
|
|
}
|
|
}
|
|
|
|
class ConvertClosureFieldZeroBenchmark extends BenchmarkBase {
|
|
ConvertClosureFieldZeroBenchmark()
|
|
: super('FunctionToJs.Convert.ClosureField.0');
|
|
|
|
late int Function() closure;
|
|
final int randomInt = random.nextInt(10);
|
|
|
|
@override
|
|
void setup() {
|
|
closure = () => randomInt;
|
|
}
|
|
|
|
@override
|
|
void run() {
|
|
cache = closure.toJS;
|
|
}
|
|
}
|
|
|
|
class ConvertClosureFieldTwoBenchmark extends BenchmarkBase {
|
|
ConvertClosureFieldTwoBenchmark()
|
|
: super('FunctionToJs.Convert.ClosureField.2');
|
|
|
|
late int Function(int, int) closure;
|
|
final int randomInt = random.nextInt(10);
|
|
|
|
@override
|
|
void setup() {
|
|
closure = (int arg1, int arg2) => randomInt + arg1 + arg2;
|
|
}
|
|
|
|
@override
|
|
void run() {
|
|
cache = closure.toJS;
|
|
}
|
|
}
|
|
|
|
class ConvertClosureFieldEightBenchmark extends BenchmarkBase {
|
|
ConvertClosureFieldEightBenchmark()
|
|
: super('FunctionToJs.Convert.ClosureField.8');
|
|
|
|
late int Function(int, int, int, int, int, int, int, int) closure;
|
|
final int randomInt = random.nextInt(10);
|
|
|
|
@override
|
|
void setup() {
|
|
closure = (int arg1, int arg2, int arg3, int arg4, int arg5, int arg6,
|
|
int arg7, int arg8) =>
|
|
randomInt + arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8;
|
|
}
|
|
|
|
@override
|
|
void run() {
|
|
cache = closure.toJS;
|
|
}
|
|
}
|
|
|
|
class CallJSInstanceZeroBenchmark extends BenchmarkBase {
|
|
CallJSInstanceZeroBenchmark() : super('FunctionToJs.CallJS.Instance.0');
|
|
|
|
late JSExportedDartFunction jsFunction;
|
|
final int randomInt = random.nextInt(10);
|
|
|
|
@pragma('dart2js:never-inline')
|
|
int zero() => randomInt;
|
|
|
|
@override
|
|
void setup() {
|
|
jsFunction = zero.toJS;
|
|
}
|
|
|
|
@override
|
|
void run() {
|
|
final val = jsFunction.call();
|
|
if (val < 0 || val >= 10) throw 'Bad result: $val';
|
|
}
|
|
}
|
|
|
|
class CallJSInstanceTwoBenchmark extends BenchmarkBase {
|
|
CallJSInstanceTwoBenchmark() : super('FunctionToJs.CallJS.Instance.2');
|
|
|
|
late JSExportedDartFunction jsFunction;
|
|
final int randomInt = random.nextInt(10);
|
|
|
|
@pragma('dart2js:never-inline')
|
|
int two(int arg1, int arg2) => randomInt + arg1 + arg2;
|
|
|
|
@override
|
|
void setup() {
|
|
jsFunction = two.toJS;
|
|
}
|
|
|
|
@override
|
|
void run() {
|
|
final val = jsFunction.call(null, 1, 1);
|
|
if (val < 2 || val >= 12) throw 'Bad result: $val';
|
|
}
|
|
}
|
|
|
|
class CallJSInstanceEightBenchmark extends BenchmarkBase {
|
|
CallJSInstanceEightBenchmark() : super('FunctionToJs.CallJS.Instance.8');
|
|
|
|
late JSExportedDartFunction jsFunction;
|
|
final int randomInt = random.nextInt(10);
|
|
|
|
@pragma('dart2js:never-inline')
|
|
int eight(int arg1, int arg2, int arg3, int arg4, int arg5, int arg6,
|
|
int arg7, int arg8) =>
|
|
randomInt + arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8;
|
|
|
|
@override
|
|
void setup() {
|
|
jsFunction = eight.toJS;
|
|
}
|
|
|
|
@override
|
|
void run() {
|
|
final val = jsFunction.call(null, 1, 1, 1, 1, 1, 1, 1, 1);
|
|
if (val < 8 || val >= 18) throw 'Bad result: $val';
|
|
}
|
|
}
|
|
|
|
class CallJSStaticZeroBenchmark extends BenchmarkBase {
|
|
CallJSStaticZeroBenchmark() : super('FunctionToJs.CallJS.Static.0');
|
|
|
|
static final int randomInt = random.nextInt(10);
|
|
|
|
static int zero() => randomInt;
|
|
|
|
late JSExportedDartFunction jsFunction;
|
|
|
|
@override
|
|
void setup() {
|
|
jsFunction = zero.toJS;
|
|
}
|
|
|
|
@override
|
|
void run() {
|
|
final val = jsFunction.call();
|
|
if (val < 0 || val >= 10) throw 'Bad result: $val';
|
|
}
|
|
}
|
|
|
|
class CallJSStaticTwoBenchmark extends BenchmarkBase {
|
|
CallJSStaticTwoBenchmark() : super('FunctionToJs.CallJS.Static.2');
|
|
|
|
static final int randomInt = random.nextInt(10);
|
|
|
|
static int two(int arg1, int arg2) => randomInt + arg1 + arg2;
|
|
|
|
late JSExportedDartFunction jsFunction;
|
|
|
|
@override
|
|
void setup() {
|
|
jsFunction = two.toJS;
|
|
}
|
|
|
|
@override
|
|
void run() {
|
|
final val = jsFunction.call(null, 1, 1);
|
|
if (val < 2 || val >= 12) throw 'Bad result: $val';
|
|
}
|
|
}
|
|
|
|
class CallJSStaticEightBenchmark extends BenchmarkBase {
|
|
CallJSStaticEightBenchmark() : super('FunctionToJs.CallJS.Static.8');
|
|
|
|
static final int randomInt = random.nextInt(10);
|
|
|
|
static int eight(int arg1, int arg2, int arg3, int arg4, int arg5, int arg6,
|
|
int arg7, int arg8) =>
|
|
randomInt + arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8;
|
|
|
|
late JSExportedDartFunction jsFunction;
|
|
|
|
@override
|
|
void setup() {
|
|
jsFunction = eight.toJS;
|
|
}
|
|
|
|
@override
|
|
void run() {
|
|
final val = jsFunction.call(null, 1, 1, 1, 1, 1, 1, 1, 1);
|
|
if (val < 8 || val >= 18) throw 'Bad result: $val';
|
|
}
|
|
}
|
|
|
|
class CallJSClosureZeroBenchmark extends BenchmarkBase {
|
|
CallJSClosureZeroBenchmark() : super('FunctionToJs.CallJS.Closure.0');
|
|
|
|
late JSExportedDartFunction jsFunction;
|
|
final int randomInt = random.nextInt(10);
|
|
|
|
@override
|
|
void setup() {
|
|
jsFunction = (() => randomInt).toJS;
|
|
}
|
|
|
|
@override
|
|
void run() {
|
|
final val = jsFunction.call();
|
|
if (val < 0 || val >= 10) throw 'Bad result: $val';
|
|
}
|
|
}
|
|
|
|
class CallJSClosureTwoBenchmark extends BenchmarkBase {
|
|
CallJSClosureTwoBenchmark() : super('FunctionToJs.CallJS.Closure.2');
|
|
|
|
late JSExportedDartFunction jsFunction;
|
|
final int randomInt = random.nextInt(10);
|
|
|
|
@override
|
|
void setup() {
|
|
jsFunction = ((int arg1, int arg2) => randomInt + arg1 + arg2).toJS;
|
|
}
|
|
|
|
@override
|
|
void run() {
|
|
final val = jsFunction.call(null, 1, 1);
|
|
if (val < 2 || val >= 12) throw 'Bad result: $val';
|
|
}
|
|
}
|
|
|
|
class CallJSClosureEightBenchmark extends BenchmarkBase {
|
|
CallJSClosureEightBenchmark() : super('FunctionToJs.CallJS.Closure.8');
|
|
|
|
late JSExportedDartFunction jsFunction;
|
|
final int randomInt = random.nextInt(10);
|
|
|
|
@override
|
|
void setup() {
|
|
jsFunction = ((int arg1, int arg2, int arg3, int arg4, int arg5, int arg6,
|
|
int arg7, int arg8) =>
|
|
randomInt + arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8).toJS;
|
|
}
|
|
|
|
@override
|
|
void run() {
|
|
final val = jsFunction.call(null, 1, 1, 1, 1, 1, 1, 1, 1);
|
|
if (val < 8 || val >= 18) throw 'Bad result: $val';
|
|
}
|
|
}
|
|
|
|
class CallDartClosureZeroBenchmark extends BenchmarkBase {
|
|
CallDartClosureZeroBenchmark() : super('FunctionToJs.CallDart.Closure.0');
|
|
|
|
late int Function() closure;
|
|
final int randomInt = random.nextInt(10);
|
|
|
|
@override
|
|
void setup() {
|
|
closure = () => randomInt;
|
|
}
|
|
|
|
@override
|
|
void run() {
|
|
final val = closure();
|
|
if (val < 0 || val >= 10) throw 'Bad result: $val';
|
|
}
|
|
}
|
|
|
|
class CallDartClosureTwoBenchmark extends BenchmarkBase {
|
|
CallDartClosureTwoBenchmark() : super('FunctionToJs.CallDart.Closure.2');
|
|
|
|
late int Function(int, int) closure;
|
|
final int randomInt = random.nextInt(10);
|
|
|
|
@override
|
|
void setup() {
|
|
closure = (int arg1, int arg2) => randomInt + arg1 + arg2;
|
|
}
|
|
|
|
@override
|
|
void run() {
|
|
final val = closure(1, 1);
|
|
if (val < 2 || val >= 12) throw 'Bad result: $val';
|
|
}
|
|
}
|
|
|
|
class CallDartClosureEightBenchmark extends BenchmarkBase {
|
|
CallDartClosureEightBenchmark() : super('FunctionToJs.CallDart.Closure.8');
|
|
|
|
late int Function(int, int, int, int, int, int, int, int) closure;
|
|
final int randomInt = random.nextInt(10);
|
|
|
|
@override
|
|
void setup() {
|
|
closure = (int arg1, int arg2, int arg3, int arg4, int arg5, int arg6,
|
|
int arg7, int arg8) =>
|
|
randomInt + arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8;
|
|
}
|
|
|
|
@override
|
|
void run() {
|
|
final val = closure(1, 1, 1, 1, 1, 1, 1, 1);
|
|
if (val < 8 || val >= 18) throw 'Bad result: $val';
|
|
}
|
|
}
|
|
|
|
void main() {
|
|
final benchmarks = [
|
|
ConvertInstanceZeroBenchmark(),
|
|
ConvertInstanceTwoBenchmark(),
|
|
ConvertInstanceEightBenchmark(),
|
|
ConvertStaticZeroBenchmark(),
|
|
ConvertStaticTwoBenchmark(),
|
|
ConvertStaticEightBenchmark(),
|
|
ConvertClosureZeroBenchmark(),
|
|
ConvertClosureTwoBenchmark(),
|
|
ConvertClosureEightBenchmark(),
|
|
ConvertClosureFieldZeroBenchmark(),
|
|
ConvertClosureFieldTwoBenchmark(),
|
|
ConvertClosureFieldEightBenchmark(),
|
|
CallJSInstanceZeroBenchmark(),
|
|
CallJSInstanceTwoBenchmark(),
|
|
CallJSInstanceEightBenchmark(),
|
|
CallJSStaticZeroBenchmark(),
|
|
CallJSStaticTwoBenchmark(),
|
|
CallJSStaticEightBenchmark(),
|
|
CallJSClosureZeroBenchmark(),
|
|
CallJSClosureTwoBenchmark(),
|
|
CallJSClosureEightBenchmark(),
|
|
CallDartClosureZeroBenchmark(),
|
|
CallDartClosureTwoBenchmark(),
|
|
CallDartClosureEightBenchmark(),
|
|
];
|
|
// Warmup all benchmarks so that the first benchmark doesn't get overfitted by
|
|
// a JIT compiler.
|
|
for (final benchmark in benchmarks) {
|
|
benchmark.setup();
|
|
}
|
|
for (var i = 0; i < 10; i++) {
|
|
for (final benchmark in benchmarks) {
|
|
benchmark.run();
|
|
}
|
|
}
|
|
for (final benchmark in benchmarks) {
|
|
benchmark.report();
|
|
}
|
|
}
|