// 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); } }