2020-01-14 06:16:35 +00:00
|
|
|
// 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');
|
|
|
|
|
2020-06-25 15:28:54 +00:00
|
|
|
@override
|
2020-01-14 06:16:35 +00:00
|
|
|
void setup() {
|
|
|
|
// Ensure setup() is idempotent.
|
|
|
|
if (inputs.isNotEmpty) return;
|
2020-06-25 15:28:54 +00:00
|
|
|
final List<num> base = List.generate(length, (i) => i + 1);
|
2020-01-14 06:16:35 +00:00
|
|
|
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) {
|
2020-06-25 15:28:54 +00:00
|
|
|
final variants = makeVariants();
|
2020-01-14 06:16:35 +00:00
|
|
|
inputs.addAll(variants);
|
2020-03-20 20:36:13 +00:00
|
|
|
totalLength +=
|
2020-03-24 00:22:45 +00:00
|
|
|
variants.fold<int>(0, (sum, iterable) => sum + iterable.length);
|
2020-01-14 06:16:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Sanity checks.
|
|
|
|
for (var sample in inputs) {
|
|
|
|
if (sample.length != length) throw 'Wrong length: $length $sample';
|
|
|
|
}
|
|
|
|
if (totalLength != elements) {
|
|
|
|
throw 'totalLength $totalLength != expected $elements';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-25 15:28:54 +00:00
|
|
|
@override
|
2020-01-14 06:16:35 +00:00
|
|
|
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.
|
2020-03-20 20:36:13 +00:00
|
|
|
Iterable<num> input = const [];
|
2020-01-14 06:16:35 +00:00
|
|
|
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, () {
|
2020-06-26 22:51:08 +00:00
|
|
|
output = <int>[...input as dynamic];
|
2020-01-14 06:16:35 +00:00
|
|
|
}),
|
2020-07-01 16:01:40 +00:00
|
|
|
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];
|
|
|
|
}),
|
2020-01-14 06:16:35 +00:00
|
|
|
];
|
|
|
|
|
2020-06-25 15:28:54 +00:00
|
|
|
void main() {
|
|
|
|
final benchmarks = [...makeBenchmarks(2), ...makeBenchmarks(100)];
|
2020-01-14 06:16:35 +00:00
|
|
|
|
|
|
|
// 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();
|
|
|
|
}
|
|
|
|
}
|