Go cache performance: why sharded RWMutex beats sync.Map in benchmarks
A detailed benchmark of six concurrent map designs in Go reveals that lock sharding with RWMutex offers superior performance, challenging the default use of sync.Map for high-contention caches. The…
A detailed benchmark of six concurrent map designs in Go reveals that lock sharding with RWMutex offers superior performance, challenging the default use of
sync.Mapfor high-contention caches.
The Answer Up Front
For Go developers building high-throughput, read-heavy services, a sharded map using sync.RWMutex is the highest-performing choice for a concurrent in-memory cache, according to this benchmark. You should skip this pattern if your application has low concurrency needs or if you value implementation simplicity far more than raw performance, where a single RWMutex might suffice. The bottom line: sync.Map is not a universal solution for concurrent maps. For high-contention scenarios, manually sharding your locks provides significant and scalable performance gains that the built-in type cannot match.
Methodology
This review analyzes the benchmark results and methodology presented by developer strebkov.dev in a blog post from June 2024. The analysis is based entirely on the author's published article and the accompanying open-source benchmark code available on GitHub. We did not independently execute the benchmarks, but the public availability of the source code makes the results verifiable.
- Tool/Technique: A comparison of six different designs for a concurrent map in Go.
- Source Signal: The blog post "Shard your locks: benchmarking 6 Go cache designs" at
https.strebkov.dev/posts/shard-your-locks/. - Date Observed: June 27, 2026.
- What's Covered: The performance of six map implementations under three different read/write ratios (99% read, 90% read, 50% read). The benchmark measures operations per second as the number of available CPU cores increases from 1 to 16.
- What's Not Covered: This review does not cover the memory overhead of each design, performance with non-uniform key distributions (e.g., hot keys), or behavior under significant garbage collection pressure. This is a v0 review; independent benchmarks are pending.
What It Does
The author sets out to find the most performant way to implement a concurrent cache in Go. Accessing a standard Go map from multiple goroutines without synchronization is unsafe and causes race conditions. The post benchmarks six common patterns for providing that safety.
The baseline: single global locks
The simplest approach is to protect a standard Go map with a single lock. The author tests two versions:
sync.Mutex: A single lock for both reading and writing. Any operation blocks all others.sync.RWMutex: A read-write lock. It allows multiple concurrent readers but only a single writer (which blocks all readers).
As expected, these approaches become a bottleneck under contention because all goroutines must compete for the same lock.
The built-in: sync.Map
Go's standard library provides sync.Map, a concurrent map implementation optimized for specific scenarios. It avoids lock contention by design, but as the benchmarks show, its performance characteristics are not universally superior. The author notes it is primarily optimized for cases where keys are written once and read many times, or for disjoint sets of goroutines accessing different keys.
The contender: sharded locks
Lock sharding is a technique to reduce lock contention. Instead of one big map with one lock, you create an array of smaller map shards, each with its own lock. A key is hashed to determine which shard (and which lock) it belongs to. This distributes the lock contention across many locks, allowing more concurrent operations. The author tests three sharded implementations, using sync.Mutex, sync.RWMutex, and sync.Map for each shard.
What's Interesting / What's Not
The most interesting result is the clear and decisive victory of the sharded sync.RWMutex implementation. Across all tested read/write ratios, it outperformed every other method. Crucially, its performance scaled almost linearly as the number of CPU cores increased. While the single-lock implementations flatlined and sync.Map showed modest gains, the sharded RWMutex design effectively used the additional cores.
Equally interesting is the underwhelming performance of the standard sync.Map. Many developers reach for it as the default "thread-safe map," but this benchmark shows it can be significantly slower than a sharded RWMutex in read-heavy scenarios. This isn't a flaw in sync.Map; it's a reminder that it's a specialized tool, optimized for append-only or disjoint access patterns, not for a highly contended cache of existing keys. The author's benchmark workload (updating existing keys) is a poor fit for sync.Map's internal design, a detail many developers may overlook.
The benchmark itself is well-constructed, providing the source code and testing across multiple core counts and read/write mixes. This is a high-quality, reproducible piece of engineering analysis, not just a theoretical discussion.
Pricing
This is a design pattern, not a product. All implementations use Go's standard library primitives. The cost is zero in licensing fees but non-zero in developer time to implement and maintain the sharded map structure correctly.
Pricing snapshot taken June 27, 2026.
Verdict
For Go applications requiring a high-performance, concurrent in-memory cache with a high read-to-write ratio, the evidence is clear: implement a sharded map using sync.RWMutex. The small increase in code complexity pays significant dividends in performance and scalability, especially on multi-core systems. The built-in sync.Map should not be the default choice for this use case; its performance was lackluster in this comparison. While it may excel in other scenarios (like append-mostly maps), it must be benchmarked against your specific workload before being used in a performance-critical path.
What We'd Test Next
A v2 of this analysis would expand the test suite. First, we would measure the memory overhead of each approach, as the sharded designs inherently use more memory for the slice headers and locks. Second, we would test with a non-uniform key distribution (e.g., Zipfian) to see how the sharding strategy holds up when certain shards become "hot." Finally, we would benchmark these hand-rolled implementations against popular open-source Go cache libraries like Ristretto or BigCache to see how they compare on both performance and features.
The investor read
This benchmark is a signal of engineering maturity. For investors evaluating Go-based startups, the choice of fundamental data structures can have a material impact on infrastructure costs and scalability. A team that understands and benchmarks trade-offs like sync.Map versus a sharded RWMutex demonstrates a capacity for capital-efficient engineering. They are less likely to solve performance problems by prematurely scaling hardware. This analysis also suggests a small but persistent market for high-performance Go libraries that abstract away these complexities, offering well-designed and pre-benchmarked components for teams without the time or expertise to build them from scratch.
Every claim ties to a primary source. See our methodology.