Skip to content

Commit 9fc3500

Browse files
Fix: Mix integer key hashes with SplitMix64 (#773)
`std::hash` is the identity for integers on libstdc++ and libc++. Our open-addressing tables mask that hash into a power-of-2 slot count and probe linearly until an empty slot, so consecutive keys land in adjacent slots and coalesce into a single contiguous run. Insertions and lookups both scan that run end to end, making the whole build quadratic. Closes #770. Co-Authored-By: Chakshu Dhannawat <65147507+chakshu-dhannawat@users.noreply.github.com> Co-Authored-By: Ash Vardanian <1983160+ashvardanian@users.noreply.github.com>
1 parent 749d03b commit 9fc3500

1 file changed

Lines changed: 25 additions & 0 deletions

File tree

include/usearch/index.hpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1277,6 +1277,31 @@ template <typename element_at> struct hash_gt {
12771277
std::size_t operator()(element_at const& element) const noexcept { return std::hash<element_at>{}(element); }
12781278
};
12791279

1280+
/**
1281+
* @brief SplitMix64 finalizer, used to scatter integer keys before masking.
1282+
*
1283+
* On libstdc++ and libc++ `std::hash` is the identity for integers. Our open-addressing
1284+
* tables mask that hash into a power-of-2 slot count and probe linearly until an @b empty
1285+
* slot, so consecutive keys land in adjacent slots and merge into one contiguous run.
1286+
* Both insertions and lookups then scan that run end-to-end, which is quadratic overall:
1287+
* a dense ascending key range collapses insertion throughput by three orders of magnitude.
1288+
* Mixing costs ~20ns per key and is dwarfed by the graph traversal in `add`.
1289+
*/
1290+
template <> struct hash_gt<std::uint64_t> {
1291+
std::size_t operator()(std::uint64_t const& element) const noexcept {
1292+
std::uint64_t x = element;
1293+
x = (x ^ (x >> 30u)) * 0xBF58476D1CE4E5B9ULL;
1294+
x = (x ^ (x >> 27u)) * 0x94D049BB133111EBULL;
1295+
return static_cast<std::size_t>(x ^ (x >> 31u));
1296+
}
1297+
};
1298+
1299+
template <> struct hash_gt<std::int64_t> {
1300+
std::size_t operator()(std::int64_t const& element) const noexcept {
1301+
return hash_gt<std::uint64_t>{}(static_cast<std::uint64_t>(element));
1302+
}
1303+
};
1304+
12801305
template <> struct hash_gt<uint40_t> {
12811306
std::size_t operator()(uint40_t const& element) const noexcept { return std::hash<std::size_t>{}(element); }
12821307
};

0 commit comments

Comments
 (0)