List copy benchmark
Change-Id: I25822ce9f2159ae801dc8c0953c1f9638b15d7f3 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/131261 Commit-Queue: Stephen Adams <sra@google.com> Reviewed-by: Lasse R.H. Nielsen <lrn@google.com>
This commit is contained in:
committed by
commit-bot@chromium.org
parent
fe666ce592
commit
a106ad0042
@@ -0,0 +1,151 @@
|
||||
// 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');
|
||||
|
||||
void setup() {
|
||||
// Ensure setup() is idempotent.
|
||||
if (inputs.isNotEmpty) return;
|
||||
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) {
|
||||
var 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';
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
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];
|
||||
}),
|
||||
];
|
||||
|
||||
main() {
|
||||
var 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user