blob: f9e05e7fba456f652c2ac43c155093a88a5da297 [file] [log] [blame]
// 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
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 ParseBigIntBenchmark extends BenchmarkBase {
final int bits;
final BigInt seed;
final List<String> strings = [];
ParseBigIntBenchmark(String name, this.bits)
: seed = (BigInt.one << bits) - BigInt.one,
super(name);
@override
void setup() {
var b = seed;
var totalLength = 0;
while (totalLength < requiredDigits) {
if (b.bitLength < bits) {
b = seed;
}
final string = b.toString();
strings.add(string);
totalLength += string.length;
b = b - (b >> 8);
}
}
@override
void run() {
for (final s in strings) {
final b = BigInt.parse(s);
sink1 = s;
sink2 = b;
}
check(sink2.isEven);
}
}
int int64UnsignedBitLength(Int64 i) => i.isNegative ? 64 : i.bitLength;
class ParseInt64Benchmark extends BenchmarkBase {
final int bits;
final Int64 seed;
final List<String> strings = [];
ParseInt64Benchmark(String name, this.bits)
: seed = (Int64.ONE << bits) - Int64.ONE,
super(name);
@override
void setup() {
var b = seed;
var totalLength = 0;
while (totalLength < requiredDigits) {
if (int64UnsignedBitLength(b) < bits) {
b = seed;
}
final string = b.toStringUnsigned();
strings.add(string);
totalLength += string.length;
b = b - b.shiftRightUnsigned(8);
}
}
@override
void run() {
for (final s in strings) {
final b = Int64.parseInt(s);
sink1 = s;
sink2 = b;
}
check(sink2.isEven);
}
}
class ParseJsBigIntBenchmark extends BenchmarkBase {
final int bits;
final Object seed;
final List<String> strings = [];
ParseJsBigIntBenchmark(String name, this.bits)
: seed = nativeBigInt.subtract(
nativeBigInt.shiftLeft(
nativeBigInt.one, nativeBigInt.fromInt(bits)),
nativeBigInt.one),
super(name);
@override
void setup() {
var b = seed;
var totalLength = 0;
while (totalLength < requiredDigits) {
if (nativeBigInt.bitLength(b) < bits) {
b = seed;
}
final string = nativeBigInt.toStringMethod(b);
strings.add(string);
totalLength += string.length;
b = nativeBigInt.subtract(
b, nativeBigInt.shiftRight(b, nativeBigInt.eight));
}
}
@override
void run() {
for (final s in strings) {
final b = nativeBigInt.parse(s);
sink1 = s;
sink2 = b;
}
check(nativeBigInt.isEven(sink2));
}
}
class FormatBigIntBenchmark extends BenchmarkBase {
final int bits;
final BigInt seed;
final List<BigInt> values = [];
FormatBigIntBenchmark(String name, this.bits)
: seed = (BigInt.one << bits) - BigInt.one,
super(name);
@override
void setup() {
var b = seed;
var totalLength = 0;
while (totalLength < requiredDigits) {
if (b.bitLength < bits) {
b = seed;
}
final string = b.toString();
values.add(b - BigInt.one); // We add 'one' back later.
totalLength += string.length;
b = b - (b >> 8);
}
}
@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 FormatInt64Benchmark extends BenchmarkBase {
final int bits;
final Int64 seed;
final List<Int64> values = [];
FormatInt64Benchmark(String name, this.bits)
: seed = (Int64.ONE << bits) - Int64.ONE,
super(name);
@override
void setup() {
var b = seed;
var totalLength = 0;
while (totalLength < requiredDigits) {
if (int64UnsignedBitLength(b) < bits) {
b = seed;
}
final string = b.toStringUnsigned();
values.add(b - Int64.ONE);
totalLength += string.length;
b = b - b.shiftRightUnsigned(8);
}
}
@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 BenchmarkBase {
final int bits;
final Object seed;
final List<Object> values = [];
FormatJsBigIntBenchmark(String name, this.bits)
: seed = nativeBigInt.subtract(
nativeBigInt.shiftLeft(
nativeBigInt.one, nativeBigInt.fromInt(bits)),
nativeBigInt.one),
super(name);
@override
void setup() {
final one = nativeBigInt.one;
var b = seed;
var totalLength = 0;
while (totalLength < requiredDigits) {
if (nativeBigInt.bitLength(b) < bits) {
b = seed;
}
final string = nativeBigInt.toStringMethod(b);
values.add(nativeBigInt.subtract(b, one)); // We add 'one' back later.
totalLength += string.length;
b = nativeBigInt.subtract(
b, nativeBigInt.shiftRight(b, nativeBigInt.eight));
}
}
@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
double measure() => 2000 * 1000 * 1.0; // A rate of one run per 2s.
}
/// 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 = [
() => 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),
() => 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());
}