|  | // Copyright (c) 2022, 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:typed_data'; | 
|  |  | 
|  | import 'package:benchmark_harness/benchmark_harness.dart'; | 
|  |  | 
|  | // Benchmark for polymorphic typed_data List access. | 
|  | // | 
|  | // The set of benchmarks compares the cost of reading typed data lists, mostly | 
|  | // different kinds of [Uint8List] - plain [Uint8List]s, [Uint8List]s that are | 
|  | // views of another [Uint8List], and unmodifiable views of these. | 
|  | // | 
|  | // The benchmarks do not try to use external [Uint8List]s, since this is does | 
|  | // not easily translate to the web. | 
|  | // | 
|  | // Each benchmark sums the contents of a `List<int>`. The benchmarks report the | 
|  | // per-element time. Benchmarks vary by the degree of polymorphism, the kinds of | 
|  | // typed_data List used, and the length of the List. | 
|  | // | 
|  | // The goal of an implementation would be to make as many of the benchmarks as | 
|  | // possible report a similar time to `TypedDataPoly.mono.array.N`. | 
|  |  | 
|  | /// A convenience for initializing the lists. | 
|  | extension on List<int> { | 
|  | void setToOnes() { | 
|  | for (int i = 0; i < length; i++) { | 
|  | this[i] = 1; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Results pass through this global variable to ensure the result is used and | 
|  | /// so defeat optimizations based on side-effect free computations that have an | 
|  | /// unused result. | 
|  | int g = 0; | 
|  |  | 
|  | class Base extends BenchmarkBase { | 
|  | final int n; | 
|  | Base(String name, this.n) : super('$name.$n'); | 
|  |  | 
|  | @override | 
|  | void report() { | 
|  | final double microsecondsPerExercise = measure(); | 
|  | // Report time in nanoseconds per element. [exercise] runs [run] 10 times, | 
|  | // and each [run] does 10 summations. | 
|  | final double score = microsecondsPerExercise * 1000.0 / n / 10.0 / 10.0; | 
|  | print('$name(RunTime): $score ns.'); | 
|  | } | 
|  |  | 
|  | void check() { | 
|  | if (g != n * 10) throw StateError('n = $n, g = $g'); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Benchmark where the `sum` method is called only with the basic [Uint8List] | 
|  | /// implementation. This represents the best-case performance. | 
|  | class Monomorphic extends Base { | 
|  | final Uint8List data1; | 
|  | Monomorphic(int n) | 
|  | : data1 = Uint8List(n)..setToOnes(), | 
|  | super('TypedDataPoly.mono.array', n); | 
|  |  | 
|  | /// An identical [sum] method appears in each benchmark so the compiler | 
|  | /// can specialize the method according to different sets of input | 
|  | /// implementation types. | 
|  | @pragma('vm:never-inline') | 
|  | @pragma('wasm:never-inline') | 
|  | @pragma('dart2js:never-inline') | 
|  | static int sum(List<int> list) { | 
|  | var s = 0; | 
|  | for (int i = 0; i < list.length; i++) { | 
|  | s += list[i]; | 
|  | } | 
|  | return s; | 
|  | } | 
|  |  | 
|  | /// Each [run] method calls [sum] ten times. | 
|  | @override | 
|  | void run() { | 
|  | g = 0; | 
|  | g += sum(data1); | 
|  | g += sum(data1); | 
|  | g += sum(data1); | 
|  | g += sum(data1); | 
|  | g += sum(data1); | 
|  | g += sum(data1); | 
|  | g += sum(data1); | 
|  | g += sum(data1); | 
|  | g += sum(data1); | 
|  | g += sum(data1); | 
|  | check(); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// [Baseline] is modelled after [Monomorphic], but does almost no work in | 
|  | /// `sum`.  This measures the cost of benchmark code outside of `sum`. | 
|  | class Baseline extends Base { | 
|  | final Uint8List data1; | 
|  | Baseline(int n) | 
|  | : data1 = Uint8List(n)..setToOnes(), | 
|  | super('TypedDataPoly.baseline', n); | 
|  |  | 
|  | @pragma('vm:never-inline') | 
|  | @pragma('wasm:never-inline') | 
|  | @pragma('dart2js:never-inline') | 
|  | static int sum(List<int> list) { | 
|  | return list.length; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void run() { | 
|  | g = 0; | 
|  | g += sum(data1); | 
|  | g += sum(data1); | 
|  | g += sum(data1); | 
|  | g += sum(data1); | 
|  | g += sum(data1); | 
|  | g += sum(data1); | 
|  | g += sum(data1); | 
|  | g += sum(data1); | 
|  | g += sum(data1); | 
|  | g += sum(data1); | 
|  | check(); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// [Polymorphic1] calls `sum` with a flat allocated `Uint8List`(`A`) and a view | 
|  | /// of part of a longer list (`V`). | 
|  | class Polymorphic1 extends Base { | 
|  | final List<int> data1; | 
|  | final List<int> data2; | 
|  | Polymorphic1._(int n, String variant, this.data1, this.data2) | 
|  | : super('TypedDataPoly.A_V.$variant', n); | 
|  |  | 
|  | factory Polymorphic1(int n, String variant) { | 
|  | final data1 = Uint8List(n)..setToOnes(); | 
|  | final data2 = Uint8List.sublistView(Uint8List(n + 1)..setToOnes(), 1); | 
|  | if (variant == 'array') return Polymorphic1._(n, variant, data1, data1); | 
|  | if (variant == 'view') return Polymorphic1._(n, variant, data2, data2); | 
|  | throw UnimplementedError('No variant "$variant"'); | 
|  | } | 
|  |  | 
|  | @pragma('vm:never-inline') | 
|  | @pragma('wasm:never-inline') | 
|  | @pragma('dart2js:never-inline') | 
|  | static int sum(List<int> list) { | 
|  | var s = 0; | 
|  | for (int i = 0; i < list.length; i++) { | 
|  | s += list[i]; | 
|  | } | 
|  | return s; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void run() { | 
|  | g = 0; | 
|  | g += sum(data1); | 
|  | g += sum(data2); | 
|  | g += sum(data1); | 
|  | g += sum(data2); | 
|  | g += sum(data1); | 
|  | g += sum(data2); | 
|  | g += sum(data1); | 
|  | g += sum(data2); | 
|  | g += sum(data1); | 
|  | g += sum(data2); | 
|  | check(); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// [Polymorphic2] calls `sum` with a flat allocated [Uint8List] and an | 
|  | /// unmodifiable view of the same list. This mildly polymorphic, so | 
|  | /// there is the possibility that `sum` runs slower than [Monomorphic.sum]. | 
|  | /// | 
|  | /// The workload can be varied: | 
|  | /// | 
|  | ///  - `view` measures the cost of accessing the unmodifiable view. | 
|  | /// | 
|  | ///  - `array` measures the cost of accessing a simple [Uint8List] using the | 
|  | ///     same code that can also access an unmodifiable view of a [Uint8List]. | 
|  | class Polymorphic2 extends Base { | 
|  | final List<int> data1; | 
|  | final List<int> data2; | 
|  | Polymorphic2._(int n, String variant, this.data1, this.data2) | 
|  | : super('TypedDataPoly.A_UV.$variant', n); | 
|  |  | 
|  | factory Polymorphic2(int n, String variant) { | 
|  | final data1 = Uint8List(n)..setToOnes(); | 
|  | final data2 = data1.asUnmodifiableView(); | 
|  | if (variant == 'array') return Polymorphic2._(n, variant, data1, data1); | 
|  | if (variant == 'view') return Polymorphic2._(n, variant, data2, data2); | 
|  | throw UnimplementedError('No variant "$variant"'); | 
|  | } | 
|  |  | 
|  | @pragma('vm:never-inline') | 
|  | @pragma('wasm:never-inline') | 
|  | @pragma('dart2js:never-inline') | 
|  | static int sum(List<int> list) { | 
|  | var s = 0; | 
|  | for (int i = 0; i < list.length; i++) { | 
|  | s += list[i]; | 
|  | } | 
|  | return s; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void run() { | 
|  | g = 0; | 
|  | g += sum(data1); | 
|  | g += sum(data2); | 
|  | g += sum(data1); | 
|  | g += sum(data2); | 
|  | g += sum(data1); | 
|  | g += sum(data2); | 
|  | g += sum(data1); | 
|  | g += sum(data2); | 
|  | g += sum(data1); | 
|  | g += sum(data2); | 
|  | check(); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// [Polymorphic3] is similar to [Polymorphic2], but the 'other' list is an | 
|  | /// unmodifiable view of a modifiable view. | 
|  | class Polymorphic3 extends Base { | 
|  | final List<int> data1; | 
|  | final List<int> data2; | 
|  | Polymorphic3._(int n, String variant, this.data1, this.data2) | 
|  | : super('TypedDataPoly.A_VUV.$variant', n); | 
|  | factory Polymorphic3(int n, String variant) { | 
|  | final data1 = Uint8List(n)..setToOnes(); | 
|  | final view1 = Uint8List.sublistView(Uint8List(n + 1)..setToOnes(), 1); | 
|  | final data2 = view1.asUnmodifiableView(); | 
|  | if (variant == 'array') return Polymorphic3._(n, variant, data1, data1); | 
|  | if (variant == 'view') return Polymorphic3._(n, variant, data2, data2); | 
|  | throw UnimplementedError('No variant "$variant"'); | 
|  | } | 
|  |  | 
|  | @pragma('vm:never-inline') | 
|  | @pragma('wasm:never-inline') | 
|  | @pragma('dart2js:never-inline') | 
|  | static int sum(List<int> list) { | 
|  | var s = 0; | 
|  | for (int i = 0; i < list.length; i++) { | 
|  | s += list[i]; | 
|  | } | 
|  | return s; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void run() { | 
|  | g = 0; | 
|  | g += sum(data1); | 
|  | g += sum(data2); | 
|  | g += sum(data1); | 
|  | g += sum(data2); | 
|  | g += sum(data1); | 
|  | g += sum(data2); | 
|  | g += sum(data1); | 
|  | g += sum(data2); | 
|  | g += sum(data1); | 
|  | g += sum(data2); | 
|  | check(); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// [Polymorphic4] stacks unmodifiable views five levels deep. | 
|  | class Polymorphic4 extends Base { | 
|  | final List<int> data1; | 
|  | final List<int> data2; | 
|  | Polymorphic4._(int n, String variant, this.data1, this.data2) | 
|  | : super('TypedDataPoly.A_UVx5.$variant', n); | 
|  |  | 
|  | factory Polymorphic4(int n, String variant) { | 
|  | final data1 = Uint8List(n)..setToOnes(); | 
|  | var data2 = data1.asUnmodifiableView(); | 
|  | data2 = data2.asUnmodifiableView(); | 
|  | data2 = data2.asUnmodifiableView(); | 
|  | data2 = data2.asUnmodifiableView(); | 
|  | data2 = data2.asUnmodifiableView(); | 
|  | if (variant == 'array') return Polymorphic4._(n, variant, data1, data1); | 
|  | if (variant == 'view') return Polymorphic4._(n, variant, data2, data2); | 
|  | throw UnimplementedError('No variant "$variant"'); | 
|  | } | 
|  |  | 
|  | @pragma('vm:never-inline') | 
|  | @pragma('wasm:never-inline') | 
|  | @pragma('dart2js:never-inline') | 
|  | static int sum(List<int> list) { | 
|  | var s = 0; | 
|  | for (int i = 0; i < list.length; i++) { | 
|  | s += list[i]; | 
|  | } | 
|  | return s; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void run() { | 
|  | g = 0; | 
|  | g += sum(data1); | 
|  | g += sum(data2); | 
|  | g += sum(data1); | 
|  | g += sum(data2); | 
|  | g += sum(data1); | 
|  | g += sum(data2); | 
|  | g += sum(data1); | 
|  | g += sum(data2); | 
|  | g += sum(data1); | 
|  | g += sum(data2); | 
|  | check(); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Massively polymorphic version with 10 kinds of typed data lists and | 
|  | /// unmodifiable views. | 
|  | class Polymorphic5 extends Base { | 
|  | final List<int> data1; | 
|  | final List<int> data2; | 
|  | final List<int> data3; | 
|  | final List<int> data4; | 
|  | final List<int> data5; | 
|  | final List<int> data6; | 
|  | final List<int> data7; | 
|  | final List<int> data8; | 
|  | final List<int> data9; | 
|  | final List<int> data10; | 
|  | Polymorphic5._( | 
|  | int n, | 
|  | String variant, | 
|  | this.data1, | 
|  | this.data2, | 
|  | this.data3, | 
|  | this.data4, | 
|  | this.data5, | 
|  | this.data6, | 
|  | this.data7, | 
|  | this.data8, | 
|  | this.data9, | 
|  | this.data10, | 
|  | ) : super('TypedDataPoly.mega.$variant', n); | 
|  |  | 
|  | factory Polymorphic5(int n, String variant) { | 
|  | final data1 = Uint8List(n)..setToOnes(); | 
|  | final data2 = Uint16List(n)..setToOnes(); | 
|  | final data3 = Uint32List(n)..setToOnes(); | 
|  | final data4 = Int8List(n)..setToOnes(); | 
|  | final data5 = Int16List(n)..setToOnes(); | 
|  |  | 
|  | final data6 = data1.asUnmodifiableView(); | 
|  | final data7 = data2.asUnmodifiableView(); | 
|  | final data8 = data3.asUnmodifiableView(); | 
|  | final data9 = data4.asUnmodifiableView(); | 
|  | final data10 = data5.asUnmodifiableView(); | 
|  |  | 
|  | if (variant == 'array') { | 
|  | return Polymorphic5._( | 
|  | n, | 
|  | variant, | 
|  | data1, | 
|  | data1, | 
|  | data1, | 
|  | data1, | 
|  | data1, | 
|  | data1, | 
|  | data1, | 
|  | data1, | 
|  | data1, | 
|  | data1, | 
|  | ); | 
|  | } | 
|  | if (variant == 'mixed') { | 
|  | return Polymorphic5._( | 
|  | n, | 
|  | variant, | 
|  | data1, | 
|  | data2, | 
|  | data3, | 
|  | data4, | 
|  | data5, | 
|  | data6, | 
|  | data7, | 
|  | data8, | 
|  | data9, | 
|  | data10, | 
|  | ); | 
|  | } | 
|  | throw UnimplementedError('No variant "$variant"'); | 
|  | } | 
|  |  | 
|  | @pragma('vm:never-inline') | 
|  | @pragma('wasm:never-inline') | 
|  | @pragma('dart2js:never-inline') | 
|  | static int sum(List<int> list) { | 
|  | var s = 0; | 
|  | for (int i = 0; i < list.length; i++) { | 
|  | s += list[i]; | 
|  | } | 
|  | return s; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void run() { | 
|  | g = 0; | 
|  | g += sum(data1); | 
|  | g += sum(data2); | 
|  | g += sum(data3); | 
|  | g += sum(data4); | 
|  | g += sum(data5); | 
|  | g += sum(data6); | 
|  | g += sum(data7); | 
|  | g += sum(data8); | 
|  | g += sum(data9); | 
|  | g += sum(data10); | 
|  | check(); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Command-line arguments: | 
|  | /// | 
|  | /// `--baseline`: Run additional benchmarks to measure the benchmarking loop | 
|  | /// component. | 
|  | /// | 
|  | /// `--1`: Run additional benchmarks for singleton lists. | 
|  | /// | 
|  | /// `--all`: Run all benchmark variants and sizes. | 
|  | /// | 
|  | void main(List<String> commandLineArguments) { | 
|  | final arguments = [...commandLineArguments]; | 
|  |  | 
|  | final Set<int> sizes = {2, 100}; | 
|  |  | 
|  | bool baseline = arguments.remove('--baseline'); | 
|  |  | 
|  | if (arguments.remove('--reset')) { | 
|  | sizes.clear(); | 
|  | } | 
|  |  | 
|  | if (arguments.remove('--1')) sizes.add(1); | 
|  | if (arguments.remove('--2')) sizes.add(2); | 
|  | if (arguments.remove('--100')) sizes.add(100); | 
|  |  | 
|  | final all = arguments.remove('--all'); | 
|  |  | 
|  | if (all) { | 
|  | baseline = true; | 
|  | sizes.addAll([1, 2, 100]); | 
|  | } | 
|  |  | 
|  | if (arguments.isNotEmpty) { | 
|  | throw ArgumentError('Unused command line arguments: $arguments'); | 
|  | } | 
|  |  | 
|  | if (sizes.isEmpty) sizes.add(2); | 
|  |  | 
|  | final benchmarks = [ | 
|  | for (final length in sizes) ...[ | 
|  | if (baseline) Baseline(length), | 
|  | // | 
|  | Monomorphic(length), | 
|  | // | 
|  | Polymorphic1(length, 'array'), | 
|  | Polymorphic1(length, 'view'), | 
|  | // | 
|  | Polymorphic2(length, 'array'), | 
|  | Polymorphic2(length, 'view'), | 
|  | // | 
|  | Polymorphic3(length, 'array'), | 
|  | Polymorphic3(length, 'view'), | 
|  | // | 
|  | Polymorphic4(length, 'array'), | 
|  | Polymorphic4(length, 'view'), | 
|  | // | 
|  | Polymorphic5(length, 'array'), | 
|  | Polymorphic5(length, 'mixed'), | 
|  | ], | 
|  | ]; | 
|  |  | 
|  | // Warmup all benchmarks to ensure JIT compilers see full polymorphism. | 
|  | for (var benchmark in benchmarks) { | 
|  | benchmark.setup(); | 
|  | } | 
|  |  | 
|  | for (var benchmark in benchmarks) { | 
|  | benchmark.warmup(); | 
|  | } | 
|  |  | 
|  | for (var benchmark in benchmarks) { | 
|  | benchmark.report(); | 
|  | } | 
|  | } |