// Copyright (c) 2020, 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. // // Benchmark for runtimeType patterns as used in Flutter. // ignore_for_file: prefer_const_constructors // ignore_for_file: avoid_function_literals_in_foreach_calls // @dart=2.9 import 'dart:typed_data'; import 'package:benchmark_harness/benchmark_harness.dart'; abstract class Key { const factory Key(String value) = ValueKey; const Key.empty(); } abstract class LocalKey extends Key { const LocalKey() : super.empty(); } class ValueKey extends LocalKey { const ValueKey(this.value); final T value; @override bool operator ==(Object other) { if (other.runtimeType != runtimeType) return false; return other is ValueKey && other.value == value; } @override int get hashCode => value.hashCode; } abstract class Widget { const Widget({this.key}); final Key key; @pragma('dart2js:noInline') static bool canUpdate(Widget oldWidget, Widget newWidget) { return oldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key; } } class AWidget extends Widget { const AWidget({Key key}) : super(key: key); } class BWidget extends Widget { const BWidget({Key key}) : super(key: key); } class CWidget extends Widget { const CWidget({Key key}) : super(key: key); } class DWidget extends Widget { const DWidget({Key key}) : super(key: key); } class EWidget extends Widget { const EWidget({Key key}) : super(key: key); } class FWidget extends Widget { const FWidget({Key key}) : super(key: key); } class WWidget extends Widget { final W /*?*/ ref; const WWidget({this.ref, Key key}) : super(key: key); } class WidgetCanUpdateBenchmark extends BenchmarkBase { WidgetCanUpdateBenchmark() : super('RuntimeType.Widget.canUpdate.byType'); // All widgets have different types. static List _widgets() => [ AWidget(), BWidget(), CWidget(), DWidget(), EWidget(), FWidget(), WWidget(), WWidget(ref: const BWidget()), WWidget(ref: CWidget()), const WWidget(ref: DWidget()), ]; // Bulk up list to reduce loop overheads. final List widgets = _widgets() + _widgets() + _widgets(); @override void exercise() => run(); @override void run() { for (var w1 in widgets) { for (var w2 in widgets) { if (Widget.canUpdate(w1, w2) != Widget.canUpdate(w2, w1)) { throw 'Hmm $w1 $w2'; } } } } // Normalize by number of calls to [Widgets.canUpdate]. @override double measure() => super.measure() / (widgets.length * widgets.length * 2); } class ValueKeyEqualBenchmark extends BenchmarkBase { ValueKeyEqualBenchmark() : super('RuntimeType.Widget.canUpdate.byKey'); // All widgets the same class but distinguished on keys. static List _widgets() => [ AWidget(), AWidget(key: ValueKey(1)), AWidget(key: ValueKey(1)), AWidget(key: ValueKey(2)), AWidget(key: ValueKey(2)), AWidget(key: ValueKey(3)), AWidget(key: ValueKey('one')), AWidget(key: ValueKey('two')), AWidget(key: ValueKey('three')), AWidget(key: ValueKey(Duration(seconds: 5))), ]; // Bulk up list to reduce loop overheads. final List widgets = _widgets() + _widgets() + _widgets(); @override void exercise() => run(); @override void run() { for (var w1 in widgets) { for (var w2 in widgets) { if (Widget.canUpdate(w1, w2) != Widget.canUpdate(w2, w1)) { throw 'Hmm $w1 $w2'; } } } } // Normalize by number of calls to [Widgets.canUpdate]. @override double measure() => super.measure() / (widgets.length * widgets.length * 2); } void pollute() { // Various bits of code to make environment less unrealistic. void check(dynamic a, dynamic b) { if (a.runtimeType != b.runtimeType) throw 'mismatch $a $b'; } check(Uint8List(1), Uint8List(2)); // dart2js needs native interceptors. check(Int16List(1), Int16List(2)); check([], []); check({}, {}); } void main() { pollute(); final benchmarks = [ WidgetCanUpdateBenchmark(), ValueKeyEqualBenchmark(), ]; // Warm up all benchmarks before running any. benchmarks.forEach((bm) => bm.run()); benchmarks.forEach((bm) => bm.report()); }