perf: preallocate maps and slices in CountValues, UniqKeys, UniqValues, FilterKeys, FilterValues, FilterKeysErr, FilterValuesErr (#833)

* perf: preallocate maps and slices in CountValues, UniqKeys, UniqValues, FilterKeys, FilterValues, FilterKeysErr, FilterValuesErr

Add size hints to map and slice allocations to avoid repeated
grow-and-copy reallocations:
- CountValues: make(map[T]int, len(collection))
- UniqKeys/UniqValues: make([]K, 0, size)
- FilterKeys/FilterValues/FilterKeysErr/FilterValuesErr: make([]K, 0, len(in))

* oops
This commit is contained in:
Samuel Berthe
2026-03-06 00:35:45 +01:00
committed by GitHub
parent fa095e4b4f
commit b11e461ff4
4 changed files with 43 additions and 121 deletions
-72
View File
@@ -67,18 +67,6 @@ func BenchmarkValues(b *testing.B) {
} }
} }
func BenchmarkUniqValues(b *testing.B) {
for _, n := range coreLengths {
m1 := genMap(n)
m2 := genMap(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.UniqValues(m1, m2)
}
})
}
}
func BenchmarkValueOr(b *testing.B) { func BenchmarkValueOr(b *testing.B) {
m := genMap(100) m := genMap(100)
b.Run("hit", func(b *testing.B) { b.Run("hit", func(b *testing.B) {
@@ -289,28 +277,6 @@ func BenchmarkFilterMapToSlice(b *testing.B) {
} }
} }
func BenchmarkFilterKeys(b *testing.B) {
for _, n := range coreLengths {
m := genMap(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.FilterKeys(m, func(_ string, v int) bool { return v%2 == 0 })
}
})
}
}
func BenchmarkFilterValues(b *testing.B) {
for _, n := range coreLengths {
m := genMap(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.FilterValues(m, func(_ string, v int) bool { return v%2 == 0 })
}
})
}
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// find.go // find.go
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -738,18 +704,6 @@ func BenchmarkIntersectBy(b *testing.B) {
} }
} }
func BenchmarkDifference(b *testing.B) {
for _, n := range coreLengths {
a := genSliceInt(n)
c := genSliceInt(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = lo.Difference(a, c)
}
})
}
}
func BenchmarkUnion(b *testing.B) { func BenchmarkUnion(b *testing.B) {
for _, n := range coreLengths { for _, n := range coreLengths {
a := genSliceInt(n) a := genSliceInt(n)
@@ -1052,32 +1006,6 @@ func BenchmarkCoreToSlicePtr(b *testing.B) {
} }
} }
func BenchmarkFromSlicePtr(b *testing.B) {
for _, n := range coreLengths {
ptrs := lo.ToSlicePtr(genSliceInt(n))
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.FromSlicePtr(ptrs)
}
})
}
}
func BenchmarkFromSlicePtrOr(b *testing.B) {
for _, n := range coreLengths {
ptrs := lo.ToSlicePtr(genSliceInt(n))
// sprinkle nils
for j := 0; j < n/10; j++ {
ptrs[j*10] = nil
}
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.FromSlicePtrOr(ptrs, -1)
}
})
}
}
func BenchmarkToAnySlice(b *testing.B) { func BenchmarkToAnySlice(b *testing.B) {
for _, n := range coreLengths { for _, n := range coreLengths {
ints := genSliceInt(n) ints := genSliceInt(n)
+36 -42
View File
@@ -74,52 +74,46 @@ func BenchmarkMap(b *testing.B) {
}) })
} }
// @TODO: also apply to UniqValues.
func BenchmarkUniqKeys(b *testing.B) { func BenchmarkUniqKeys(b *testing.B) {
m := []map[int64]int64{ m := []map[int64]int64{
mapGenerator(100000), mapGenerator(1000),
mapGenerator(100000), mapGenerator(1000),
mapGenerator(100000), mapGenerator(1000),
} }
b.Run("lo.UniqKeys", func(b *testing.B) {
// allocate just in time + ordered
b.Run("lo.UniqKeys.jit-alloc", func(b *testing.B) {
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
seen := make(map[int64]struct{}) _ = lo.UniqKeys(m...)
result := make([]int64, 0) }
})
for i := range m { }
for k := range m[i] {
if _, exists := seen[k]; exists { func BenchmarkUniqValues(b *testing.B) {
continue m := []map[int64]int64{
} mapGenerator(1000),
seen[k] = struct{}{} mapGenerator(1000),
result = append(result, k) //nolint:staticcheck mapGenerator(1000),
} }
} b.Run("lo.UniqValues", func(b *testing.B) {
} for n := 0; n < b.N; n++ {
}) _ = lo.UniqValues(m...)
}
// preallocate + unordered })
b.Run("lo.UniqKeys.preallocate", func(b *testing.B) { }
for n := 0; n < b.N; n++ {
size := 0 func BenchmarkFilterKeys(b *testing.B) {
for i := range m { m := mapGenerator(1000)
size += len(m[i]) b.Run("lo.FilterKeys", func(b *testing.B) {
} for n := 0; n < b.N; n++ {
seen := make(map[int64]struct{}, size) _ = lo.FilterKeys(m, func(k, v int64) bool { return k%2 == 0 })
}
for i := range m { })
for k := range m[i] { }
seen[k] = struct{}{}
} func BenchmarkFilterValues(b *testing.B) {
} m := mapGenerator(1000)
b.Run("lo.FilterValues", func(b *testing.B) {
result := make([]int64, 0, len(seen)) for n := 0; n < b.N; n++ {
_ = lo.FilterValues(m, func(k, v int64) bool { return v%2 == 0 })
for k := range seen {
result = append(result, k) //nolint:staticcheck
}
} }
}) })
} }
+6 -6
View File
@@ -27,7 +27,7 @@ func UniqKeys[K comparable, V any](in ...map[K]V) []K {
} }
seen := make(map[K]struct{}, size) seen := make(map[K]struct{}, size)
result := make([]K, 0) result := make([]K, 0, size)
for i := range in { for i := range in {
for k := range in[i] { for k := range in[i] {
@@ -76,7 +76,7 @@ func UniqValues[K, V comparable](in ...map[K]V) []V {
} }
seen := make(map[V]struct{}, size) seen := make(map[V]struct{}, size)
result := make([]V, 0) result := make([]V, 0, size)
for i := range in { for i := range in {
for _, v := range in[i] { for _, v := range in[i] {
@@ -465,7 +465,7 @@ func FilterMapToSliceErr[K comparable, V, R any](in map[K]V, iteratee func(key K
// It is a mix of lo.Filter() and lo.Keys(). // It is a mix of lo.Filter() and lo.Keys().
// Play: https://go.dev/play/p/OFlKXlPrBAe // Play: https://go.dev/play/p/OFlKXlPrBAe
func FilterKeys[K comparable, V any](in map[K]V, predicate func(key K, value V) bool) []K { func FilterKeys[K comparable, V any](in map[K]V, predicate func(key K, value V) bool) []K {
result := make([]K, 0) result := make([]K, 0, len(in))
for k, v := range in { for k, v := range in {
if predicate(k, v) { if predicate(k, v) {
@@ -480,7 +480,7 @@ func FilterKeys[K comparable, V any](in map[K]V, predicate func(key K, value V)
// It is a mix of lo.Filter() and lo.Values(). // It is a mix of lo.Filter() and lo.Values().
// Play: https://go.dev/play/p/YVD5r_h-LX- // Play: https://go.dev/play/p/YVD5r_h-LX-
func FilterValues[K comparable, V any](in map[K]V, predicate func(key K, value V) bool) []V { func FilterValues[K comparable, V any](in map[K]V, predicate func(key K, value V) bool) []V {
result := make([]V, 0) result := make([]V, 0, len(in))
for k, v := range in { for k, v := range in {
if predicate(k, v) { if predicate(k, v) {
@@ -498,7 +498,7 @@ func FilterValues[K comparable, V any](in map[K]V, predicate func(key K, value V
// The order of the keys in the input map is not specified. // The order of the keys in the input map is not specified.
// Play: https://go.dev/play/p/j2gUQzCTu4t // Play: https://go.dev/play/p/j2gUQzCTu4t
func FilterKeysErr[K comparable, V any](in map[K]V, predicate func(key K, value V) (bool, error)) ([]K, error) { func FilterKeysErr[K comparable, V any](in map[K]V, predicate func(key K, value V) (bool, error)) ([]K, error) {
result := make([]K, 0) result := make([]K, 0, len(in))
for k, v := range in { for k, v := range in {
ok, err := predicate(k, v) ok, err := predicate(k, v)
@@ -520,7 +520,7 @@ func FilterKeysErr[K comparable, V any](in map[K]V, predicate func(key K, value
// The order of the keys in the input map is not specified. // The order of the keys in the input map is not specified.
// Play: https://go.dev/play/p/hKvHlqLzbdE // Play: https://go.dev/play/p/hKvHlqLzbdE
func FilterValuesErr[K comparable, V any](in map[K]V, predicate func(key K, value V) (bool, error)) ([]V, error) { func FilterValuesErr[K comparable, V any](in map[K]V, predicate func(key K, value V) (bool, error)) ([]V, error) {
result := make([]V, 0) result := make([]V, 0, len(in))
for k, v := range in { for k, v := range in {
ok, err := predicate(k, v) ok, err := predicate(k, v)
+1 -1
View File
@@ -1003,7 +1003,7 @@ func CountByErr[T any](collection []T, predicate func(item T) (bool, error)) (in
// CountValues counts the number of each element in the collection. // CountValues counts the number of each element in the collection.
// Play: https://go.dev/play/p/-p-PyLT4dfy // Play: https://go.dev/play/p/-p-PyLT4dfy
func CountValues[T comparable](collection []T) map[T]int { func CountValues[T comparable](collection []T) map[T]int {
result := make(map[T]int) result := make(map[T]int, len(collection))
for i := range collection { for i := range collection {
result[collection[i]]++ result[collection[i]]++