|  | // 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(); | 
|  | } | 
|  | } |