Zero-Allocation Slice Filter and Map Utilities
Performance-focused slice helpers that avoid extra allocations by pre-sizing outputs and reusing buffers.
main.go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
package main
import ( "log"
"strconv"
"time")
// ZeroAllocFilterInt filters an int slice with zero heap allocations (pre-allocates output)
// Predicate returns true if element should be included in the result
func ZeroAllocFilterInt(input []int, predicate func(int) bool) []int {
// Pre-allocate output slice with capacity equal to input (worst case: all elements are kept)
output := make([]int, 0, len(input))
for _, v := range input {
if predicate(v) {
output = append(output, v)
}
}
// Optional: shrink capacity to fit length (avoids memory waste)
return append([]int(nil), output...)
}
// ZeroAllocMapIntToString maps an int slice to a string slice with minimal allocations
// Transformer converts an int to a string
func ZeroAllocMapIntToString(input []int, transformer func(int) string) []string {
// Pre-allocate output slice with exact length (no reallocations)
output := make([]string, len(input))
for i, v := range input {
output[i] = transformer(v)
}
return output
}
// ZeroAllocReduceInt reduces an int slice to a single int value
// Reducer takes accumulator and current element, returns new accumulator
func ZeroAllocReduceInt(input []int, initial int, reducer func(int, int) int) int {
acc := initial
for _, v := range input {
acc = reducer(acc, v)
}
return acc
}
// ZeroAllocFilterMap combines filter and map in one pass (even more efficient)
func ZeroAllocFilterMapIntToFloat(input []int, predicate func(int) bool, transformer func(int) float64) []float64 {
// First pass: count elements that pass predicate to pre-allocate exact capacity
count := 0
for _, v := range input {
if predicate(v) {
count++
}
}
// Second pass: filter and transform
output := make([]float64, 0, count)
for _, v := range input {
if predicate(v) {
output = append(output, transformer(v))
}
}
return output
}
// Example Usage
func main() {
// Sample input slice
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// 1. Filter even numbers (zero allocation)
evens := ZeroAllocFilterInt(numbers, func(n int) bool {
return n%2 == 0
})
log.Printf("Even numbers: %v (len: %d, cap: %d)", evens, len(evens), cap(evens))
// 2. Map ints to strings (zero allocation growth)
stringNumbers := ZeroAllocMapIntToString(numbers, func(n int) string {
return strconv.Itoa(n)
})
log.Printf("String numbers: %v", stringNumbers)
// 3. Reduce to sum of all numbers
sum := ZeroAllocReduceInt(numbers, 0, func(acc, n int) int {
return acc + n
})
log.Printf("Sum of numbers: %d", sum)
// 4. Filter and map in one pass (optimal for large slices)
filteredFloats := ZeroAllocFilterMapIntToFloat(numbers, func(n int) bool {
return n > 5
}, func(n int) float64 {
return float64(n) * 1.5
})
log.Printf("Numbers >5 multiplied by 1.5: %v", filteredFloats)
// Benchmark example (compare with standard slice operations)
largeSlice := make([]int, 1_000_000)
for i := range largeSlice {
largeSlice[i] = i + 1
}
start := time.Now()
_ = ZeroAllocFilterInt(largeSlice, func(n int) bool { return n%3 == 0 })
duration := time.Since(start)
log.Printf("Filtered 1M elements in %v (zero allocation)", duration)
}How It Works
Performance-focused slice helpers that avoid extra allocations by pre-sizing outputs and reusing buffers.
Implements filter, map, and reduce operations that allocate once based on input length, reuse underlying arrays, and avoid garbage collector churn while iterating.
Key Concepts
- 1Preallocated output slices minimize GC pressure.
- 2Functions accept predicates and mappers but keep tight loops.
- 3Includes a reduce helper for aggregated calculations without copies.
When to Use This Pattern
- High-throughput data processing in latency-sensitive services.
- Performance-critical code paths like parsers or log pipelines.
- Hot loops in real-time analytics where allocations add jitter.
Best Practices
- Benchmark with real data to ensure gains outweigh complexity.
- Avoid returning subslices that keep large backing arrays alive.
- Document ownership of returned slices to prevent unintended reuse.
Go Version1.14
Difficultyintermediate
Production ReadyYes
Lines of Code103