Key findings
- Slice with preallocation is the fastest option across all operations (Enqueue, Dequeue, Push, Pop) on both amd64 and arm64.
- Preallocating always helps. Passing a
sizeHintto the constructor eliminates growth allocations in the steady state and improves throughput in every case. - Rankings are consistent across Go 1.18 and Go 1.24, and across amd64 and arm64 architectures. The relative ordering of implementations does not change with Go version or CPU architecture.
- The overall ranking from fastest to slowest: Slice > ListWithInternalPool > List > ListWithSyncPool
Benchmark data
ARM64 (Apple M3 Pro, Go 1.24.1)
AMD64 (Intel Core i7-4980HQ @ 2.80GHz, Go 1.18.0)
Ranking totals sum the per-operation rank across all four operations. A lower total means better overall throughput.
>> in the per-operation rankings below indicates a large performance drop.Per-operation ranking
Performance tips
Always provide a size hint
Passing asizeHint avoids repeated backing-array growth and is the single highest-impact tuning you can do. Even a rough estimate is better than 0.
Prefer slice-based implementations
Unless you have profiled a specific allocation bottleneck, start with the slice-backed implementation. On both amd64 and arm64, across Go 1.18 and Go 1.24, it consistently outperforms all linked-list variants when preallocated.Avoid sync.Pool-based implementations
ListWithSyncPool (ListSP) ranks last in nearly every benchmark. The sync.Pool GC interaction and coordination overhead outweighs any allocation savings for typical queue and stack workloads.
Set and OrderedMap have no alternatives to tune
Only one implementation exists for each:- Set:
set.NewBasicMap— map-backed, no alternative implementations. - OrderedMap:
orderedmap.NewSlice— slice-backed, no alternative implementations.