1
0
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:
Srujan Gaddam 2024-06-14 19:42:10 +00:00 committed by Commit Queue
parent 06ec78851b
commit 7ec9514093

View 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();
}
}