| // Copyright (c) 2019, 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. |
| |
| // ignore_for_file: avoid_function_literals_in_foreach_calls |
| |
| // @dart=2.9 |
| |
| import 'dart:math' show Random; |
| |
| import 'package:benchmark_harness/benchmark_harness.dart'; |
| import 'package:fixnum/fixnum.dart'; |
| |
| import 'native_version_dummy.dart' |
| if (dart.library.js) 'native_version_javascript.dart'; |
| |
| // Benckmark BigInt and Int64 formatting and parsing. |
| |
| // A global sink that is used in the [check] method ensures that the results are |
| // not optimized. |
| dynamic sink1, sink2; |
| |
| void check(bool sink2isEven) { |
| if (sink1.codeUnits.last.isEven != sink2isEven) { |
| throw StateError('Inconsistent $sink1 vs $sink2'); |
| } |
| } |
| |
| // These benchmarks measure digit-throughput for parsing and formatting. |
| // |
| // Each benchmark targets processing [requiredDigits] decimal digits, spread |
| // over a list of input values. This makes the benchmarks for different integer |
| // lengths roughly comparable. The number is chosen so that most benchmarks |
| // have very close to this number of digits. It corresponds to nine 4096-bit |
| // integers. |
| const requiredDigits = 11106; |
| |
| class Benchmark extends BenchmarkBase { |
| final List<String> strings; |
| Benchmark(String name, int bits, {bool forInt = false}) |
| : strings = generateStrings(bits, forInt), |
| super(name); |
| |
| static List<String> generateStrings(int bits, bool forInt) { |
| final List<String> strings = []; |
| final BigInt seed = (BigInt.one << bits) - BigInt.one; |
| var b = seed; |
| var restartDelta = BigInt.zero; |
| var totalLength = 0; |
| while (totalLength < requiredDigits) { |
| if (b.bitLength < bits) { |
| restartDelta += seed >> 20; |
| restartDelta += BigInt.one; |
| // Restart from a slighly reduced seed to generate different numbers. |
| b = seed - restartDelta; |
| } |
| var string = b.toString(); |
| |
| // Web integers lose precision due to rounding for larger values. Make |
| // sure the string will round-trip correctly. |
| if (forInt) string = int.parse(string).toString(); |
| |
| strings.add(string); |
| totalLength += string.length; |
| var delta = b >> 8; |
| if (delta == BigInt.zero) delta = BigInt.one; |
| b = b - delta; |
| } |
| return strings; |
| } |
| } |
| |
| class ParseBigIntBenchmark extends Benchmark { |
| ParseBigIntBenchmark(String name, int bits) : super(name, bits); |
| |
| @override |
| void run() { |
| for (final s in strings) { |
| final b = BigInt.parse(s); |
| sink1 = s; |
| sink2 = b; |
| } |
| check(sink2.isEven); |
| } |
| } |
| |
| class ParseInt64Benchmark extends Benchmark { |
| ParseInt64Benchmark(String name, int bits) : super(name, bits); |
| |
| @override |
| void run() { |
| for (final s in strings) { |
| final b = Int64.parseInt(s); |
| sink1 = s; |
| sink2 = b; |
| } |
| check(sink2.isEven); |
| } |
| } |
| |
| class ParseIntBenchmark extends Benchmark { |
| ParseIntBenchmark(String name, int bits) : super(name, bits, forInt: true); |
| |
| @override |
| void run() { |
| for (final s in strings) { |
| final b = int.parse(s); |
| sink1 = s; |
| sink2 = b; |
| } |
| check(sink2.isEven); |
| } |
| } |
| |
| class ParseJsBigIntBenchmark extends Benchmark { |
| ParseJsBigIntBenchmark(String name, int bits) : super(name, bits); |
| |
| @override |
| void run() { |
| for (final s in strings) { |
| final b = nativeBigInt.parse(s); |
| sink1 = s; |
| sink2 = b; |
| } |
| check(nativeBigInt.isEven(sink2)); |
| } |
| } |
| |
| class FormatBigIntBenchmark extends Benchmark { |
| final List<BigInt> values = []; |
| |
| FormatBigIntBenchmark(String name, int bits) : super(name, bits); |
| |
| @override |
| void setup() { |
| for (String s in strings) { |
| final BigInt b = BigInt.parse(s); |
| values.add(b - BigInt.one); // We add 'one' back later. |
| } |
| } |
| |
| @override |
| void run() { |
| final one = BigInt.one; |
| for (final b0 in values) { |
| // Instances might cache `toString()`, so use arithmetic to create a new |
| // instance to try to protect against measuring a cached string. |
| final b = b0 + one; |
| final s = b.toString(); |
| sink1 = s; |
| sink2 = b; |
| } |
| check(sink2.isEven); |
| } |
| } |
| |
| class FormatIntBenchmark extends Benchmark { |
| final List<int> values = []; |
| |
| FormatIntBenchmark(String name, int bits) : super(name, bits, forInt: true); |
| |
| @override |
| void setup() { |
| for (String s in strings) { |
| final int b = int.parse(s); |
| values.add(b - 4096); // We add this back later. |
| } |
| } |
| |
| @override |
| void run() { |
| for (final b0 in values) { |
| // Instances might cache `toString()`, so use arithmetic to create a new |
| // instance to try to protect against measuring a cached string. We use |
| // 4096 to avoid the arithmetic being a no-op due to rounding on web |
| // integers (i.e. doubles). |
| final b = b0 + 4096; |
| final s = b.toString(); |
| sink1 = s; |
| sink2 = b; |
| } |
| check(sink2.isEven); |
| } |
| } |
| |
| class FormatInt64Benchmark extends Benchmark { |
| final List<Int64> values = []; |
| |
| FormatInt64Benchmark(String name, int bits) : super(name, bits); |
| |
| @override |
| void setup() { |
| for (String s in strings) { |
| final b = Int64.parseInt(s); |
| values.add(b - Int64.ONE); // We add this back later. |
| } |
| } |
| |
| @override |
| void run() { |
| final one = Int64.ONE; |
| for (final b0 in values) { |
| // Instances might cache `toString()`, so use arithmetic to create a new |
| // instance to try to protect against measuring a cached string. |
| final b = b0 + one; |
| final s = b.toStringUnsigned(); |
| sink1 = s; |
| sink2 = b; |
| } |
| check(sink2.isEven); |
| } |
| } |
| |
| class FormatJsBigIntBenchmark extends Benchmark { |
| final List<Object> values = []; |
| |
| FormatJsBigIntBenchmark(String name, int bits) : super(name, bits); |
| |
| @override |
| void setup() { |
| final one = nativeBigInt.one; |
| for (String s in strings) { |
| final b = nativeBigInt.parse(s); |
| values.add(nativeBigInt.subtract(b, one)); // We add this back later. |
| } |
| } |
| |
| @override |
| void run() { |
| final one = nativeBigInt.one; |
| for (final b0 in values) { |
| // Instances might cache `toString()`, so use arithmetic to create a new |
| // instance to try to protect against measuring a cached string. |
| final b = nativeBigInt.add(b0, one); |
| final s = nativeBigInt.toStringMethod(b); |
| sink1 = s; |
| sink2 = b; |
| } |
| check(nativeBigInt.isEven(sink2)); |
| } |
| } |
| |
| /// [DummyBenchmark] instantly returns a fixed 'slow' result. |
| class DummyBenchmark extends BenchmarkBase { |
| DummyBenchmark(String name) : super(name); |
| @override |
| // A rate of one run per 2s, with a millisecond of noise. Some variation is |
| // needed for Golem's noise-based filtering and regression detection. |
| double measure() => (2000 + Random().nextDouble() - 0.5) * 1000; |
| } |
| |
| /// Create [ParseJsBigIntBenchmark], or a dummy benchmark if JavaScript BigInt |
| /// is not available. This is to satisfy Golem's constraint that group |
| /// benchmarks always produce results for the same set of series. |
| BenchmarkBase Function() selectParseNativeBigIntBenchmark( |
| String name, int bits) { |
| return nativeBigInt.enabled |
| ? () => ParseJsBigIntBenchmark(name, bits) |
| : () => DummyBenchmark(name); |
| } |
| |
| /// Create [FormatJsBigIntBenchmark], or a dummy benchmark if JavaScript BigInt |
| /// is not available. This is to satisfy Golem's constraint that group |
| /// benchmarks always produce results for the same set of series. |
| BenchmarkBase Function() selectFormatNativeBigIntBenchmark( |
| String name, int bits) { |
| return nativeBigInt.enabled |
| ? () => FormatJsBigIntBenchmark(name, bits) |
| : () => DummyBenchmark(name); |
| } |
| |
| void main() { |
| final benchmarks = [ |
| () => ParseIntBenchmark('Int.parse.0009.bits', 9), |
| () => ParseIntBenchmark('Int.parse.0032.bits', 32), |
| // Use '63' bits to avoid 64-bit arithmetic overflowing to negative. Keep |
| // the name as '64' to help comparisons. The effect of an incorrect number |
| // is reduced since benchmark results are normalized to a 'per digit' score |
| () => ParseIntBenchmark('Int.parse.0064.bits', 63), |
| () => ParseInt64Benchmark('Int64.parse.0009.bits', 9), |
| () => ParseInt64Benchmark('Int64.parse.0032.bits', 32), |
| () => ParseInt64Benchmark('Int64.parse.0064.bits', 64), |
| () => ParseBigIntBenchmark('BigInt.parse.0009.bits', 9), |
| () => ParseBigIntBenchmark('BigInt.parse.0032.bits', 32), |
| () => ParseBigIntBenchmark('BigInt.parse.0064.bits', 64), |
| () => ParseBigIntBenchmark('BigInt.parse.0256.bits', 256), |
| () => ParseBigIntBenchmark('BigInt.parse.1024.bits', 1024), |
| () => ParseBigIntBenchmark('BigInt.parse.4096.bits', 4096), |
| selectParseNativeBigIntBenchmark('JsBigInt.parse.0009.bits', 9), |
| selectParseNativeBigIntBenchmark('JsBigInt.parse.0032.bits', 32), |
| selectParseNativeBigIntBenchmark('JsBigInt.parse.0064.bits', 64), |
| selectParseNativeBigIntBenchmark('JsBigInt.parse.0256.bits', 256), |
| selectParseNativeBigIntBenchmark('JsBigInt.parse.1024.bits', 1024), |
| selectParseNativeBigIntBenchmark('JsBigInt.parse.4096.bits', 4096), |
| () => FormatIntBenchmark('Int.toString.0009.bits', 9), |
| () => FormatIntBenchmark('Int.toString.0032.bits', 32), |
| () => FormatIntBenchmark('Int.toString.0064.bits', 63), // '63': See above. |
| () => FormatInt64Benchmark('Int64.toString.0009.bits', 9), |
| () => FormatInt64Benchmark('Int64.toString.0032.bits', 32), |
| () => FormatInt64Benchmark('Int64.toString.0064.bits', 64), |
| () => FormatBigIntBenchmark('BigInt.toString.0009.bits', 9), |
| () => FormatBigIntBenchmark('BigInt.toString.0032.bits', 32), |
| () => FormatBigIntBenchmark('BigInt.toString.0064.bits', 64), |
| () => FormatBigIntBenchmark('BigInt.toString.0256.bits', 256), |
| () => FormatBigIntBenchmark('BigInt.toString.1024.bits', 1024), |
| () => FormatBigIntBenchmark('BigInt.toString.4096.bits', 4096), |
| selectFormatNativeBigIntBenchmark('JsBigInt.toString.0009.bits', 9), |
| selectFormatNativeBigIntBenchmark('JsBigInt.toString.0032.bits', 32), |
| selectFormatNativeBigIntBenchmark('JsBigInt.toString.0064.bits', 64), |
| selectFormatNativeBigIntBenchmark('JsBigInt.toString.0256.bits', 256), |
| selectFormatNativeBigIntBenchmark('JsBigInt.toString.1024.bits', 1024), |
| selectFormatNativeBigIntBenchmark('JsBigInt.toString.4096.bits', 4096), |
| ]; |
| |
| // Warm up all benchmarks to ensure consistent behavious of shared code. |
| benchmarks.forEach((bm) => bm() |
| ..setup() |
| ..run() |
| ..run()); |
| |
| benchmarks.forEach((bm) => bm().report()); |
| } |