| // 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 millisecondsPerExercise = measure(); |
| // Report time in nanoseconds per element. [exercise] runs [run] 10 times, |
| // and each [run] does 10 summations. |
| final double score = millisecondsPerExercise * 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(); |
| } |
| } |