In the previous article, I introduced swiss map
and a Go implementation by Dolthub. Readers unfamiliar with swiss map
should review that piece first.
With the upcoming release of Go 1.24, swiss map
will replace the existing map
implementation in the Go standard library. It maintains full API compatibility while delivering over 50% performance improvements in specific benchmark scenarios.
Currently, swiss map
is my most anticipated feature in Go 1.24. But does it truly live up to the hype? This article analyzes its core design through three lenses: compatibility, Extensible Hashing implementation, and remaining challenges.
Compatibility: Seamless Migration Support
One of swiss map
’s key design goals is backward compatibility with Go’s legacy map
. Conditional compilation flags and type conversions enable zero-code migration. For example, in export_swiss_test.go
, the newTestMapType
function directly converts legacy map
metadata into swiss map
’s type structure:
|
|
This design allows existing code to enable swiss map
via the experimental flag GOEXPERIMENT=swissmap
(now enabled by default in gotip builds like go1.24-3f4164f5
).
To revert to the legacy map implementation, use GOEXPERIMENT=noswissmap
.
Swiss Map’s Data Structure
Extendible Hashing: Efficient Incremental Scaling
Beyond compatibility improvements, swiss map
introduces Extensible Hashing to enable efficient incremental scaling. Unlike traditional hash tables, which require complete data migration during resizing, Extensible Hashing distributes scaling costs across multiple operations using multi-level directories and table splitting.
Directory and Table Hierarchy
The Map
struct in map.go
uses globalDepth
and directory
to manage hierarchy:
|
|
The directory size is 1 << globalDepth
, with each entry pointing to a table
. When a table reaches its capacity (maxTableCapacity
, default 1024), it triggers a split instead of global resizing.
Split Operation
A split creates two child tables (left
and right
) with increased localDepth
, redistributing data based on hash bits:
|
|
New tables allocate contiguous memory blocks via newarray
, preserving cache locality.
Data Redistribution: Hash Masking
During splits, hash values’ high-order bits (determined by localDepth
) dictate data placement:
|
|
- Mask Calculation:
localDepthMask
generates masks like:localDepth=1
→0x80000000
(32-bit) or0x8000000000000000
(64-bit)
Directory Expansion
When a split occurs at the global depth, the directory doubles in size:
|
|
Example: Directory Expansion
Initial state (globalDepth=1
):
|
|
After split (globalDepth=2
):
|
|
Key Advantages
- Locality: Only overloaded tables split
- Incremental Scaling: Directory grows on demand
- Cache Efficiency: Continuous memory allocation for table groups
Additional Optimizations
swiss map
optimizes small-element scenarios (≤8 elements) using a single group, minimizing performance penalties for small datasets.
Remaining Challenges
Despite significant improvements, several issues remain:
Concurrency Limitations
The current implementation uses a simple writing
flag for concurrent write detection:
|
|
This may cause race conditions in high-concurrency scenarios. Future versions may introduce finer-grained locking.
Memory Fragmentation
The group
structure (8 control bytes + 8 key-value slots) may waste memory for small types. For example, int32
keys with int8
values leave 3 bytes unused per slot.
Iterator Complexity
The Iter
implementation handles directory expansion and table splits, adding complexity:
|
|
Frequent resizing may impact iterator performance.
Additionally, numerous TODOs remain in the latest codebase (see the image below), and questions about their resolution remain unresolved before Go 1.24’s release.
Community discussions highlight performance variability, with some reports of regressions.
https://x.com/valyala/status/1879988053076504761
Performance Testing
Test code: github.com/hxzhouh/gomapbench
Environment: go version devel go1.24-3f4164f5 Mon Jan 20 09:25:11 2025 -0800 darwin/arm64
Average performance improvements hover around 20%, with some scenarios showing up to 50% gains. However, these results are machine-specific, and some users report performance regressions. The final evaluation awaits further optimization.
Conclusion
Go 1.24’s swiss map
delivers significant performance gains through compatibility design, Extendible Hashing, and optimized probing sequences. However, challenges remain in concurrency handling and memory efficiency. Developers should consider adopting it in performance-critical contexts while monitoring its evolving implementation.
References
- Tony Bai’s Analysis
- GitHub Issue #54766
- Extendible Hashing Explained
- Original Implementation Article
- Long Time Link
- If you find my blog helpful, please subscribe to me via RSS
- Or follow me on X
- If you have a Medium account, follow me there. My articles will be published there as soon as possible.