| // 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 |
| |
| import 'dart:typed_data'; |
| |
| import 'package:benchmark_harness/benchmark_harness.dart'; |
| |
| abstract class Key { |
| const factory Key(String value) = ValueKey<String>; |
| const Key.empty(); |
| } |
| |
| abstract class LocalKey extends Key { |
| const LocalKey() : super.empty(); |
| } |
| |
| class ValueKey<T> extends LocalKey { |
| const ValueKey(this.value); |
| final T value; |
| @override |
| bool operator ==(Object other) { |
| if (other.runtimeType != runtimeType) return false; |
| return other is ValueKey<T> && 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<W extends Widget> 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<Widget> _widgets() => [ |
| AWidget(), |
| BWidget(), |
| CWidget(), |
| DWidget(), |
| EWidget(), |
| FWidget(), |
| WWidget<AWidget>(), |
| WWidget<BWidget>(ref: const BWidget()), |
| WWidget<CWidget>(ref: CWidget()), |
| const WWidget<DWidget>(ref: DWidget()), |
| ]; |
| // Bulk up list to reduce loop overheads. |
| final List<Widget> 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<Widget> _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<Widget> 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(<bool>{}, <bool>{}); |
| } |
| |
| void main() { |
| pollute(); |
| |
| final benchmarks = [ |
| WidgetCanUpdateBenchmark(), |
| ValueKeyEqualBenchmark(), |
| ]; |
| |
| // Warm up all benchmarks before running any. |
| benchmarks.forEach((bm) => bm.run()); |
| |
| benchmarks.forEach((bm) => bm.report()); |
| } |