| // 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<int>(1, 2); |
| const a7 = Rectangle<int>(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); |
| } |
| } |