Maps are faster in Go 1.24
Go 1.24 has arrived with many improvements, but one of the most interesting changes is the introduction of a Swiss Table-based map implementation. This update improves performance for map operations, making Go even more efficient.
In this blog post, we’ll explore what the Swiss Table implementation is, why it matters, and how it can impact your Go programs.
What Is the Swiss Table?
The Swiss Table is a hash table design originally developed by Google and implemented in languages like C++ (as part of Abseil). It’s known for its:
- Compactness: The Swiss Table uses a clever probing scheme and metadata to store keys and values in a highly space-efficient manner.
- Performance: It minimizes cache misses by storing metadata in contiguous memory regions, enabling fast lookups, insertions, and deletions.
Go 1.24 adapts this design for its built-in map
type, providing a seamless upgrade without requiring code changes.
Benefits of the Swiss Table Implementation
The Swiss Table brings several key advantages to Go’s map
type:
-
Faster Lookups: The new design reduces the number of cache misses during lookups, which is especially noticeable in workloads with frequent map reads.
-
Improved Memory Usage: By optimizing how keys and values are stored, the Swiss Table implementation makes better use of memory, reducing overhead for large maps.
-
Better Performance for High Load Factors: Maps in Go can now perform more efficiently even as they become densely populated, reducing the need for frequent resizing.
Measuring the Impact: Benchmarks
Let’s see the Swiss Table in action with some simple benchmarks. The following code compares map operations in Go 1.24 to earlier versions:
package main
import (
"fmt"
"time"
)
func main() {
// Create a large map
m := make(map[int]int, 1_000_000)
for i := 0; i < 1_000_000; i++ {
m[i] = i
}
// Measure lookup performance
start := time.Now()
for i := 0; i < 10_000_000; i++ {
_ = m[i%1_000_000]
}
fmt.Printf("Lookup time: %v\n", time.Since(start))
// Measure insertion performance
start = time.Now()
for i := 1_000_000; i < 2_000_000; i++ {
m[i] = i
}
fmt.Printf("Insertion time: %v\n", time.Since(start))
// Measure deletion performance
start = time.Now()
for i := 0; i < 1_000_000; i++ {
delete(m, i)
}
fmt.Printf("Deletion time: %v\n", time.Since(start))
}
Expected Results
- Pre-Go 1.24: Longer lookup and insertion times due to higher cache miss rates and less efficient memory management.
- Go 1.24: Faster operations across the board, with especially noticeable improvements for densely populated maps.
Actual Results
Go 1.23:
Lookup time: 318.447458ms
Insertion time: 103.009625ms
Deletion time: 36.222416ms
Go 1.24
Lookup time: 237.979625ms
Insertion time: 60.243833ms
Deletion time: 58.681917ms
Noticably faster for most things, but seems slower for deletes, why is that?
Deletes in Swiss maps can sometimes be slower compared to traditional hash maps. This is primarily due to the way Swiss tables manage their internal structure:
-
Metadata Management: Swiss tables store metadata in contiguous blocks to improve cache locality. When a key is deleted, the associated metadata must be updated.
-
Probing Impact: Swiss tables rely on a probing strategy to resolve collisions. Deleting an entry can create a “gap” in the probing sequence, which requires back-shifting other entries to maintain the search path integrity. This back-shifting process adds to the deletion time.
-
Memory Compaction: In certain cases, Swiss tables may perform additional operations to optimize memory usage after deletions. While this improves efficiency, it increases the cost of the delete operation.
Take a look at the design notes if you want to learn more.
Real-World Performance
Despite these factors, the slightly slower delete performance is typically offset by the significant improvements in lookups and insertions. If your application involves frequent deletions, consider benchmarking your workload with Go 1.24’s map implementation to ensure it meets your performance needs.
Compatibility and Transparency
One of the best parts of this upgrade is its backward compatibility. Developers don’t need to make any changes to their code to benefit from the new implementation. Simply upgrading to Go 1.24 will unlock these performance gains.
Practical Applications
The Swiss Table implementation is particularly beneficial for applications that rely heavily on map operations, such as:
- Database Indexing: Faster lookups and insertions can improve query performance.
- Caching Systems: Reduced overhead leads to better resource utilization and responsiveness.
- Data Analytics Pipelines: High-performance maps can handle large datasets more efficiently.
Wrapping Up
Go 1.24’s adoption of the Swiss Table map implementation should be faster for most use cases. With faster lookups, improved memory usage, and better handling of high load factors, this update reinforces Go’s position as a go-to language for high-performance applications.
Upgrade to Go 1.24 and give it a try. Be sure to check out the other byteSizeGo blogs about Go1.24 such as this one.