| // 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'; | 
 |  | 
 | // Benchmark 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 slightly 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 behaviors of shared code. | 
 |   benchmarks.forEach((bm) => bm() | 
 |     ..setup() | 
 |     ..run() | 
 |     ..run()); | 
 |  | 
 |   benchmarks.forEach((bm) => bm().report()); | 
 | } |