mirror of
https://github.com/dart-lang/sdk
synced 2024-07-01 07:14:29 +00:00
[benchmarks] Add Function.toJS benchmark
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>
This commit is contained in:
parent
06ec78851b
commit
7ec9514093
507
benchmarks/FunctionToJs/dart/FunctionToJs.dart
Normal file
507
benchmarks/FunctionToJs/dart/FunctionToJs.dart
Normal file
|
@ -0,0 +1,507 @@
|
|||
// 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();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user