-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge ce99413 into dev
- Loading branch information
Showing
20 changed files
with
440 additions
and
65 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.