[vm/aot] Improve AOT compilation speed by using better hash codes

This change improves hash code implementations in multiple places in
the compiler. That reduces number of probes during lookups in hash maps
and improves AOT compilation time of large applications.

On a large Flutter app, compiled in release mode for arm64:
Total gen_snapshot time 89.184s -> 60.736s (-31.9%)

Also, this change adds --hash_map_probes_limit=N option which sets
a hard limit for the number of probes in hash maps. This option
makes it easy to find hash maps where there are many collisions
due to poor hash code implementation.

TEST=ci

Issue: https://github.com/dart-lang/sdk/issues/43299
Bug: b/154155290
Change-Id: Ibf6f37d4b9f3bf42dd6731bfb4095a7305b98b2d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/229240
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
Alexander Markov 2022-01-21 02:02:00 +00:00 committed by Commit Bot
parent d17205184a
commit 2a34453dd1
7 changed files with 39 additions and 3 deletions

View file

@ -219,7 +219,7 @@ class InstanceKeyValueTrait {
static Value ValueOf(Pair kv) { return kv; }
static inline uword Hash(Key key) { return key->GetClassId(); }
static inline uword Hash(Key key) { return key->CanonicalizeHash(); }
static inline bool IsKeyEqual(Pair pair, Key key) {
return pair->ptr() == key->ptr();

View file

@ -101,6 +101,11 @@ intptr_t ObjectHash(const Object& obj) {
if (obj.IsNull()) {
return kNullIdentityHash;
}
// TypeArguments should be handled before Instance as TypeArguments extends
// Instance and TypeArguments::CanonicalizeHash just returns 0.
if (obj.IsTypeArguments()) {
return TypeArguments::Cast(obj).Hash();
}
if (obj.IsInstance()) {
return Instance::Cast(obj).CanonicalizeHash();
}
@ -121,6 +126,10 @@ intptr_t ObjectHash(const Object& obj) {
return obj.GetClassId();
}
const char* ObjectToCString(const Object& obj) {
return obj.ToCString();
}
void SetToNull(Object* obj) {
*obj = Object::null();
}

View file

@ -181,6 +181,9 @@ inline const Object& ToObject(const Function& handle) {
// or canonical hash.
intptr_t ObjectHash(const Object& obj);
// Prints the given object into a C string.
const char* ObjectToCString(const Object& obj);
// If the given object represents a Dart integer returns true and sets [value]
// to the value of the integer.
bool HasIntegerValue(const dart::Object& obj, int64_t* value);

View file

@ -143,6 +143,8 @@ constexpr bool FLAG_support_il_printer = false;
P(marker_tasks, int, 2, \
"The number of tasks to spawn during old gen GC marking (0 means " \
"perform all marking on main thread).") \
P(hash_map_probes_limit, int, kMaxInt32, \
"Limit number of probes while doing lookups in hash maps.") \
P(max_polymorphic_checks, int, 4, \
"Maximum number of polymorphic check, otherwise it is megamorphic.") \
P(max_equality_polymorphic_checks, int, 32, \

View file

@ -6,6 +6,7 @@
#define RUNTIME_VM_HASH_MAP_H_
#include "platform/utils.h"
#include "vm/flags.h"
#include "vm/growable_array.h" // For Malloc, EmptyBase
#include "vm/hash.h"
#include "vm/zone.h"
@ -132,6 +133,7 @@ BaseDirectChainedHashMap<KeyValueTrait, B, Allocator>::Lookup(
uint32_t mask = hash_table_size_ - 1;
uint32_t hash_index = hash & mask;
uint32_t start = hash_index;
intptr_t probes = 0;
for (;;) {
uint32_t pair_index = hash_table_[hash_index];
if (pair_index == kEmpty) {
@ -139,6 +141,7 @@ BaseDirectChainedHashMap<KeyValueTrait, B, Allocator>::Lookup(
}
if (pair_index != kDeleted) {
ASSERT(pair_index < pairs_size_);
RELEASE_ASSERT(++probes < FLAG_hash_map_probes_limit);
if (KeyValueTrait::IsKeyEqual(pairs_[pair_index], key)) {
return &pairs_[pair_index];
}
@ -233,6 +236,7 @@ void BaseDirectChainedHashMap<KeyValueTrait, B, Allocator>::Insert(
uint32_t mask = hash_table_size_ - 1;
uint32_t hash_index = hash & mask;
uint32_t start = hash_index;
intptr_t probes = 0;
for (;;) {
uint32_t pair_index = hash_table_[hash_index];
if ((pair_index == kEmpty) || (pair_index == kDeleted)) {
@ -241,6 +245,7 @@ void BaseDirectChainedHashMap<KeyValueTrait, B, Allocator>::Insert(
next_pair_index_++;
break;
}
RELEASE_ASSERT(++probes < FLAG_hash_map_probes_limit);
ASSERT(pair_index < pairs_size_);
hash_index = (hash_index + 1) & mask;
// Hashtable must contain at least one empty marker.
@ -273,6 +278,7 @@ bool BaseDirectChainedHashMap<KeyValueTrait, B, Allocator>::Remove(
uint32_t mask = hash_table_size_ - 1;
uint32_t hash_index = hash & mask;
uint32_t start = hash_index;
intptr_t probes = 0;
for (;;) {
uint32_t pair_index = hash_table_[hash_index];
if (pair_index == kEmpty) {
@ -280,6 +286,7 @@ bool BaseDirectChainedHashMap<KeyValueTrait, B, Allocator>::Remove(
}
if (pair_index != kDeleted) {
ASSERT(pair_index < pairs_size_);
RELEASE_ASSERT(++probes < FLAG_hash_map_probes_limit);
if (KeyValueTrait::IsKeyEqual(pairs_[pair_index], key)) {
hash_table_[hash_index] = kDeleted;
pairs_[pair_index] = typename KeyValueTrait::Pair();

View file

@ -5887,6 +5887,11 @@ class CodeSourceMap : public Object {
return memcmp(untag(), other.untag(), InstanceSize(Length())) == 0;
}
uint32_t Hash() const {
NoSafepointScope no_safepoint;
return HashBytes(Data(), Length());
}
void PrintToJSONObject(JSONObject* jsobj, bool ref) const;
private:

View file

@ -1020,7 +1020,7 @@ class CodeSourceMapKeyValueTrait {
static inline uword Hash(Key key) {
ASSERT(!key->IsNull());
return Utils::WordHash(key->Length());
return Utils::WordHash(key->Hash());
}
static inline bool IsKeyEqual(Pair pair, Key key) {
@ -1070,7 +1070,14 @@ class ArrayKeyValueTrait {
static inline uword Hash(Key key) {
ASSERT(!key->IsNull());
return Utils::WordHash(key->Length());
ASSERT(Thread::Current()->no_safepoint_scope_depth() > 0);
const intptr_t len = key->Length();
uint32_t hash = Utils::WordHash(len);
for (intptr_t i = 0; i < len; ++i) {
hash =
CombineHashes(hash, Utils::WordHash(static_cast<uword>(key->At(i))));
}
return hash;
}
static inline bool IsKeyEqual(Pair pair, Key key) {
@ -1128,6 +1135,9 @@ void ProgramVisitor::DedupLists(Thread* thread) {
};
StackZone stack_zone(thread);
// ArrayKeyValueTrait::Hash is based on object addresses, so make
// sure GC doesn't happen and doesn't move objects.
NoSafepointScope no_safepoint;
DedupListsVisitor visitor(thread->zone());
WalkProgram(thread->zone(), thread->isolate_group(), &visitor);
}