// 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`, 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> 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 base = List.generate(length, (i) => i + 1); List> makeVariants() { return [ // Weight ordinary lists more. ...List.generate(19, (_) => List.of(base)), base.toList(growable: false), List.unmodifiable(base), UnmodifiableListView(base), base.reversed, String.fromCharCodes(List.from(base)).codeUnits, Uint8List.fromList(List.from(base)), ]; } const elements = 10000; int totalLength = 0; while (totalLength < elements) { final variants = makeVariants(); inputs.addAll(variants); totalLength += variants.fold(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 input = const []; var output; List makeBenchmarks(int length) => [ Benchmark('toList', length, () { output = input.toList(); }), Benchmark('toList.fixed', length, () { output = input.toList(growable: false); }), Benchmark('List.of', length, () { output = List.of(input); }), Benchmark('List.of.fixed', length, () { output = List.of(input, growable: false); }), Benchmark('List.num.from', length, () { output = List.from(input); }), Benchmark('List.int.from', length, () { output = List.from(input); }), Benchmark('List.num.from.fixed', length, () { output = List.from(input, growable: false); }), Benchmark('List.int.from.fixed', length, () { output = List.from(input, growable: false); }), Benchmark('List.num.unmodifiable', length, () { output = List.unmodifiable(input); }), Benchmark('List.int.unmodifiable', length, () { output = List.unmodifiable(input); }), Benchmark('spread.num', length, () { output = [...input]; }), Benchmark('spread.int', length, () { output = [...input as dynamic]; }), Benchmark('spread.int.cast', length, () { output = [...input.cast()]; }), Benchmark('spread.int.map', length, () { output = [...input.map((x) => x as int)]; }), Benchmark('for.int', length, () { output = [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(); } }