From 1745926fc6492f88a8d27cdaa1b10c6d9be1a61c Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Fri, 20 Feb 2026 15:39:06 +0100 Subject: [PATCH] AK+Everywhere: Use MurmurHash3 for int/u64 hashing Rework our hash functions a bit for significant better performance: * Rename int_hash to u32_hash to mirror u64_hash. * Make pair_int_hash call u64_hash instead of multiple u32_hash()es. * Implement MurmurHash3's fmix32 and fmix64 for u32_hash and u64_hash. On my machine, this speeds up u32_hash by 20%, u64_hash by ~290%, and pair_int_hash by ~260%. We lose the property that an input of 0 results in something that is not 0. I've experimented with an offset to both hash functions, but it resulted in a measurable performance degradation for u64_hash. If there's a good use case for 0 not to result in 0, we can always add in that offset as a countermeasure in the future. --- AK/HashFunctions.h | 36 ++++++++++--------- AK/Traits.h | 4 +-- Libraries/LibGfx/Color.h | 2 +- Libraries/LibGfx/Font/Typeface.h | 4 +-- Libraries/LibJS/Runtime/PropertyKey.h | 2 +- Libraries/LibRegex/RegexMatcher.cpp | 2 +- .../GenerateNamedCharacterReferences.cpp | 2 +- Tests/AK/TestHashFunctions.cpp | 30 ++++++++-------- .../AST/expected/multiple-binding-forms.txt | 16 ++++----- 9 files changed, 51 insertions(+), 47 deletions(-) diff --git a/AK/HashFunctions.h b/AK/HashFunctions.h index b650f694dd..c64f3fb452 100644 --- a/AK/HashFunctions.h +++ b/AK/HashFunctions.h @@ -8,27 +8,31 @@ #include -constexpr unsigned int_hash(u32 key) +// MurmurHash3 32-bit finalizer (fmix32). +constexpr unsigned u32_hash(u32 key) { - key += ~(key << 15); - key ^= (key >> 10); - key += (key << 3); - key ^= (key >> 6); - key += ~(key << 11); - key ^= (key >> 16); + key ^= key >> 16; + key *= 0x85ebca6bU; + key ^= key >> 13; + key *= 0xc2b2ae35U; + key ^= key >> 16; return key; } +// MurmurHash3 64-bit finalizer (fmix64). +constexpr unsigned u64_hash(u64 key) +{ + key ^= key >> 33; + key *= 0xff51afd7ed558ccdULL; + key ^= key >> 33; + key *= 0xc4ceb9fe1a85ec53ULL; + key ^= key >> 33; + return static_cast(key); +} + constexpr unsigned pair_int_hash(u32 key1, u32 key2) { - return int_hash((int_hash(key1) * 209) ^ (int_hash(key2 * 413))); -} - -constexpr unsigned u64_hash(u64 key) -{ - u32 first = key & 0xFFFFFFFF; - u32 last = key >> 32; - return pair_int_hash(first, last); + return u64_hash((static_cast(key1) << 32) | key2); } constexpr unsigned ptr_hash(FlatPtr ptr) @@ -36,7 +40,7 @@ constexpr unsigned ptr_hash(FlatPtr ptr) if constexpr (sizeof(ptr) == 8) return u64_hash(ptr); else - return int_hash(ptr); + return u32_hash(ptr); } inline unsigned ptr_hash(void const* ptr) diff --git a/AK/Traits.h b/AK/Traits.h index 2d4a95e686..da8fa83437 100644 --- a/AK/Traits.h +++ b/AK/Traits.h @@ -46,7 +46,7 @@ struct Traits : public DefaultTraits { static unsigned hash(T value) { if constexpr (sizeof(T) < 8) - return int_hash(value); + return u32_hash(value); else return u64_hash(value); } @@ -60,7 +60,7 @@ struct Traits : public DefaultTraits { static unsigned hash(T value) { if constexpr (sizeof(T) < 8) - return int_hash(bit_cast(value)); + return u32_hash(bit_cast(value)); else return u64_hash(bit_cast(value)); } diff --git a/Libraries/LibGfx/Color.h b/Libraries/LibGfx/Color.h index 94a4edb662..fce5eda098 100644 --- a/Libraries/LibGfx/Color.h +++ b/Libraries/LibGfx/Color.h @@ -749,7 +749,7 @@ class Traits : public DefaultTraits { public: static unsigned hash(Color const& color) { - return int_hash(color.value()); + return u32_hash(color.value()); } }; diff --git a/Libraries/LibGfx/Font/Typeface.h b/Libraries/LibGfx/Font/Typeface.h index c2c3b3789b..74f693ee7a 100644 --- a/Libraries/LibGfx/Font/Typeface.h +++ b/Libraries/LibGfx/Font/Typeface.h @@ -49,9 +49,9 @@ struct FontCacheKey { unsigned hash() const { - auto h = pair_int_hash(int_hash(bit_cast(point_size)), axes.size()); + auto h = pair_int_hash(u32_hash(bit_cast(point_size)), axes.size()); for (auto const& axis : axes) - h = pair_int_hash(h, pair_int_hash(axis.tag.to_u32(), int_hash(bit_cast(axis.value)))); + h = pair_int_hash(h, pair_int_hash(axis.tag.to_u32(), u32_hash(bit_cast(axis.value)))); h = pair_int_hash(h, Traits::hash(shape_features)); return h; } diff --git a/Libraries/LibJS/Runtime/PropertyKey.h b/Libraries/LibJS/Runtime/PropertyKey.h index 4d1fb65053..4bb954d805 100644 --- a/Libraries/LibJS/Runtime/PropertyKey.h +++ b/Libraries/LibJS/Runtime/PropertyKey.h @@ -215,7 +215,7 @@ struct Traits : public DefaultTraits { if (name.is_symbol()) return ptr_hash(name.as_symbol()); if (name.is_number()) - return int_hash(name.as_number()); + return u32_hash(name.as_number()); VERIFY_NOT_REACHED(); } diff --git a/Libraries/LibRegex/RegexMatcher.cpp b/Libraries/LibRegex/RegexMatcher.cpp index 955c0fb748..601d48c0d4 100644 --- a/Libraries/LibRegex/RegexMatcher.cpp +++ b/Libraries/LibRegex/RegexMatcher.cpp @@ -721,6 +721,6 @@ template struct AK::Traits> : public AK::DefaultTraits> { static unsigned hash(regex::CacheKey const& key) { - return pair_int_hash(key.pattern.hash(), int_hash(to_underlying(key.options.value()))); + return pair_int_hash(key.pattern.hash(), u32_hash(to_underlying(key.options.value()))); } }; diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateNamedCharacterReferences.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateNamedCharacterReferences.cpp index 0d8c65385e..b37cbd1518 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateNamedCharacterReferences.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateNamedCharacterReferences.cpp @@ -277,7 +277,7 @@ private: for (int i = 0; i < 128; i++) { hash ^= ptr_hash(node->m_children[i].ptr()); } - hash ^= int_hash(static_cast(node->m_is_terminal)); + hash ^= u32_hash(static_cast(node->m_is_terminal)); return hash; } static bool equals(NonnullRefPtr const& a, NonnullRefPtr const& b) diff --git a/Tests/AK/TestHashFunctions.cpp b/Tests/AK/TestHashFunctions.cpp index 96610533d6..4b44a150fb 100644 --- a/Tests/AK/TestHashFunctions.cpp +++ b/Tests/AK/TestHashFunctions.cpp @@ -9,22 +9,22 @@ #include #include -TEST_CASE(int_hash) +TEST_CASE(u32_hash) { - static_assert(int_hash(42) == 3564735745u); - static_assert(int_hash(0) == 1177991625u); + static_assert(u32_hash(42) == 142593372u); + static_assert(u32_hash(0) == 0u); } TEST_CASE(pair_int_hash) { - static_assert(pair_int_hash(42, 17) == 339337046u); - static_assert(pair_int_hash(0, 0) == 954888656u); + static_assert(pair_int_hash(42, 17) == 1110885963u); + static_assert(pair_int_hash(0, 0) == 0u); } TEST_CASE(u64_hash) { - static_assert(u64_hash(42) == 2824066580u); - static_assert(u64_hash(0) == 954888656u); + static_assert(u64_hash(42) == 2386713036u); + static_assert(u64_hash(0) == 0u); } TEST_CASE(ptr_hash) @@ -32,17 +32,17 @@ TEST_CASE(ptr_hash) // These tests are not static_asserts because the values are // different and the goal is to bind the behavior. if constexpr (sizeof(FlatPtr) == 8) { - EXPECT_EQ(ptr_hash(FlatPtr(42)), 2824066580u); - EXPECT_EQ(ptr_hash(FlatPtr(0)), 954888656u); + EXPECT_EQ(ptr_hash(FlatPtr(42)), 2386713036u); + EXPECT_EQ(ptr_hash(FlatPtr(0)), 0u); - EXPECT_EQ(ptr_hash(reinterpret_cast(42)), 2824066580u); - EXPECT_EQ(ptr_hash(reinterpret_cast(0)), 954888656u); + EXPECT_EQ(ptr_hash(reinterpret_cast(42)), 2386713036u); + EXPECT_EQ(ptr_hash(reinterpret_cast(0)), 0u); } else { - EXPECT_EQ(ptr_hash(FlatPtr(42)), 3564735745u); - EXPECT_EQ(ptr_hash(FlatPtr(0)), 1177991625u); + EXPECT_EQ(ptr_hash(FlatPtr(42)), 142593372u); + EXPECT_EQ(ptr_hash(FlatPtr(0)), 0u); - EXPECT_EQ(ptr_hash(reinterpret_cast(42)), 3564735745u); - EXPECT_EQ(ptr_hash(reinterpret_cast(0)), 1177991625u); + EXPECT_EQ(ptr_hash(reinterpret_cast(42)), 142593372u); + EXPECT_EQ(ptr_hash(reinterpret_cast(0)), 0u); } } diff --git a/Tests/LibJS/AST/expected/multiple-binding-forms.txt b/Tests/LibJS/AST/expected/multiple-binding-forms.txt index a58ec7ba54..5f4bdf76d1 100644 --- a/Tests/LibJS/AST/expected/multiple-binding-forms.txt +++ b/Tests/LibJS/AST/expected/multiple-binding-forms.txt @@ -87,10 +87,10 @@ Program (script) @4:1 │ │ ├─ BindingPattern (array) │ │ │ ├─ entry │ │ │ │ └─ alias -│ │ │ │ └─ Identifier "a" [variable:2] @28:9 +│ │ │ │ └─ Identifier "a" [variable:1] @28:9 │ │ │ └─ entry │ │ │ └─ alias -│ │ │ └─ Identifier "b" [variable:3] @28:9 +│ │ │ └─ Identifier "b" [variable:4] @28:9 │ │ └─ ArrayExpression @28:18 │ │ ├─ NumericLiteral 1 @28:19 │ │ └─ NumericLiteral 2 @28:22 @@ -99,10 +99,10 @@ Program (script) @4:1 │ │ ├─ BindingPattern (object) │ │ │ ├─ entry │ │ │ │ └─ name -│ │ │ │ └─ Identifier "c" [variable:1] @29:11 +│ │ │ │ └─ Identifier "c" [variable:3] @29:11 │ │ │ └─ entry │ │ │ └─ name -│ │ │ └─ Identifier "d" [variable:4] @29:11 +│ │ │ └─ Identifier "d" [variable:2] @29:11 │ │ └─ ObjectExpression @29:22 │ │ ├─ ObjectProperty @29:22 │ │ │ ├─ StringLiteral "c" @29:22 @@ -129,10 +129,10 @@ Program (script) @4:1 │ │ ├─ BinaryExpression (+) @31:22 │ │ │ ├─ BinaryExpression (+) @31:18 │ │ │ │ ├─ BinaryExpression (+) @31:14 -│ │ │ │ │ ├─ Identifier "a" [variable:2] @31:12 -│ │ │ │ │ └─ Identifier "b" [variable:3] @31:16 -│ │ │ │ └─ Identifier "c" [variable:1] @31:20 -│ │ │ └─ Identifier "d" [variable:4] @31:24 +│ │ │ │ │ ├─ Identifier "a" [variable:1] @31:12 +│ │ │ │ │ └─ Identifier "b" [variable:4] @31:16 +│ │ │ │ └─ Identifier "c" [variable:3] @31:20 +│ │ │ └─ Identifier "d" [variable:2] @31:24 │ │ └─ Identifier "e" [variable:0] @31:28 │ └─ MemberExpression [computed] @31:33 │ ├─ Identifier "f" [variable:5] @31:32