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.
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 projectsGo 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 hashPerformance 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/sThe 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 aesConclusion
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
BirdNest Tech Talk
Author of the rpcx microservice framework, original book author, and chair of Baidu's Go CMC committee.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
