6f076f90e1
Fix violations of lints that I expect have no influence on the performance characteristics of this benchmark: * annotate_overrides * directives_ordering Change-Id: I49f33347aea5cfdcbac5c9195a18427bdb7c667e Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/433266 Commit-Queue: Nicholas Shahan <nshahan@google.com> Reviewed-by: Stephen Adams <sra@google.com>
550 lines
12 KiB
Dart
550 lines
12 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.
|
|
|
|
import 'dart:js_interop';
|
|
|
|
import 'package:benchmark_harness/benchmark_harness.dart';
|
|
|
|
// Static js-interop benchmarks.
|
|
//
|
|
// Type checks:
|
|
//
|
|
// JSInterop.as.Foo - x as Foo
|
|
// JSInterop.as.T.Foo - x as T, where T is Foo
|
|
//
|
|
// Type tests:
|
|
//
|
|
// JSInterop.isA.Foo - x.isA<Foo>()
|
|
//
|
|
// Calls.
|
|
//
|
|
// JSInterop.call.moveFoo -
|
|
// Repeatedly copy the same Foo (some kind of JSObject) to JavaScript and
|
|
// back.
|
|
//
|
|
// JSInterop.call.{inline,hoisted,implicit}3ArgsSSS
|
|
// Passes three String arguments, using different positioning of `.toJS`
|
|
// conversions.
|
|
//
|
|
// JSInterop.call.{inline,hoisted,implicit}3ArgsIII
|
|
// Similar to JSInterop.call.*ArgsSSS but passing integer Numbers instead.
|
|
//
|
|
// JSInterop.call.inline7Args
|
|
// Compare with JSInterop.call.inline3ArgsSSS to assess the cost of more
|
|
// arguments.
|
|
|
|
/// Aliases for `Object.assign` with different types.
|
|
///
|
|
/// `Object.assign` with one argument returns the argument. It is a fast
|
|
/// JavaScript method that returns a JSObject.
|
|
@JS('Object')
|
|
extension type ObjectMethods(JSObject _) implements JSObject {
|
|
@JS('assign')
|
|
external static JSObject moveJSObject(JSObject o);
|
|
|
|
@JS('assign')
|
|
external static JSArray moveJSArray(JSArray o);
|
|
|
|
@JS('assign')
|
|
external static JSUint8Array moveJSUint8Array(JSUint8Array o);
|
|
}
|
|
|
|
// Alias for `.toString()` with different types.
|
|
//
|
|
// `x.toString()`, when called on a JSString receiver, calls
|
|
// `String.prototype.toString`, which is fast, since it returns the
|
|
// receiver. Additional arguments are ignored.
|
|
extension ToStringMethod on JSAny {
|
|
@JS('toString')
|
|
external JSString toString1(JSAny? a);
|
|
|
|
@JS('toString')
|
|
external JSString toString3SSS(JSString a, JSString b, JSString c);
|
|
|
|
@JS('toString')
|
|
external JSString toString3NNN(JSNumber a, JSNumber b, JSNumber c);
|
|
|
|
// This version uses Dart primitive types which implies an implicit
|
|
// conversion.
|
|
@JS('toString')
|
|
external String toString3ConvertedSSS(String a, String b, String c);
|
|
|
|
// This version uses Dart primitive types which implies an implicit
|
|
// conversion.
|
|
@JS('toString')
|
|
external String toString3ConvertedIII(int a, int b, int c);
|
|
|
|
@JS('toString')
|
|
external JSString toString7(
|
|
JSAny? a1,
|
|
JSAny? a2,
|
|
JSAny? a3,
|
|
JSAny? a4,
|
|
JSAny? a5,
|
|
JSAny? a6,
|
|
JSAny? a7,
|
|
);
|
|
}
|
|
|
|
extension type Date._(JSObject _) implements JSObject {
|
|
external Date.forTimestamp(int _);
|
|
external static int now();
|
|
}
|
|
|
|
const int N = 1000;
|
|
|
|
class Base extends BenchmarkBase {
|
|
Base(super.name);
|
|
}
|
|
|
|
List<Object?> allJSObjects() {
|
|
return List<Object?>.generate(N, (i) => createObject(), growable: false);
|
|
}
|
|
|
|
List<Object?> allJSAnys() {
|
|
return allJSObjects()
|
|
..[1] = 123.toJS
|
|
..[2] = 'x'.toJS;
|
|
}
|
|
|
|
List<Object?> allJSAnyQs() {
|
|
return allJSObjects()
|
|
..[0] = null
|
|
..[1] = 123.toJS
|
|
..[2] = 'x'.toJS;
|
|
}
|
|
|
|
final List<Object?> allJSNumbers = List<Object?>.generate(
|
|
N,
|
|
(i) => i.toJS,
|
|
growable: false,
|
|
);
|
|
|
|
final List<Object?> allJSStrings = List<Object?>.generate(
|
|
N,
|
|
(i) => '$i'.toJS,
|
|
growable: false,
|
|
);
|
|
|
|
final List<Object?> allJSUint8Array = List<Object?>.generate(
|
|
N,
|
|
(_) => JSUint8Array.withLength(2),
|
|
growable: false,
|
|
);
|
|
|
|
abstract class CastsBase extends Base {
|
|
final List<Object?> data;
|
|
CastsBase(String name, {required this.data}) : super('JSInterop.as.$name');
|
|
}
|
|
|
|
class Casts1 extends CastsBase {
|
|
Casts1() : super('JSObject', data: allJSObjects());
|
|
|
|
@override
|
|
void run() {
|
|
for (final o in data) {
|
|
sink = o as JSObject;
|
|
}
|
|
}
|
|
}
|
|
|
|
class Casts2 extends CastsBase {
|
|
Casts2() : super('JSAny', data: allJSAnys());
|
|
|
|
@override
|
|
void run() {
|
|
for (final o in data) {
|
|
sink = o as JSAny;
|
|
}
|
|
}
|
|
}
|
|
|
|
class Casts3 extends CastsBase {
|
|
Casts3() : super('JSAnyQ', data: allJSAnyQs());
|
|
|
|
@override
|
|
void run() {
|
|
for (final o in data) {
|
|
sink = o as JSAny?;
|
|
}
|
|
}
|
|
}
|
|
|
|
class Casts4 extends CastsBase {
|
|
Casts4() : super('JSNumber', data: allJSNumbers);
|
|
|
|
@override
|
|
void run() {
|
|
for (final o in data) {
|
|
sink = o as JSNumber;
|
|
}
|
|
}
|
|
}
|
|
|
|
class Casts5 extends CastsBase {
|
|
Casts5() : super('JSString', data: allJSStrings);
|
|
|
|
@override
|
|
void run() {
|
|
for (final o in data) {
|
|
sink = o as JSString;
|
|
}
|
|
}
|
|
}
|
|
|
|
class Casts6 extends CastsBase {
|
|
Casts6() : super('JSUint8Array', data: allJSUint8Array);
|
|
|
|
@override
|
|
void run() {
|
|
for (final o in data) {
|
|
sink = o as JSUint8Array;
|
|
}
|
|
}
|
|
}
|
|
|
|
class CastsT<T> extends CastsBase {
|
|
CastsT(String name, {required super.data}) : super('T.$name');
|
|
|
|
@override
|
|
void run() {
|
|
for (final o in data) {
|
|
sink = o as T;
|
|
}
|
|
}
|
|
}
|
|
|
|
class Calls1SSS extends Base {
|
|
Calls1SSS() : super('JSInterop.calls.inline3ArgsSSS');
|
|
|
|
@override
|
|
void run() {
|
|
String s = 'hello';
|
|
for (int i = 0; i < N; i++) {
|
|
// Pass in arguments with '.toJS' conversion.
|
|
// There is potential for a compiler to hoist the conversions.
|
|
// Implicit conversion of result of `toString3` to JSString.
|
|
// Explicit `.toDart` conversion of result JSString to Dart String.
|
|
sink = s = s.toJS.toString3SSS('a'.toJS, 'b'.toJS, 'c'.toJS).toDart;
|
|
}
|
|
}
|
|
}
|
|
|
|
class Calls1NNN extends Base {
|
|
Calls1NNN() : super('JSInterop.calls.inline3ArgsIII');
|
|
|
|
@override
|
|
void run() {
|
|
String s = 'hello';
|
|
for (int i = 0; i < N; i++) {
|
|
// Pass in arguments with '.toJS' conversion.
|
|
// There is potential for a compiler to hoist the conversions.
|
|
// Implicit conversion of result of `toString3` to JSString.
|
|
// Explicit `.toDart` conversion of result JSString to Dart String.
|
|
sink = s = s.toJS.toString3NNN(1.toJS, 2.toJS, 3.toJS).toDart;
|
|
}
|
|
}
|
|
}
|
|
|
|
class Calls2SSS extends Base {
|
|
Calls2SSS() : super('JSInterop.calls.hoisted3ArgsSSS');
|
|
|
|
static final _a = 'a'.toJS;
|
|
static final _b = 'b'.toJS;
|
|
static final _c = 'c'.toJS;
|
|
|
|
@override
|
|
void run() {
|
|
String s = 'hello';
|
|
for (int i = 0; i < N; i++) {
|
|
// Manually hoisted argument conversions.
|
|
// Implicit conversion of result of `toString3` to JSString.
|
|
// Explicit `.toDart` conversion of result JSString to Dart String.
|
|
sink = s = s.toJS.toString3SSS(_a, _b, _c).toDart;
|
|
}
|
|
}
|
|
}
|
|
|
|
class Calls2NNN extends Base {
|
|
Calls2NNN() : super('JSInterop.calls.hoisted3ArgsIII');
|
|
|
|
static final _a = 1.toJS;
|
|
static final _b = 2.toJS;
|
|
static final _c = 3.toJS;
|
|
|
|
@override
|
|
void run() {
|
|
String s = 'hello';
|
|
for (int i = 0; i < N; i++) {
|
|
// Manually hoisted argument conversions.
|
|
// Implicit conversion of result of `toString3` to JSString.
|
|
// Explicit `.toDart` conversion of result JSString to Dart String.
|
|
sink = s = s.toJS.toString3NNN(_a, _b, _c).toDart;
|
|
}
|
|
}
|
|
}
|
|
|
|
class Calls3SSS extends Base {
|
|
Calls3SSS() : super('JSInterop.calls.implicit3ArgsSSS');
|
|
|
|
@override
|
|
void run() {
|
|
String s = 'hello';
|
|
for (int i = 0; i < N; i++) {
|
|
// Implicit conversion of arguments and result as they have a primitive
|
|
// type.
|
|
sink = s = s.toJS.toString3ConvertedSSS('a', 'b', 'c');
|
|
}
|
|
}
|
|
}
|
|
|
|
class Calls3NNN extends Base {
|
|
Calls3NNN() : super('JSInterop.calls.implicit3ArgsIII');
|
|
|
|
@override
|
|
void run() {
|
|
String s = 'hello';
|
|
for (int i = 0; i < N; i++) {
|
|
// Implicit conversion of arguments and result as they have a primitive
|
|
// type.
|
|
sink = s = s.toJS.toString3ConvertedIII(1, 2, 3);
|
|
}
|
|
}
|
|
}
|
|
|
|
class Calls4 extends Base {
|
|
Calls4() : super('JSInterop.calls.inline7Args');
|
|
|
|
@override
|
|
void run() {
|
|
String s = 'hello';
|
|
for (int i = 0; i < N; i++) {
|
|
sink = s = s.toJS
|
|
.toString7(
|
|
'1'.toJS,
|
|
'2'.toJS,
|
|
'3'.toJS,
|
|
'4'.toJS,
|
|
'5'.toJS,
|
|
'6'.toJS,
|
|
'7'.toJS,
|
|
)
|
|
.toDart;
|
|
}
|
|
}
|
|
}
|
|
|
|
class Calls5 extends Base {
|
|
Calls5() : super('JSInterop.calls.moveJSObject');
|
|
|
|
static final _o = JSObject();
|
|
|
|
@override
|
|
void run() {
|
|
JSObject o = _o;
|
|
for (int i = 0; i < N; i++) {
|
|
// Implicit conversion of result to JSObject.
|
|
sink = o = ObjectMethods.moveJSObject(o);
|
|
}
|
|
}
|
|
}
|
|
|
|
class Calls6 extends Base {
|
|
Calls6() : super('JSInterop.calls.moveJSArray');
|
|
|
|
static final _o = JSArray.withLength(2);
|
|
|
|
@override
|
|
void run() {
|
|
JSArray o = _o;
|
|
for (int i = 0; i < N; i++) {
|
|
// Implicit conversion of result to JSArray.
|
|
sink = o = ObjectMethods.moveJSArray(o);
|
|
}
|
|
}
|
|
}
|
|
|
|
class Calls7 extends Base {
|
|
Calls7() : super('JSInterop.calls.moveJSUint8Array');
|
|
|
|
static final _o = JSUint8Array.withLength(2);
|
|
|
|
@override
|
|
void run() {
|
|
JSUint8Array o = _o;
|
|
for (int i = 0; i < N; i++) {
|
|
// Implicit conversion of result to JSUint8Array.
|
|
sink = o = ObjectMethods.moveJSUint8Array(o);
|
|
}
|
|
}
|
|
}
|
|
|
|
List<JSAny?> _objects() => List<JSAny?>.generate(
|
|
N,
|
|
(i) => switch (i % 7) {
|
|
0 => JSObject(),
|
|
1 => i.toJS,
|
|
2 => JSArray(),
|
|
3 => '$i'.toJS,
|
|
4 => Date.forTimestamp(Date.now()),
|
|
5 => null,
|
|
6 => JSUint8Array.withLength(2),
|
|
int() => throw StateError('unexpected: $i'),
|
|
},
|
|
growable: false,
|
|
);
|
|
|
|
abstract class IsABase extends Base {
|
|
IsABase(String name) : super('JSInterop.isA.$name');
|
|
|
|
final List<JSAny?> objects = _objects();
|
|
}
|
|
|
|
class IsA1 extends IsABase {
|
|
IsA1() : super('JSObject');
|
|
|
|
@override
|
|
void run() {
|
|
for (final o in objects) {
|
|
sink = o.isA<JSObject>();
|
|
}
|
|
}
|
|
}
|
|
|
|
class IsA2 extends IsABase {
|
|
IsA2() : super('JSAny');
|
|
|
|
@override
|
|
void run() {
|
|
for (final o in objects) {
|
|
sink = o.isA<JSAny>();
|
|
}
|
|
}
|
|
}
|
|
|
|
class IsA3 extends IsABase {
|
|
IsA3() : super('JSString');
|
|
|
|
@override
|
|
void run() {
|
|
for (final o in objects) {
|
|
sink = o.isA<JSString>();
|
|
}
|
|
}
|
|
}
|
|
|
|
class IsA4 extends IsABase {
|
|
IsA4() : super('JSArray');
|
|
|
|
@override
|
|
void run() {
|
|
for (final o in objects) {
|
|
sink = o.isA<JSArray>();
|
|
}
|
|
}
|
|
}
|
|
|
|
class IsA5 extends IsABase {
|
|
IsA5() : super('Date');
|
|
|
|
@override
|
|
void run() {
|
|
for (final o in objects) {
|
|
sink = o.isA<Date>();
|
|
}
|
|
}
|
|
}
|
|
|
|
class IsA6 extends IsABase {
|
|
IsA6() : super('DateQ');
|
|
|
|
@override
|
|
void run() {
|
|
for (final o in objects) {
|
|
sink = o.isA<Date?>();
|
|
}
|
|
}
|
|
}
|
|
|
|
class IsA7 extends IsABase {
|
|
IsA7() : super('JSUint8Array');
|
|
|
|
@override
|
|
void run() {
|
|
for (final o in objects) {
|
|
sink = o.isA<JSUint8Array>();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Returns a new JSObject, but with a type that is hard for an optimizing
|
|
/// compiler to know exactly.
|
|
Object? createObject() {
|
|
if (inscrutableTrue) return JSObject();
|
|
if (inscrutableTrue) return 123.toJS;
|
|
if (inscrutableTrue) return 'x'.toJS;
|
|
if (inscrutableTrue) return true.toJS;
|
|
if (inscrutableTrue) return StringBuffer();
|
|
return null;
|
|
}
|
|
|
|
bool get inscrutableTrue => DateTime.now().millisecondsSinceEpoch > 42;
|
|
|
|
Object? sink;
|
|
|
|
void main() {
|
|
print(Date.forTimestamp(Date.now()));
|
|
|
|
final benchmarks = [
|
|
IsA1(),
|
|
IsA2(),
|
|
IsA3(),
|
|
IsA4(),
|
|
IsA5(),
|
|
IsA6(),
|
|
IsA7(),
|
|
|
|
Calls1SSS(),
|
|
Calls1NNN(),
|
|
Calls2SSS(),
|
|
Calls2NNN(),
|
|
Calls3SSS(),
|
|
Calls3NNN(),
|
|
Calls4(),
|
|
Calls5(),
|
|
Calls6(),
|
|
Calls7(),
|
|
|
|
Casts1(),
|
|
Casts2(),
|
|
Casts3(),
|
|
Casts4(),
|
|
Casts5(),
|
|
Casts6(),
|
|
|
|
CastsT<JSObject>('JSObject', data: allJSObjects()),
|
|
CastsT<JSAny>('JSAny', data: allJSAnys()),
|
|
CastsT<JSAny?>('JSAnyQ', data: allJSAnyQs()),
|
|
CastsT<JSNumber>('JSNumber', data: allJSNumbers),
|
|
CastsT<JSString>('JSString', data: allJSStrings),
|
|
CastsT<JSUint8Array>('JSUint8Array', data: allJSUint8Array),
|
|
];
|
|
|
|
// Warmup all benchmarks to ensure JIT compilers see full polymorphism.
|
|
for (var benchmark in benchmarks) {
|
|
benchmark.setup();
|
|
benchmark.run();
|
|
benchmark.run();
|
|
if (sink == null) throw StateError('unexpected');
|
|
}
|
|
|
|
for (var benchmark in benchmarks) {
|
|
benchmark.run();
|
|
}
|
|
|
|
for (var benchmark in benchmarks) {
|
|
benchmark.report();
|
|
}
|
|
}
|