Skip to content

refactor: reuse helpers where possible#737

Closed
NathanBaulch wants to merge 2 commits intosamber:masterfrom
NathanBaulch:reusehelpers
Closed

refactor: reuse helpers where possible#737
NathanBaulch wants to merge 2 commits intosamber:masterfrom
NathanBaulch:reusehelpers

Conversation

@NathanBaulch
Copy link
Contributor

@NathanBaulch NathanBaulch commented Nov 6, 2025

This is a continuation of #691, which in turn was done in response to the huge number of code reuse opportunities I found whilst building the it subpackage.

This PR manages to reduce the total net SLOC by 409 lines (+103 -512) by carefully using existing helpers wherever it can be done efficiently without additional memory allocations or unnecessary compute.

I've utilized first-class functions quite a lot, especially one of my favorite patterns involving Partial, HasKey and Keyify to build a compact and efficient predicate, eg:

DropWhile(collection, Partial(HasKey, Keyify(cutset)))

I've also added internal _map, _filter and _reject functions that omit the index argument on callbacks to further improve first-class function syntax opportunities. Let me know if these should be renamed.

@codecov
Copy link

codecov bot commented Nov 6, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.79%. Comparing base (d99edab) to head (3afe206).

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #737      +/-   ##
==========================================
- Coverage   94.04%   93.79%   -0.26%     
==========================================
  Files          18       18              
  Lines        2838     2577     -261     
==========================================
- Hits         2669     2417     -252     
+ Misses        152      146       -6     
+ Partials       17       14       -3     
Flag Coverage Δ
unittests 93.79% <100.00%> (-0.26%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

}

return mIn, index
return MinIndexBy(collection, func(a, b T) bool { return a < b })
Copy link
Owner

@samber samber Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just made a quick benchmark to check if inlining works well.

func MinIndex2[T constraints.Ordered](collection []T) (T, int) {
	return MinIndexBy(collection, func(a, b T) bool { return a < b })
}

func BenchmarkMinIndex(b *testing.B) {
	arr := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

	b.Run("lo.MinIndex", func(b *testing.B) {
		for n := 0; n < b.N; n++ {
			_, _ = MinIndex(arr)
		}
	})
	b.Run("lo.MinIndexNew", func(b *testing.B) {
		for n := 0; n < b.N; n++ {
			_, _ = MinIndex2(arr)
		}
	})
}

I did not investigate more, but the result is not very good:

goos: darwin
goarch: arm64
pkg: github.com/samber/lo
cpu: Apple M3
=== RUN   BenchmarkMinIndex
BenchmarkMinIndex
=== RUN   BenchmarkMinIndex/lo.MinIndex
BenchmarkMinIndex/lo.MinIndex
BenchmarkMinIndex/lo.MinIndex-8                 329917464                3.370 ns/op           0 B/op          0 allocs/op
=== RUN   BenchmarkMinIndex/lo.MinIndexNew
BenchmarkMinIndex/lo.MinIndexNew
BenchmarkMinIndex/lo.MinIndexNew-8              261727790                4.435 ns/op           0 B/op          0 allocs/op
PASS

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, that's disappointing. Hopefully I can look deeper into this sometime.

Side note, it would be nice if we had a comprehensive suite of benchmarks with realistic examples that could be automatically checked for perf regressions. Someday...

@NathanBaulch
Copy link
Contributor Author

NathanBaulch commented Nov 7, 2025

It's much worse on my machine! 😓
I added a third test with inlining explicitly disabled and it's twice as bad again. So the callback funcs are inlined but clearly aren't being further optimized like in the original helper (bounds checks, branch prediction or some other compiler magic).

goos: linux
goarch: amd64
pkg: github.com/samber/lo
cpu: Intel(R) Core(TM) Ultra 9 275HX
BenchmarkMinIndex/lo.MinIndex-24              1000000000         0.775 ns/op
BenchmarkMinIndex/lo.MinIndexNew-24            317205955         3.914 ns/op
BenchmarkMinIndex/lo.MinIndexNoInlining-24     134372474         8.784 ns/op

@samber
Copy link
Owner

samber commented Nov 7, 2025

Ok, so I'm closing this PR for now. Improving readability is cool, but for such simple helpers, I consider the performance gain as more important.

FYI, some helpers have been combined for performance reasons: Filter+Map -> FilterMap.

@samber samber closed this Nov 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants