diff --git a/DEPS b/DEPS index 51dcdedac60..6856948fa65 100644 --- a/DEPS +++ b/DEPS @@ -123,7 +123,7 @@ vars = { "args_rev": "5c83bc9785d6c32ffe6824ba79fadcc51fbcd1c1", "async_rev": "47968047eb9888f74ca0691640821bd55b47e763", "bazel_worker_rev": "79d2ad13c83d5e0883136503d86ddf60fe665900", - "benchmark_harness_rev": "aa139fdf3a3b829fa8c10719102c66729495afb6", + "benchmark_harness_rev": "197702c2c73e58eb2d31c1fd83cc5d8096d3eceb", "boolean_selector_rev": "24635df68661bb44c1c13fb405562421e24298e5", "browser_launcher_rev": "c4b2c81aa9debcce3651eda1b68a9bc5d5adf400", "characters_rev": "7633a16a22c626e19ca750223237396315268a06", diff --git a/benchmarks/ObjectHashPerf/dart/ObjectHashPerf.dart b/benchmarks/ObjectHashPerf/dart/ObjectHashPerf.dart new file mode 100644 index 00000000000..ecb6a10795c --- /dev/null +++ b/benchmarks/ObjectHashPerf/dart/ObjectHashPerf.dart @@ -0,0 +1,256 @@ +// Copyright (c) 2024, 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: hash_and_equals + +// Benchmark for `Object.hash` and `Object.hashAll`. + +import 'dart:math'; + +import 'package:benchmark_harness/perf_benchmark_harness.dart'; + +int get nextHash => Random().nextInt(0x20000000); + +// An object with a fast hashCode. +class Leaf { + @override + final int hashCode = nextHash; +} + +abstract class Node5 { + final item1 = Leaf(); + final item2 = Leaf(); + final item3 = Leaf(); + final item4 = Random().nextBool(); + final item5 = nextHash; +} + +class Node5Hash extends Node5 { + // This is the main subject of the benchmark - a typical use of `Object.hash`. + @override + int get hashCode => Object.hash(item1, item2, item3, item4, item5); +} + +class Node5Manual extends Node5 { + // This is a similar quality hashCode but with statically resolvable + // `hashCode` calls and a 0 seed (instead of loading a unique random seed from + // global late-final variable). + @override + int get hashCode => _SystemHash.hash5(item1.hashCode, item2.hashCode, + item3.hashCode, item4.hashCode, item5.hashCode, 0); +} + +class Node5List extends Node5 { + // This is a pattern that is sometimes used, especially for large numbers of + // items. + @override + int get hashCode => Object.hashAll([item1, item2, item3, item4, item5]); +} + +/// Returns a list with most values created by [makeValue], and a few objects of +/// different types so that the `hashCode` calls are polymorphic, like the ones +/// in the hashed collections. +List generateData(Object Function(int) makeValue) { + final List data = List.generate(1000, makeValue); + final exceptions = [ + Leaf(), + Node5Hash(), + Node5Manual(), + Node5List(), + '', + true, + false, + 123, + Object() + ]; + data.setRange(1, 1 + exceptions.length, exceptions); + return data; +} + +class BenchmarkNode5Hash extends PerfBenchmarkBase { + final List data = generateData((_) => Node5Hash()); + + BenchmarkNode5Hash() : super('ObjectHashPerf.hash.5'); + + @override + void run() { + for (final e in data) { + sink = e.hashCode; + } + } +} + +class BenchmarkNode5Manual extends PerfBenchmarkBase { + final List data = generateData((_) => Node5Manual()); + + BenchmarkNode5Manual() : super('ObjectHashPerf.manual.5'); + + @override + void run() { + for (final e in data) { + sink = e.hashCode; + } + } +} + +class BenchmarkNode5List extends PerfBenchmarkBase { + final List data = generateData((_) => Node5List()); + + BenchmarkNode5List() : super('ObjectHashPerf.list.5'); + + @override + void run() { + for (final e in data) { + sink = e.hashCode; + } + } +} + +class BenchmarkNode5HashHashAll extends PerfBenchmarkBase { + final List data = generateData((_) => Node5Hash()); + + BenchmarkNode5HashHashAll() : super('ObjectHashPerf.hash.5.hashAll'); + + @override + void run() { + sink = Object.hashAll(data); + } +} + +class BenchmarkNode5ManualHashAll extends PerfBenchmarkBase { + final List data = generateData((_) => Node5Manual()); + + BenchmarkNode5ManualHashAll() : super('ObjectHashPerf.manual.5.hashAll'); + + @override + void run() { + sink = Object.hashAll(data); + } +} + +Object? sink; + +void main() async { + generalUses(); + + final benchmarks = [ + BenchmarkNode5Hash.new, + BenchmarkNode5Manual.new, + BenchmarkNode5List.new, + BenchmarkNode5HashHashAll.new, + BenchmarkNode5ManualHashAll.new, + ]; + + // Warmup all benchmarks so that JIT compilers see full polymorphism before + // measuring. + for (var benchmark in benchmarks) { + benchmark().warmup(); + } + + if (sink == null) throw StateError('sink unassigned'); + + generalUses(); + + for (var benchmark in benchmarks) { + await benchmark().reportPerf(); + } +} + +/// Does a variety of calls to `Object.hash` to ensure the compiler does not +/// over-specialize the code on a few benchmark inputs. +void generalUses() { + void check(int a, int b) { + if (a != b) throw StateError('inconsistent'); + } + + // Exercise arity dispatch. + check(Object.hash(1, 2), Object.hash(1, 2)); + check(Object.hash(1, 2, 3), Object.hash(1, 2, 3)); + check(Object.hash(1, 2, 3, 4), Object.hash(1, 2, 3, 4)); + check(Object.hash(1, 2, 3, 4, 5), Object.hash(1, 2, 3, 4, 5)); + check(Object.hash(1, 2, 3, 4, 5, 6), Object.hash(1, 2, 3, 4, 5, 6)); + check(Object.hash(1, 2, 3, 4, 5, 6, 7), Object.hash(1, 2, 3, 4, 5, 6, 7)); + + final xs = Iterable.generate(20).toList(); + check(Function.apply(Object.hash, xs), Function.apply(Object.hash, xs)); + + // Exercise internal hashCode dispatch. + final a1 = 123; + final a2 = 'hello'; + final a3 = true; + final a4 = Object(); + final a5 = StringBuffer(); + const a6 = Point(1, 2); + const a7 = Rectangle(100, 200, 1, 1); + + check(Object.hash(a1, a2, a3, a4, a5), Object.hash(a1, a2, a3, a4, a5)); + check(Object.hash(a2, a3, a4, a5, a6), Object.hash(a2, a3, a4, a5, a6)); + check(Object.hash(a3, a4, a5, a6, a7), Object.hash(a3, a4, a5, a6, a7)); + check(Object.hash(a4, a5, a6, a7, a1), Object.hash(a4, a5, a6, a7, a1)); + check(Object.hash(a5, a6, a7, a1, a2), Object.hash(a5, a6, a7, a1, a2)); + check(Object.hash(a6, a7, a1, a2, a3), Object.hash(a6, a7, a1, a2, a3)); + check(Object.hash(a7, a1, a2, a3, a4), Object.hash(a7, a1, a2, a3, a4)); + + check(_SystemHash.hash2(1, 2, 0), _SystemHash.hash2(1, 2, 0)); + check(_SystemHash.hash3(1, 2, 3, 0), _SystemHash.hash3(1, 2, 3, 0)); + check(_SystemHash.hash4(1, 2, 3, 4, 0), _SystemHash.hash4(1, 2, 3, 4, 0)); + check( + _SystemHash.hash5(1, 2, 3, 4, 5, 0), _SystemHash.hash5(1, 2, 3, 4, 5, 0)); + + // Pollute hashAll argument type. + check(Object.hashAll({}), Object.hashAll([])); + check(Object.hashAll({}.values), Object.hashAll({}.keys)); + check(Object.hashAll(''.codeUnits), Object.hashAll(const Iterable.empty())); + check(Object.hashAll(const [0]), Object.hashAll(Iterable.generate(1))); +} + +// Partial copy of dart:internal `SystemHash` that is used by `Object.hash` so +// that we can create comparable manual hashCode methods. +class _SystemHash { + static int combine(int hash, int value) { + hash = 0x1fffffff & (hash + value); + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } + + static int hash2(int v1, int v2, int seed) { + int hash = seed; + hash = combine(hash, v1); + hash = combine(hash, v2); + return finish(hash); + } + + static int hash3(int v1, int v2, int v3, int seed) { + int hash = seed; + hash = combine(hash, v1); + hash = combine(hash, v2); + hash = combine(hash, v3); + return finish(hash); + } + + static int hash4(int v1, int v2, int v3, int v4, int seed) { + int hash = seed; + hash = combine(hash, v1); + hash = combine(hash, v2); + hash = combine(hash, v3); + hash = combine(hash, v4); + return finish(hash); + } + + static int hash5(int v1, int v2, int v3, int v4, int v5, int seed) { + int hash = seed; + hash = combine(hash, v1); + hash = combine(hash, v2); + hash = combine(hash, v3); + hash = combine(hash, v4); + hash = combine(hash, v5); + return finish(hash); + } +}