| // 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. |
| |
| import 'dart:collection'; |
| import 'dart:typed_data'; |
| import 'package:benchmark_harness/benchmark_harness.dart'; |
| |
| // Benchmark for polymorphic list copying. |
| // |
| // Each benchmark creates a list from an Iterable. There are many slightly |
| // different ways to do this. |
| // |
| // In each benchmark the call site is polymorphic in the input type to simulate |
| // the behaviour of library methods in the context of a large application. The |
| // input lists are skewed heavily to the default growable list. This attempts to |
| // model 'real world' copying of 'ordinary' lists. |
| // |
| // The benchmarks are run for small lists (2 elements, names ending in |
| // `.2`) and 'large' lists (100 elements or `.100`). The benchmarks are |
| // normalized on the number of elements to make the input sizes comparable. |
| // |
| // Most inputs have type `Iterable<num>`, but contain only `int` values. This |
| // allows is to compare the down-conversion versions of copying where each |
| // element must be checked. |
| |
| class Benchmark extends BenchmarkBase { |
| final int length; |
| final Function() copy; |
| |
| final List<Iterable<num>> inputs = []; |
| |
| Benchmark(String name, this.length, this.copy) |
| : super('ListCopy.$name.$length'); |
| |
| @override |
| void setup() { |
| // Ensure setup() is idempotent. |
| if (inputs.isNotEmpty) return; |
| final List<num> base = List.generate(length, (i) => i + 1); |
| List<Iterable<num>> makeVariants() { |
| return [ |
| // Weight ordinary lists more. |
| ...List.generate(19, (_) => List<num>.of(base)), |
| |
| base.toList(growable: false), |
| List<num>.unmodifiable(base), |
| UnmodifiableListView(base), |
| base.reversed, |
| String.fromCharCodes(List<int>.from(base)).codeUnits, |
| Uint8List.fromList(List<int>.from(base)), |
| ]; |
| } |
| |
| const elements = 10000; |
| int totalLength = 0; |
| while (totalLength < elements) { |
| final variants = makeVariants(); |
| inputs.addAll(variants); |
| totalLength += |
| variants.fold<int>(0, (sum, iterable) => sum + iterable.length); |
| } |
| |
| // Sanity checks. |
| for (var sample in inputs) { |
| if (sample.length != length) throw 'Wrong length: $length $sample'; |
| } |
| if (totalLength != elements) { |
| throw 'totalLength $totalLength != expected $elements'; |
| } |
| } |
| |
| @override |
| void run() { |
| for (var sample in inputs) { |
| input = sample; |
| // Unroll loop 10 times to reduce loop overhead, which is about 15% for |
| // the fastest short input benchmarks. |
| copy(); |
| copy(); |
| copy(); |
| copy(); |
| copy(); |
| copy(); |
| copy(); |
| copy(); |
| copy(); |
| copy(); |
| } |
| if (output.length != inputs.first.length) throw 'Bad result: $output'; |
| } |
| } |
| |
| // All the 'copy' methods use [input] and [output] rather than a parameter and |
| // return value to avoid any possibility of type check in the call sequence. |
| Iterable<num> input = const []; |
| var output; |
| |
| List<Benchmark> makeBenchmarks(int length) => [ |
| Benchmark('toList', length, () { |
| output = input.toList(); |
| }), |
| Benchmark('toList.fixed', length, () { |
| output = input.toList(growable: false); |
| }), |
| Benchmark('List.of', length, () { |
| output = List<num>.of(input); |
| }), |
| Benchmark('List.of.fixed', length, () { |
| output = List<num>.of(input, growable: false); |
| }), |
| Benchmark('List.num.from', length, () { |
| output = List<num>.from(input); |
| }), |
| Benchmark('List.int.from', length, () { |
| output = List<int>.from(input); |
| }), |
| Benchmark('List.num.from.fixed', length, () { |
| output = List<num>.from(input, growable: false); |
| }), |
| Benchmark('List.int.from.fixed', length, () { |
| output = List<int>.from(input, growable: false); |
| }), |
| Benchmark('List.num.unmodifiable', length, () { |
| output = List<num>.unmodifiable(input); |
| }), |
| Benchmark('List.int.unmodifiable', length, () { |
| output = List<int>.unmodifiable(input); |
| }), |
| Benchmark('spread.num', length, () { |
| output = <num>[...input]; |
| }), |
| Benchmark('spread.int', length, () { |
| output = <int>[...input as dynamic]; |
| }), |
| Benchmark('spread.int.cast', length, () { |
| output = <int>[...input.cast<int>()]; |
| }), |
| Benchmark('spread.int.map', length, () { |
| output = <int>[...input.map((x) => x as int)]; |
| }), |
| Benchmark('for.int', length, () { |
| output = <int>[for (var n in input) n as int]; |
| }), |
| ]; |
| |
| void main() { |
| final benchmarks = [...makeBenchmarks(2), ...makeBenchmarks(100)]; |
| |
| // 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) { |
| // `report` calls `setup`, but `setup` is idempotent. |
| benchmark.report(); |
| } |
| } |