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.
This commit is contained in:
Jelle Raaijmakers
2026-02-20 15:39:06 +01:00
committed by Andreas Kling
parent ebda8fcf11
commit 1745926fc6
Notes: github-actions[bot] 2026-02-20 21:48:50 +00:00
9 changed files with 51 additions and 47 deletions

View File

@@ -8,27 +8,31 @@
#include <AK/Types.h> #include <AK/Types.h>
constexpr unsigned int_hash(u32 key) // MurmurHash3 32-bit finalizer (fmix32).
constexpr unsigned u32_hash(u32 key)
{ {
key += ~(key << 15); key ^= key >> 16;
key ^= (key >> 10); key *= 0x85ebca6bU;
key += (key << 3); key ^= key >> 13;
key ^= (key >> 6); key *= 0xc2b2ae35U;
key += ~(key << 11); key ^= key >> 16;
key ^= (key >> 16);
return key; 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<unsigned>(key);
}
constexpr unsigned pair_int_hash(u32 key1, u32 key2) constexpr unsigned pair_int_hash(u32 key1, u32 key2)
{ {
return int_hash((int_hash(key1) * 209) ^ (int_hash(key2 * 413))); return u64_hash((static_cast<u64>(key1) << 32) | key2);
}
constexpr unsigned u64_hash(u64 key)
{
u32 first = key & 0xFFFFFFFF;
u32 last = key >> 32;
return pair_int_hash(first, last);
} }
constexpr unsigned ptr_hash(FlatPtr ptr) constexpr unsigned ptr_hash(FlatPtr ptr)
@@ -36,7 +40,7 @@ constexpr unsigned ptr_hash(FlatPtr ptr)
if constexpr (sizeof(ptr) == 8) if constexpr (sizeof(ptr) == 8)
return u64_hash(ptr); return u64_hash(ptr);
else else
return int_hash(ptr); return u32_hash(ptr);
} }
inline unsigned ptr_hash(void const* ptr) inline unsigned ptr_hash(void const* ptr)

View File

@@ -46,7 +46,7 @@ struct Traits<T> : public DefaultTraits<T> {
static unsigned hash(T value) static unsigned hash(T value)
{ {
if constexpr (sizeof(T) < 8) if constexpr (sizeof(T) < 8)
return int_hash(value); return u32_hash(value);
else else
return u64_hash(value); return u64_hash(value);
} }
@@ -60,7 +60,7 @@ struct Traits<T> : public DefaultTraits<T> {
static unsigned hash(T value) static unsigned hash(T value)
{ {
if constexpr (sizeof(T) < 8) if constexpr (sizeof(T) < 8)
return int_hash(bit_cast<u32>(value)); return u32_hash(bit_cast<u32>(value));
else else
return u64_hash(bit_cast<u64>(value)); return u64_hash(bit_cast<u64>(value));
} }

View File

@@ -749,7 +749,7 @@ class Traits<Color> : public DefaultTraits<Color> {
public: public:
static unsigned hash(Color const& color) static unsigned hash(Color const& color)
{ {
return int_hash(color.value()); return u32_hash(color.value());
} }
}; };

View File

@@ -49,9 +49,9 @@ struct FontCacheKey {
unsigned hash() const unsigned hash() const
{ {
auto h = pair_int_hash(int_hash(bit_cast<u32>(point_size)), axes.size()); auto h = pair_int_hash(u32_hash(bit_cast<u32>(point_size)), axes.size());
for (auto const& axis : axes) for (auto const& axis : axes)
h = pair_int_hash(h, pair_int_hash(axis.tag.to_u32(), int_hash(bit_cast<u32>(axis.value)))); h = pair_int_hash(h, pair_int_hash(axis.tag.to_u32(), u32_hash(bit_cast<u32>(axis.value))));
h = pair_int_hash(h, Traits<Gfx::ShapeFeatures>::hash(shape_features)); h = pair_int_hash(h, Traits<Gfx::ShapeFeatures>::hash(shape_features));
return h; return h;
} }

View File

@@ -215,7 +215,7 @@ struct Traits<JS::PropertyKey> : public DefaultTraits<JS::PropertyKey> {
if (name.is_symbol()) if (name.is_symbol())
return ptr_hash(name.as_symbol()); return ptr_hash(name.as_symbol());
if (name.is_number()) if (name.is_number())
return int_hash(name.as_number()); return u32_hash(name.as_number());
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
} }

View File

@@ -721,6 +721,6 @@ template<typename Parser>
struct AK::Traits<regex::CacheKey<Parser>> : public AK::DefaultTraits<regex::CacheKey<Parser>> { struct AK::Traits<regex::CacheKey<Parser>> : public AK::DefaultTraits<regex::CacheKey<Parser>> {
static unsigned hash(regex::CacheKey<Parser> const& key) static unsigned hash(regex::CacheKey<Parser> 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())));
} }
}; };

View File

@@ -277,7 +277,7 @@ private:
for (int i = 0; i < 128; i++) { for (int i = 0; i < 128; i++) {
hash ^= ptr_hash(node->m_children[i].ptr()); hash ^= ptr_hash(node->m_children[i].ptr());
} }
hash ^= int_hash(static_cast<u32>(node->m_is_terminal)); hash ^= u32_hash(static_cast<u32>(node->m_is_terminal));
return hash; return hash;
} }
static bool equals(NonnullRefPtr<Node> const& a, NonnullRefPtr<Node> const& b) static bool equals(NonnullRefPtr<Node> const& a, NonnullRefPtr<Node> const& b)

View File

@@ -9,22 +9,22 @@
#include <AK/HashFunctions.h> #include <AK/HashFunctions.h>
#include <AK/Types.h> #include <AK/Types.h>
TEST_CASE(int_hash) TEST_CASE(u32_hash)
{ {
static_assert(int_hash(42) == 3564735745u); static_assert(u32_hash(42) == 142593372u);
static_assert(int_hash(0) == 1177991625u); static_assert(u32_hash(0) == 0u);
} }
TEST_CASE(pair_int_hash) TEST_CASE(pair_int_hash)
{ {
static_assert(pair_int_hash(42, 17) == 339337046u); static_assert(pair_int_hash(42, 17) == 1110885963u);
static_assert(pair_int_hash(0, 0) == 954888656u); static_assert(pair_int_hash(0, 0) == 0u);
} }
TEST_CASE(u64_hash) TEST_CASE(u64_hash)
{ {
static_assert(u64_hash(42) == 2824066580u); static_assert(u64_hash(42) == 2386713036u);
static_assert(u64_hash(0) == 954888656u); static_assert(u64_hash(0) == 0u);
} }
TEST_CASE(ptr_hash) TEST_CASE(ptr_hash)
@@ -32,17 +32,17 @@ TEST_CASE(ptr_hash)
// These tests are not static_asserts because the values are // These tests are not static_asserts because the values are
// different and the goal is to bind the behavior. // different and the goal is to bind the behavior.
if constexpr (sizeof(FlatPtr) == 8) { if constexpr (sizeof(FlatPtr) == 8) {
EXPECT_EQ(ptr_hash(FlatPtr(42)), 2824066580u); EXPECT_EQ(ptr_hash(FlatPtr(42)), 2386713036u);
EXPECT_EQ(ptr_hash(FlatPtr(0)), 954888656u); EXPECT_EQ(ptr_hash(FlatPtr(0)), 0u);
EXPECT_EQ(ptr_hash(reinterpret_cast<void const*>(42)), 2824066580u); EXPECT_EQ(ptr_hash(reinterpret_cast<void const*>(42)), 2386713036u);
EXPECT_EQ(ptr_hash(reinterpret_cast<void const*>(0)), 954888656u); EXPECT_EQ(ptr_hash(reinterpret_cast<void const*>(0)), 0u);
} else { } else {
EXPECT_EQ(ptr_hash(FlatPtr(42)), 3564735745u); EXPECT_EQ(ptr_hash(FlatPtr(42)), 142593372u);
EXPECT_EQ(ptr_hash(FlatPtr(0)), 1177991625u); EXPECT_EQ(ptr_hash(FlatPtr(0)), 0u);
EXPECT_EQ(ptr_hash(reinterpret_cast<void const*>(42)), 3564735745u); EXPECT_EQ(ptr_hash(reinterpret_cast<void const*>(42)), 142593372u);
EXPECT_EQ(ptr_hash(reinterpret_cast<void const*>(0)), 1177991625u); EXPECT_EQ(ptr_hash(reinterpret_cast<void const*>(0)), 0u);
} }
} }

View File

@@ -87,10 +87,10 @@ Program (script) @4:1
│ │ ├─ BindingPattern (array) │ │ ├─ BindingPattern (array)
│ │ │ ├─ entry │ │ │ ├─ entry
│ │ │ │ └─ alias │ │ │ │ └─ alias
│ │ │ │ └─ Identifier "a" [variable:2] @28:9 │ │ │ │ └─ Identifier "a" [variable:1] @28:9
│ │ │ └─ entry │ │ │ └─ entry
│ │ │ └─ alias │ │ │ └─ alias
│ │ │ └─ Identifier "b" [variable:3] @28:9 │ │ │ └─ Identifier "b" [variable:4] @28:9
│ │ └─ ArrayExpression @28:18 │ │ └─ ArrayExpression @28:18
│ │ ├─ NumericLiteral 1 @28:19 │ │ ├─ NumericLiteral 1 @28:19
│ │ └─ NumericLiteral 2 @28:22 │ │ └─ NumericLiteral 2 @28:22
@@ -99,10 +99,10 @@ Program (script) @4:1
│ │ ├─ BindingPattern (object) │ │ ├─ BindingPattern (object)
│ │ │ ├─ entry │ │ │ ├─ entry
│ │ │ │ └─ name │ │ │ │ └─ name
│ │ │ │ └─ Identifier "c" [variable:1] @29:11 │ │ │ │ └─ Identifier "c" [variable:3] @29:11
│ │ │ └─ entry │ │ │ └─ entry
│ │ │ └─ name │ │ │ └─ name
│ │ │ └─ Identifier "d" [variable:4] @29:11 │ │ │ └─ Identifier "d" [variable:2] @29:11
│ │ └─ ObjectExpression @29:22 │ │ └─ ObjectExpression @29:22
│ │ ├─ ObjectProperty @29:22 │ │ ├─ ObjectProperty @29:22
│ │ │ ├─ StringLiteral "c" @29:22 │ │ │ ├─ StringLiteral "c" @29:22
@@ -129,10 +129,10 @@ Program (script) @4:1
│ │ ├─ BinaryExpression (+) @31:22 │ │ ├─ BinaryExpression (+) @31:22
│ │ │ ├─ BinaryExpression (+) @31:18 │ │ │ ├─ BinaryExpression (+) @31:18
│ │ │ │ ├─ BinaryExpression (+) @31:14 │ │ │ │ ├─ BinaryExpression (+) @31:14
│ │ │ │ │ ├─ Identifier "a" [variable:2] @31:12 │ │ │ │ │ ├─ Identifier "a" [variable:1] @31:12
│ │ │ │ │ └─ Identifier "b" [variable:3] @31:16 │ │ │ │ │ └─ Identifier "b" [variable:4] @31:16
│ │ │ │ └─ Identifier "c" [variable:1] @31:20 │ │ │ │ └─ Identifier "c" [variable:3] @31:20
│ │ │ └─ Identifier "d" [variable:4] @31:24 │ │ │ └─ Identifier "d" [variable:2] @31:24
│ │ └─ Identifier "e" [variable:0] @31:28 │ │ └─ Identifier "e" [variable:0] @31:28
│ └─ MemberExpression [computed] @31:33 │ └─ MemberExpression [computed] @31:33
│ ├─ Identifier "f" [variable:5] @31:32 │ ├─ Identifier "f" [variable:5] @31:32