Home › Data structures & hashing › Hash functions
algorithm low

Fast hash functions

Problem Hash tables, sharding, deduplication and sketches need to turn keys into evenly spread numbers very quickly, including similar keys like sequential integers. This is the non-cryptographic hash used there.

Non-cryptographic hashes built from 64×64→128-bit multiplies and xor-shifts; pick by key size, trust level and whether the hash is persisted.

O(n) in input bytes; order of 1-5 ns for keys up to 16 bytes, tens of GB/s on long inputs
hashingwyhashxxhashsiphashhashdos
Overview

A hash-table hash must spread similar keys (sequential ints, shared prefixes) over all output bits, fast. Modern designs build on one primitive, the folded multiply lo(a×b) ^ hi(a×b) of a 128-bit product, which mixes 64 input bits in about 4 cycles. Short keys are read with overlapping loads; long inputs run independent lanes.

Key facts

Use when

  • Hash tables, sharding, deduplication, Bloom filters, sketches, non-adversarial checksums.
  • Database partitioning (Cassandra), compression frame checksums (zstd, LZ4) and language runtimes (see In production).

Avoid when

  • An attacker wants collisions or forgeries: use a MAC or crypto hash.
  • You need a stable on-disk format and the library promises none.

Deep dive

How it works

In: a key (integer or bytes) and an optional seed · Out: a 64-bit hash with well-mixed high and low bits · State: none (the seed is a parameter) · Cost: O(bytes); order of 1-5 ns for keys up to 16 bytes, tens of GB/s on long inputs

  1. Integer keys: a finalizer (bijective mix) suffices: xor-shift, multiply by an odd constant, repeat. Murmur3 fmix64 and splitmix64 avalanche all bits in 2 multiplies.
  2. Short strings: read the first and last 8 (or 4) bytes with possibly overlapping unaligned loads, so any length 4-16 costs 2 loads and no loop. Mix length in to separate "ab" from "ab\0".
  3. Long strings: process 16-64 B per step on independent accumulators (ILP), then fold them; XXH3 uses SIMD multiply-accumulate across 8 lanes.
  4. Seeding: xor a random seed into the initial state. This defeats precomputed collision sets for non-cryptographic hashes in practice but is not a proof; SipHash is a real keyed PRF.
  5. Reducing to a table size: power-of-two mask on the good bits (multiplicative hashes have strong high bits), or Lemire's ((h as u128 * n as u128) >> 64) for arbitrary n without division.

Code

use std::collections::HashMap;
use std::hash::{BuildHasherDefault, Hasher};

/// Murmur3 fmix64: bijective, full avalanche. Good integer hash / finalizer.
pub fn fmix64(mut x: u64) -> u64 {
    x ^= x >> 33; x = x.wrapping_mul(0xFF51_AFD7_ED55_8CCD);
    x ^= x >> 33; x = x.wrapping_mul(0xC4CE_B9FE_1A85_EC53);
    x ^ (x >> 33)
}

/// 64x64 -> 128 multiply, fold halves. Note: if either operand is 0, output is 0, so inputs are xored with constants.
#[inline] fn fold(a: u64, b: u64) -> u64 { let r = a as u128 * b as u128; r as u64 ^ (r >> 64) as u64 }
#[inline] fn r64(d: &[u8]) -> u64 { u64::from_le_bytes(d[..8].try_into().unwrap()) }
#[inline] fn r32(d: &[u8]) -> u64 { u32::from_le_bytes(d[..4].try_into().unwrap()) as u64 }

const P0: u64 = 0xA076_1D64_78BD_642F;
const P1: u64 = 0xE703_7ED1_A0B4_28DB;

/// Folded-multiply byte hash in the style of wyhash/rapidhash (illustrative: run SMHasher before trusting it).
pub fn hash_bytes(d: &[u8], seed: u64) -> u64 {
    let n = d.len();
    let mut s = seed ^ P0;
    let (a, b) = if n <= 16 {
        if n >= 8 { (r64(d), r64(&d[n - 8..])) } // overlapping loads: no byte loop
        else if n >= 4 { (r32(d), r32(&d[n - 4..])) }
        else if n > 0 { (((d[0] as u64) << 16) | ((d[n / 2] as u64) << 8) | d[n - 1] as u64, 0) }
        else { (0, 0) }
    } else {
        let mut i = 0;
        while i + 16 < n { // single lane; production code runs 3-4 lanes for ILP
            s = fold(r64(&d[i..]) ^ P1, r64(&d[i + 8..]) ^ s);
            i += 16;
        }
        (r64(&d[n - 16..]), r64(&d[n - 8..])) // last 16 bytes, may overlap the loop
    };
    fold(P1 ^ n as u64, fold(a ^ P1, b ^ s))
}

/// FxHash (rustc-hash 1.x): one rotate, xor, multiply per word. Very fast, weak low bits, no seed.
#[derive(Default)]
pub struct FxHasher(u64);
impl Hasher for FxHasher {
    fn write(&mut self, bytes: &[u8]) {
        for c in bytes.chunks(8) {
            let mut w = [0u8; 8];
            w[..c.len()].copy_from_slice(c);
            self.write_u64(u64::from_le_bytes(w));
        }
    }
    fn write_u64(&mut self, w: u64) { self.0 = (self.0.rotate_left(5) ^ w).wrapping_mul(0x517C_C1B7_2722_0A95); }
    fn finish(&self) -> u64 { self.0 }
}
pub type FxHashMap<K, V> = HashMap<K, V, BuildHasherDefault<FxHasher>>;

/// Map a hash to [0, n) without division (Lemire).
pub fn reduce(h: u64, n: u64) -> u64 { ((h as u128 * n as u128) >> 64) as u64 }

Complexity and constants

  • 64-bit multiply: 3-4 cycles latency, 1/cycle throughput on x86-64 and Apple/ARM big cores; mulx/umulh give the high half.
  • fmix64: dependency chain of 2 multiplies + 3 xor-shifts, order of 10 cycles latency; throughput higher when hashing independent keys.
  • Short-key hashing is latency-bound and often dominates hash-table lookup time for string keys; long-key hashing is bandwidth-bound.
  • SipHash-1-3 (Rust's default HashMap hasher) costs order of 3-10× a fast hash on short keys.

Efficiency

  • Cost: keys up to 16 bytes take order of 1-5 ns (two overlapping loads and a few 64-bit multiplies at 3-4 cycles latency); SipHash-1-3 takes order of 10-20 ns. Long inputs: XXH3 reaches tens of GB/s (order of), SipHash order of 1-2 GB/s. Bound: multiply latency for short keys, memory bandwidth for long ones.
  • Levers:
    • folded 128-bit multiply — mixes 64 input bits in about 4 cycles (wyhash family, foldhash) — trusted keys
    • overlapping loads for 4-16 byte keys — no byte loop, 2 loads for any length (as above) — short strings
    • several independent lanes — hides multiply latency (XXH3 uses SIMD lanes) — inputs over 16 bytes
    • hash once, derive k indexes by double hashing — 1 hash instead of k — Bloom filters, sketches (Bloom filter)
    • hardware CRC32C or AES rounds — about 1 cycle per 8 bytes (CRC32C), one AES round per 128 bits (aHash) — checksums, trusted hash tables
  • Naive → standard → state of the art:
    • byte-at-a-time hash (FNV) · about 1 byte per cycle (order of)
    • SipHash-1-3 · order of 10-20 ns on short keys, safe against HashDoS
    • foldhash, rapidhash, XXH3 · order of 1-5 ns on short keys, tens of GB/s on long ones

Implementation notes

SituationChoiceWhy
Keys from the network or users, in a hash tableSipHash-1-3 with random key (Rust default), or a seeded fast hash (foldhash, aHash) if you accept non-cryptographic strengthHashDoS: attacker-built collisions make a table O(n) per op
Trusted integer keysfmix64, splitmix64, foldhash, Fx1-2 multiplies
Trusted strings, in-memoryfoldhash, rapidhash, wyhash, XXH31-5 ns short keys
Large blobs, checksums, dedupXXH3-64/128SIMD, tens of GB/s, stable spec
On-disk or cross-machine (sharding, filters)A fixed, versioned algorithm (XXH3, xxh64, Murmur3)RandomState changes per process
Integrity against an adversaryBLAKE3, HMAC-SHA-256Non-crypto hashes are trivially forgeable
  • aHash uses AES-NI rounds (aesenc) when available; one AES round mixes 128 bits in about 4 cycles.
  • Seed per table or per process from the OS RNG; keep it out of logs and error messages.
  • For HashMap<u64, _> with sequential keys, identity hashing looks fast in microbenchmarks and collapses in Swiss tables, which need good top and bottom bits.

Pitfalls

  • Hashing structs field by field through Hasher::write with tiny calls: per-call overhead can exceed the mixing cost. Implement Hash to write one packed u64/u128 when possible.
  • Fx/multiplicative hashes have weak low bits; masking with & (n-1) on a table that indexes by low bits can cluster. Use the high bits or add a finalizer.
  • Assuming XXH64 values equal XXH3 values, or that a crate's output is stable across major versions.
  • Folded multiply "blinding": if input ^ constant == 0 the product is 0 and earlier state is lost. Real designs keep the risk at 2^-64 per block and accept it for non-adversarial use.
  • hash % n with power-of-two n keeps only the low bits; with a weak hash that is a collision factory.

Variants and alternatives

SipHash-2-4 / 1-3keyed PRF, the safe default for untrusted keys.
XXH3 (xxHash 0.8)64/128-bit, SIMD, stable output; the standard for checksums.
wyhash / rapidhashfolded multiply; rapidhash is a faster, higher-quality successor of wyhash.
foldhashhashbrown's default hasher since 0.15; seeded, very fast for small keys.
aHashAES-NI based; was hashbrown's default before foldhash.
Murmur3, CityHash, FarmHasholder; still common in persisted formats (Cassandra partitioner uses Murmur3).
CRC32C (crc32 instruction)hardware, ~1 cycle/8 B, but linear, so poor for hash tables without a finalizer.

In production

  • Languages and runtimes: Rust std (SipHash-1-3), hashbrown (foldhash), rustc (FxHash), Go runtime (AES-based hash on amd64/arm64).
  • Databases and storage: Cassandra (Murmur3 partitioner), RocksDB (xxHash for checksums), Abseil absl::Hash (wyhash family).
  • Compression and data formats: xxHash in zstd and LZ4 frame checksums.
  • Networking: SipHash was designed for hash-table keys from the network (HashDoS; Crosby and Wallach 2003).

Implementations

namelangrepoinstallverdictlicencehealthas_ofnotes
foldhashRustorlp/foldhashcargo add foldhash————hashbrown default; seeded, fast for small keys
rapidhashRusthoxxep/rapidhashcargo add rapidhash————wyhash successor, stable output
rustc-hashRustrust-lang/rustc-hashcargo add rustc-hash————FxHash; trusted keys only
xxhash-rustRustDoumanAsh/xxhash-rustcargo add xxhash-rust --features xxh3————XXH32, XXH64, XXH3
xxHashCCyan4973/xxHashbrew install xxhash————reference XXH3, SIMD, stable spec

Sources

  • Jean-Philippe Aumasson, Daniel J. Bernstein, SipHash: a fast short-input PRF, INDOCRYPT 2012. paper · pdf
  • Scott A. Crosby, Dan S. Wallach, Denial of Service via Algorithmic Complexity Attacks, USENIX Security 2003. pdf
  • Yann Collet, xxHash specification and benchmarks. spec · code
  • Daniel Lemire, Fast Random Integer Generation in an Interval, ACM TOMACS, 2019. paper · arXiv
  • Austin Appleby, SMHasher test suite (and Reini Urban's extended SMHasher). code
  • Wang Yi, wyhash. code

Used by

audio fingerprinting · plannedSpectral-peak landmark hashes to identify recordings robustly to noise
context modeling · plannedAdaptive context selection and prediction feeding an entropy coder (MA trees, context mixing)
Count-min sketchEngineers must find the most frequent items (hot keys, top talkers, abusive clients) in a huge stream without storing a counter per distinct item. It also feeds cache admission decisions.Coverage-guided fuzzingAnyone shipping code that parses untrusted bytes (codecs, protocol stacks, interpreters, firmware) must find crashing inputs nobody thought to write. Hand-written test cases and random bytes both miss deep paths.
crush placement · plannedCRUSH: deterministic, hierarchy-aware pseudo-random placement of objects on devices
Cuckoo hashingRead-heavy tables such as switch lookup tables and caches need a hash table whose lookups have predictable worst-case latency and high memory utilisation.
data shuffle · plannedHash/range repartitioning of intermediate data between workers (exchange operator)
Device fingerprintingA signup, login or checkout service must recognise that a request comes from a device it has seen before, even when cookies are cleared or accounts differ, to link accounts, catch takeovers and throttle mass signups.
feature flag evaluation · plannedLocal evaluation of targeting rules and percentage rollouts from a flag snapshot
function fingerprinting · plannedIdentify library functions in binaries via byte signatures and hashes with wildcards (FLIRT-style)
HyperLogLogCounting unique users, IPs or keys across a huge stream is too memory hungry if exact. This gives approximate distinct counts in small memory and can combine counts across shards or time ranges.LZ77General data such as text, logs, binaries and serialized records contains repeated byte strings that waste space. This is the basic way to exploit repeats with very fast decompression.Minimal perfect hashingA read-only dictionary with a fixed set of keys (URL ids, keyword tables, k-mer tables) pays for a full hash table including stored keys. This maps each key to its own slot with minimal memory.
minimizer kmer index · plannedIndex sequences by window minimizers of k-mers for fast seeding and chaining (minimap2)
Near-duplicate detection with MinHash LSHTeams holding a huge collection of documents, web pages or sequences must find and remove near-identical items, which repeat content and bias what is learned or indexed, without comparing every pair.Partitioned commit logA high-volume event stream outgrows one machine, yet readers need each entity's events in order and no acknowledged event may vanish when a machine fails.
persistent data structures hamt · plannedHash array mapped tries and persistent vectors with structural sharing for immutable state
Presolve reductionsOptimization models generated by software are full of redundant rows, fixed variables and loose bounds that slow linear and integer solvers. This shrinks and tightens the model before the solver starts.Prompt cachingAgent loops and apps resend the same long system prompt, tool definitions or documents on every LLM call, paying latency and cost each time. This avoids reprocessing the unchanged part of a prompt.
set associative flash cache · plannedHash keys to small flash sets/pages for tiny objects with low DRAM index cost (Kangaroo style)
set reconciliation · plannedHave/want negotiation and IBLT-style sync of two sets
shuffle sharding · plannedAssign each tenant a random subset of nodes to isolate failures
Spatial hashingSimulations of thousands to millions of similar-sized moving objects (particles, atoms, fluid elements, agents) must find which ones are near each other at every step without comparing all pairs.

Systems (6):

Alternatives and related