mirror of
https://github.com/samber/lo.git
synced 2026-04-22 15:37:14 +08:00
perf: optimize it.ContainsBy, EveryBy, SomeBy, NoneBy to avoid unnecessary allocations (#812)
Replace intermediate iterator chains with direct loops and early returns.
The direct loops with early return eliminate intermediate iterator
creation from Filter(), Reject(), IsNotEmpty() and IsEmpty() functions.
Benchmark results (benchstat):
│ old.txt │ new.txt │
│ sec/op │ sec/op vs base │
ItContainsBy/ints_10-4 807.2n ± 54% 151.0n ± 6% -81.29% (p=0.000 n=8)
ItContainsBy/ints_100-4 2.447µ ± 14% 1.531µ ± 11% -37.42% (p=0.000 n=8)
ItContainsBy/ints_1000-4 17.67µ ± 4% 13.85µ ± 4% -21.64% (p=0.000 n=8)
ItEveryBy/ints_10-4 1022.5n ± 63% 208.0n ± 34% -79.66% (p=0.000 n=8)
ItEveryBy/ints_100-4 5.640µ ± 36% 1.542µ ± 9% -72.67% (p=0.000 n=8)
ItEveryBy/ints_1000-4 52.22µ ± 54% 19.29µ ± 16% -63.06% (p=0.000 n=8)
ItSomeBy/ints_10-4 2227.5n ± 43% 187.1n ± 14% -91.60% (p=0.000 n=8)
ItSomeBy/ints_100-4 4.611µ ± 22% 1.691µ ± 10% -63.32% (p=0.000 n=8)
ItSomeBy/ints_1000-4 39.41µ ± 27% 22.61µ ± 24% -42.63% (p=0.000 n=8)
ItNoneBy/ints_10-4 1657.5n ± 34% 196.3n ± 21% -88.15% (p=0.000 n=8)
ItNoneBy/ints_100-4 4.503µ ± 20% 1.743µ ± 12% -61.30% (p=0.000 n=8)
ItNoneBy/ints_1000-4 29.39µ ± 29% 16.66µ ± 15% -43.32% (p=0.000 n=8)
geomean 5.591µ 1.747µ -68.76%
│ old.txt │ new.txt │
│ B/op │ B/op vs base │
ItContainsBy/ints_10-4 200.0 ± 0% 0.0 ± 0% -100.00% (p=0.000 n=8)
ItContainsBy/ints_100-4 200.0 ± 0% 0.0 ± 0% -100.00% (p=0.000 n=8)
ItContainsBy/ints_1000-4 200.0 ± 0% 0.0 ± 0% -100.00% (p=0.000 n=8)
ItEveryBy/ints_10-4 184.0 ± 0% 0.0 ± 0% -100.00% (p=0.000 n=8)
ItEveryBy/ints_100-4 184.0 ± 0% 0.0 ± 0% -100.00% (p=0.000 n=8)
ItEveryBy/ints_1000-4 184.0 ± 0% 0.0 ± 0% -100.00% (p=0.000 n=8)
ItSomeBy/ints_10-4 200.0 ± 0% 0.0 ± 0% -100.00% (p=0.000 n=8)
ItSomeBy/ints_100-4 200.0 ± 0% 0.0 ± 0% -100.00% (p=0.000 n=8)
ItSomeBy/ints_1000-4 200.0 ± 0% 0.0 ± 0% -100.00% (p=0.000 n=8)
ItNoneBy/ints_10-4 200.0 ± 0% 0.0 ± 0% -100.00% (p=0.000 n=8)
ItNoneBy/ints_100-4 200.0 ± 0% 0.0 ± 0% -100.00% (p=0.000 n=8)
ItNoneBy/ints_1000-4 200.0 ± 0% 0.0 ± 0% -100.00% (p=0.000 n=8)
│ old.txt │ new.txt │
│ allocs/op │ allocs/op vs base │
ItContainsBy/ints_10-4 7.000 ± 0% 0.000 ± 0% -100.00% (p=0.000 n=8)
ItContainsBy/ints_100-4 7.000 ± 0% 0.000 ± 0% -100.00% (p=0.000 n=8)
ItContainsBy/ints_1000-4 7.000 ± 0% 0.000 ± 0% -100.00% (p=0.000 n=8)
ItEveryBy/ints_10-4 6.000 ± 0% 0.000 ± 0% -100.00% (p=0.000 n=8)
ItEveryBy/ints_100-4 6.000 ± 0% 0.000 ± 0% -100.00% (p=0.000 n=8)
ItEveryBy/ints_1000-4 6.000 ± 0% 0.000 ± 0% -100.00% (p=0.000 n=8)
ItSomeBy/ints_10-4 7.000 ± 0% 0.000 ± 0% -100.00% (p=0.000 n=8)
ItSomeBy/ints_100-4 7.000 ± 0% 0.000 ± 0% -100.00% (p=0.000 n=8)
ItSomeBy/ints_1000-4 7.000 ± 0% 0.000 ± 0% -100.00% (p=0.000 n=8)
ItNoneBy/ints_10-4 7.000 ± 0% 0.000 ± 0% -100.00% (p=0.000 n=8)
ItNoneBy/ints_100-4 7.000 ± 0% 0.000 ± 0% -100.00% (p=0.000 n=8)
ItNoneBy/ints_1000-4 7.000 ± 0% 0.000 ± 0% -100.00% (p=0.000 n=8)
Co-authored-by: Samuel Berthe <dev@samuel-berthe.fr>
This commit is contained in:
@@ -261,3 +261,50 @@ func BenchmarkItFind(b *testing.B) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkItContainsBy(b *testing.B) {
|
||||
for _, n := range itLengths {
|
||||
ints := genInts(n)
|
||||
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
|
||||
target := rand.IntN(100_000)
|
||||
for range b.N {
|
||||
_ = it.ContainsBy(ints, func(x int) bool { return x == target })
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkItEveryBy(b *testing.B) {
|
||||
for _, n := range itLengths {
|
||||
ints := genInts(n)
|
||||
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
|
||||
for range b.N {
|
||||
_ = it.EveryBy(ints, func(x int) bool { return x >= 0 })
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkItSomeBy(b *testing.B) {
|
||||
for _, n := range itLengths {
|
||||
ints := genInts(n)
|
||||
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
|
||||
target := rand.IntN(100_000)
|
||||
for range b.N {
|
||||
_ = it.SomeBy(ints, func(x int) bool { return x == target })
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkItNoneBy(b *testing.B) {
|
||||
for _, n := range itLengths {
|
||||
ints := genInts(n)
|
||||
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
|
||||
target := rand.IntN(100_000)
|
||||
for range b.N {
|
||||
_ = it.NoneBy(ints, func(x int) bool { return x == target })
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+28
-4
@@ -18,7 +18,13 @@ func Contains[T comparable](collection iter.Seq[T], element T) bool {
|
||||
// ContainsBy returns true if predicate function return true.
|
||||
// Will iterate through the entire sequence if predicate never returns true.
|
||||
func ContainsBy[T any](collection iter.Seq[T], predicate func(item T) bool) bool {
|
||||
return IsNotEmpty(Filter(collection, predicate))
|
||||
for item := range collection {
|
||||
if predicate(item) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Every returns true if all elements of a subset are contained in a collection or if the subset is empty.
|
||||
@@ -45,7 +51,13 @@ func Every[T comparable](collection iter.Seq[T], subset ...T) bool {
|
||||
// EveryBy returns true if the predicate returns true for all elements in the collection or if the collection is empty.
|
||||
// Will iterate through the entire sequence if predicate never returns false.
|
||||
func EveryBy[T any](collection iter.Seq[T], predicate func(item T) bool) bool {
|
||||
return IsEmpty(Reject(collection, predicate))
|
||||
for item := range collection {
|
||||
if !predicate(item) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Some returns true if at least 1 element of a subset is contained in a collection.
|
||||
@@ -68,7 +80,13 @@ func Some[T comparable](collection iter.Seq[T], subset ...T) bool {
|
||||
// If the collection is empty SomeBy returns false.
|
||||
// Will iterate through the entire sequence if predicate never returns true.
|
||||
func SomeBy[T any](collection iter.Seq[T], predicate func(item T) bool) bool {
|
||||
return IsNotEmpty(Filter(collection, predicate))
|
||||
for item := range collection {
|
||||
if predicate(item) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// None returns true if no element of a subset is contained in a collection or if the subset is empty.
|
||||
@@ -89,7 +107,13 @@ func None[T comparable](collection iter.Seq[T], subset ...T) bool {
|
||||
// NoneBy returns true if the predicate returns true for none of the elements in the collection or if the collection is empty.
|
||||
// Will iterate through the entire sequence if predicate never returns true.
|
||||
func NoneBy[T any](collection iter.Seq[T], predicate func(item T) bool) bool {
|
||||
return IsEmpty(Filter(collection, predicate))
|
||||
for item := range collection {
|
||||
if predicate(item) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Intersect returns the intersection between given collections.
|
||||
|
||||
Reference in New Issue
Block a user