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) {
m := genMap(100)
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
// ---------------------------------------------------------------------------
@@ -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) {
for _, n := range coreLengths {
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) {
for _, n := range coreLengths {
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) {
m := []map[int64]int64{
mapGenerator(100000),
mapGenerator(100000),
mapGenerator(100000),
mapGenerator(1000),
mapGenerator(1000),
mapGenerator(1000),
}
// allocate just in time + ordered
b.Run("lo.UniqKeys.jit-alloc", func(b *testing.B) {
b.Run("lo.UniqKeys", func(b *testing.B) {
for n := 0; n < b.N; n++ {
seen := make(map[int64]struct{})
result := make([]int64, 0)
for i := range m {
for k := range m[i] {
if _, exists := seen[k]; exists {
continue
}
seen[k] = struct{}{}
result = append(result, k) //nolint:staticcheck
}
}
}
})
// preallocate + unordered
b.Run("lo.UniqKeys.preallocate", func(b *testing.B) {
for n := 0; n < b.N; n++ {
size := 0
for i := range m {
size += len(m[i])
}
seen := make(map[int64]struct{}, size)
for i := range m {
for k := range m[i] {
seen[k] = struct{}{}
}
}
result := make([]int64, 0, len(seen))
for k := range seen {
result = append(result, k) //nolint:staticcheck
}
_ = lo.UniqKeys(m...)
}
})
}
func BenchmarkUniqValues(b *testing.B) {
m := []map[int64]int64{
mapGenerator(1000),
mapGenerator(1000),
mapGenerator(1000),
}
b.Run("lo.UniqValues", func(b *testing.B) {
for n := 0; n < b.N; n++ {
_ = lo.UniqValues(m...)
}
})
}
func BenchmarkFilterKeys(b *testing.B) {
m := mapGenerator(1000)
b.Run("lo.FilterKeys", func(b *testing.B) {
for n := 0; n < b.N; n++ {
_ = lo.FilterKeys(m, func(k, v int64) bool { return k%2 == 0 })
}
})
}
func BenchmarkFilterValues(b *testing.B) {
m := mapGenerator(1000)
b.Run("lo.FilterValues", func(b *testing.B) {
for n := 0; n < b.N; n++ {
_ = lo.FilterValues(m, func(k, v int64) bool { return v%2 == 0 })
}
})
}
+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)
result := make([]K, 0)
result := make([]K, 0, size)
for i := range in {
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)
result := make([]V, 0)
result := make([]V, 0, size)
for i := range in {
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().
// 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 {
result := make([]K, 0)
result := make([]K, 0, len(in))
for k, v := range in {
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().
// 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 {
result := make([]V, 0)
result := make([]V, 0, len(in))
for k, v := range in {
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.
// 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) {
result := make([]K, 0)
result := make([]K, 0, len(in))
for k, v := range in {
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.
// 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) {
result := make([]V, 0)
result := make([]V, 0, len(in))
for k, v := range in {
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.
// Play: https://go.dev/play/p/-p-PyLT4dfy
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 {
result[collection[i]]++