Fundamentals 17 min read

Why Go’s maphash Beats Traditional Hashes: Deep Dive and Benchmarks

The article explains hash algorithm fundamentals, compares common hashes, presents extensive Go benchmark results across multiple data sizes, highlights the superior performance of Go's maphash (memhash) implementation, and shows how to access it via linkname and assembly details.

BirdNest Tech Talk
BirdNest Tech Talk
BirdNest Tech Talk
Why Go’s maphash Beats Traditional Hashes: Deep Dive and Benchmarks

Hash algorithm basics

Hash algorithms map arbitrary‑length input to a fixed‑length output. Core properties are one‑wayness, avalanche effect, and low collision probability. Typical uses include data integrity verification, password storage, hash tables, digital signatures, deduplication, and load balancing.

Common hash algorithms

Algorithm   Output Length   Speed   Security   Typical Use   Pros   Cons   Recommendation
MD5         128 bits        Very fast   Low   File checksum, quick compare   Simple, fast   Broken, collisions   Non‑security checks only
SHA1        160 bits        Fast   Medium‑low   Git, file checksum   Safer than MD5, moderate speed   Collisions proven   Migrate to SHA‑2
SHA256      256 bits        Medium   High   Digital signatures, blockchain   High security, widely adopted   Slower   Use for security‑critical cases
SHA512      512 bits        Medium   Very high   High‑security scenarios   Extremely secure   High CPU/memory cost   Use for top‑secret data
BCrypt      184 bits        Slow   Very high   Password storage   Built‑in salt, rainbow‑table resistant   Slow, resource heavy   Preferred for password hashing
PBKDF2      Variable        Configurable   High   Key derivation, password storage   Adjustable iterations   Resource intensive   Good for configurable security
HMAC        Depends on hash   Medium   High   Message authentication, API security   Provides integrity & authenticity   Requires key management   Use for API auth
Argon2      Variable        Configurable   Extremely high   Password hashing   Tunable memory hardness, modern design   Complex implementation   Ideal for new password storage projects

Go standard library hash packages

Algorithm   Package Path               Output Length   Performance   Use Case   Example Call   Characteristics
MD5         crypto/md5                16 bytes       Very fast   Non‑secure checksum   md5.Sum(data)   Simple, insecure
SHA1        crypto/sha1               20 bytes       Fast        Data verification   sha1.Sum(data)   Safer than MD5 but discouraged
SHA256      crypto/sha256             32 bytes       Medium      Encryption scenarios   sha256.Sum256(data)   Secure, common
SHA512      crypto/sha512             64 bytes       Medium      High‑security scenarios   sha512.Sum512(data)   Highest security in std lib
Adler32     hash/adler32               4 bytes       Extremely fast   Quick integrity check   adler32.Checksum(data)   Fast, low collision resistance
CRC32       hash/crc32                4 bytes       Extremely fast   Data verification   crc32.ChecksumIEEE(data)   Fast, low collision resistance
FNV32       hash/fnv                  4 bytes       Extremely fast   Hash tables   fnv.New32().Sum(data)   Good for short strings
FNV64       hash/fnv                  8 bytes       Extremely fast   Hash tables   fnv.New64().Sum(data)   64‑bit variant
BLAKE2b     golang.org/x/crypto/blake2b Variable (max 64 bytes)   Fast   Encryption   blake2b.New256(nil)   Modern, fast, includes BLAKE2s
BCrypt      golang.org/x/crypto/bcrypt 24 bytes       Slow        Password storage   bcrypt.GenerateFromPassword()   Dedicated password hash

Performance testing methodology

A generic benchmark iterates over payload sizes 32, 64, 128, 256, 512, 1024 bytes, fills each with random data, and measures every hash implementation using Go's testing.B. The benchmark runs on a Linux/AMD64 server (Intel Xeon Gold 6271C, 96 cores, 5.10 kernel, 796 GB RAM) to expose CPU‑level performance.

var n int64
var testBytes []byte
func BenchmarkHash(b *testing.B) {
    sizes := []int64{32, 64, 128, 256, 512, 1024}
    for _, n = range sizes {
        testBytes = make([]byte, n)
        readN, err := rand.Read(testBytes)
        if readN != int(n) { panic(fmt.Sprintf("expect %d but got %d", n, readN)) }
        if err != nil { panic(err) }
        b.Run(fmt.Sprintf("Sha1-%d", n), benchmarkSha1)
        b.Run(fmt.Sprintf("Sha256-%d", n), BenchmarkSha256)
        b.Run(fmt.Sprintf("Sha256SIMD-%d", n), BenchmarkSha256SIMD)
        b.Run(fmt.Sprintf("Sha512-%d", n), BenchmarkSha512)
        b.Run(fmt.Sprintf("MD5-%d", n), BenchmarkMD5)
        b.Run(fmt.Sprintf("Fnv-%d", n), BenchmarkFnv)
        b.Run(fmt.Sprintf("Adler32-%d", n), BenchmarkAdler32)
        b.Run(fmt.Sprintf("Crc32-%d", n), BenchmarkCrc32)
        b.Run(fmt.Sprintf("CityHash-%d", n), BenchmarkCityhash)
        b.Run(fmt.Sprintf("FarmHash-%d", n), BenchmarkFarmhash)
        b.Run(fmt.Sprintf("Farmhash_dgryski-%d", n), BenchmarkFarmhash_dgryski)
        b.Run(fmt.Sprintf("Murmur3-%d", n), BenchmarkMurmur3)
        b.Run(fmt.Sprintf("Highwayhash-%d", n), BenchmarkHighwayhash)
        b.Run(fmt.Sprintf("XXHash64-%d", n), BenchmarkXXHash64)
        b.Run(fmt.Sprintf("XXHash64_ASM-%d", n), BenchmarkXXHash64_ASM)
        b.Run(fmt.Sprintf("MapHash64-%d", n), BenchmarkMapHash64)
        b.Run(fmt.Sprintf("StdMapHash64-%d", n), BenchmarkStdMapHash64)
        b.Run(fmt.Sprintf("ChibiHash64-%d", n), BenchmarkChibiHash)
        b.Run(fmt.Sprintf("Blake2b-%d", n), BenchmarkBlake2b)
        fmt.Println()
    }
}

Benchmark results (1024‑byte payload)

BenchmarkHash/Sha1-1024-96               797005   1471 ns/op   695.96 MB/s
BenchmarkHash/Sha256-1024-96            377248   3172 ns/op   322.82 MB/s
BenchmarkHash/Sha256SIMD-1024-96        378414   3176 ns/op   322.46 MB/s
BenchmarkHash/Sha512-1024-96            507495   2353 ns/op   435.16 MB/s
BenchmarkHash/MD5-1024-96               725233   1648 ns/op   621.33 MB/s
BenchmarkHash/Fnv-1024-96               911083   1312 ns/op   780.48 MB/s
BenchmarkHash/Adler32-1024-96          2734160    434.1 ns/op 2359.04 MB/s
BenchmarkHash/Crc32-1024-96           15229868     78.35 ns/op 13069.26 MB/s
BenchmarkHash/CityHash-1024-96         1774196    676.4 ns/op 1513.94 MB/s
BenchmarkHash/FarmHash-1024-96        2465469    487.2 ns/op 2101.90 MB/s
BenchmarkHash/Farmhash_dgryski-1024-96 10912843    109.9 ns/op 9316.16 MB/s
BenchmarkHash/Murmur3-1024-96         6018717    199.2 ns/op 5141.23 MB/s
BenchmarkHash/Highwayhash-1024-96      9591049    125.0 ns/op 8188.84 MB/s
BenchmarkHash/XXHash64-1024-96        11106692    108.0 ns/op 9477.11 MB/s
BenchmarkHash/XXHash64_ASM-1024-96    12232465     98.09 ns/op 10439.81 MB/s
BenchmarkHash/MapHash64-1024-96       25519408     47.02 ns/op 21777.23 MB/s
BenchmarkHash/StdMapHash64-1024-96     9233738    130.2 ns/op 7864.52 MB/s
BenchmarkHash/ChibiHash64-1024-96      2550945    470.3 ns/op 2177.50 MB/s
BenchmarkHash/Blake2b-1024-96           952615   1258 ns/op   814.09 MB/s

The MapHash implementation (exposed as runtime.memhash) leads with 47.02 ns/op and a throughput of 21 777 MB/s, making it the fastest hash for the tested payload.

Understanding Go’s maphash (runtime·memhash)

In the Go runtime the hash used for map keys is called memhash. It can be accessed from user code via the go:linkname hack:

//go:noescape
//go:linkname memhash runtime.memhash
func memhash(p unsafe.Pointer, h, s uintptr) uintptr

type stringStruct struct {
    str unsafe.Pointer
    len int
}

func MemHash(data []byte) uint64 {
    ss := (*stringStruct)(unsafe.Pointer(&data))
    return uint64(memhash(ss.str, 0, uintptr(ss.len)))
}

The implementation lives in src/runtime/alg.go and ultimately in assembly files such as src/runtime/asm_amd64.s. When the CPU supports AES‑NI, the hash uses AES instructions; otherwise it falls back to a pure‑memory algorithm.

TEXT runtime·memhash<ABIInternal>(SB),NOSPLIT,$0-32
    // AX = ptr to data
    // BX = seed
    // CX = size
    CMPB runtime·useAeshash(SB), $0
    JEQ noaes
    JMP aeshashbody<>(SB)
noaes:
    JMP runtime·memhashFallback<ABIInternal>(SB)

Checking CPU AES support

Linux: cat /proc/cpuinfo | grep -m 1 -i aes macOS:

sysctl -a | grep aes
Linux AES check
Linux AES check
macOS AES check
macOS AES check

Conclusion

Go’s internal memhash (exposed as MapHash) outperforms conventional hash functions for raw speed because it leverages AES‑NI when available. The benchmark demonstrates a clear performance hierarchy, and developers should benchmark hash choices against their specific workloads, balancing speed against security requirements.

References

hash‑bench: https://github.com/smallnest/hash-bench

PerformanceGobenchmarkHashcryptographymaphash
BirdNest Tech Talk
Written by

BirdNest Tech Talk

Author of the rpcx microservice framework, original book author, and chair of Baidu's Go CMC committee.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.