Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(Failed) iterator experiment instead of multiple for loops #809

Closed
wants to merge 1 commit into from

Conversation

brandur
Copy link
Contributor

@brandur brandur commented Mar 18, 2025

Here, experiment with upgrading to Go 1.23 so we can replace a couple
hooks-related TODOs where we have two separate for loops with a single
invocation that uses an iterator to iterate two slices without an
additional slice invocation.

I'm putting this up for the sake of interest, but it unfortunately turns
out to be quite a failed experiment. The iterators work, but using them
is slower than both the previous two for loops, and also slower than
just allocating a new slice with the contents of the previous two and
iterating that.

At larger slice sizes, the iterator becomes marginally faster than
allocating a new slice, but only marginally. Using two for loops
continues to be bit a bit faster (perhaps unsurprisingly).

Anyway, opened this for general interest, but am going to close it, and
probably just replace the two for loops with a slice allocation, since
it looks nicer and it's the fastest option for small slices sizes (and
we generally expect hooks to be relatively small slices).

$ go test ./rivershared/util/sliceutil -bench=.
goos: darwin
goarch: arm64
pkg: github.com/riverqueue/river/rivershared/util/sliceutil
cpu: Apple M4
BenchmarkIterateCombined/IterateCombined-10              1481770               822.0 ns/op
BenchmarkIterateCombined/SliceAllocation-10              1617640               754.1 ns/op
BenchmarkIterateCombined/TwoForLoops-10                  1560508               775.7 ns/op
BenchmarkIterateCombined/IterateCombinedLong-10          1393824               863.5 ns/op
BenchmarkIterateCombined/SliceAllocationLong-10          1399813               866.7 ns/op
BenchmarkIterateCombined/TwoForLoopsLong-10              1445377               835.2 ns/op
PASS
ok      github.com/riverqueue/river/rivershared/util/sliceutil  12.272s

Here, experiment with upgrading to Go 1.23 so we can replace a couple
hooks-related TODOs where we have two separate for loops with a single
invocation that uses an iterator to iterate two slices without an
additional slice invocation.

I'm putting this up for the sake of interest, but it unfortunately turns
out to be quite a failed experiment. The iterators work, but using them
is slower than both the previous two `for` loops, and also slower than
just allocating a new slice with the contents of the previous two and
iterating that.

At larger slice sizes, the iterator becomes marginally faster than
allocating a new slice, but only _marginally_. Using two for loops
continues to be bit a bit faster (perhaps unsurprisingly).

Anyway, opened this for general interest, but am going to close it, and
probably just replace the two for loops with a slice allocation, since
it looks nicer and it's the fastest option for small slices sizes (and
we generally expect hooks to be relatively small slices).

    $ go test ./rivershared/util/sliceutil -bench=.
    goos: darwin
    goarch: arm64
    pkg: github.com/riverqueue/river/rivershared/util/sliceutil
    cpu: Apple M4
    BenchmarkIterateCombined/IterateCombined-10              1481770               822.0 ns/op
    BenchmarkIterateCombined/SliceAllocation-10              1617640               754.1 ns/op
    BenchmarkIterateCombined/TwoForLoops-10                  1560508               775.7 ns/op
    BenchmarkIterateCombined/IterateCombinedLong-10          1393824               863.5 ns/op
    BenchmarkIterateCombined/SliceAllocationLong-10          1399813               866.7 ns/op
    BenchmarkIterateCombined/TwoForLoopsLong-10              1445377               835.2 ns/op
    PASS
    ok      github.com/riverqueue/river/rivershared/util/sliceutil  12.272s
@brandur brandur force-pushed the brandur-seq-experiment branch from 3a9f89e to 6561c0f Compare March 18, 2025 06:11
brandur added a commit that referenced this pull request Mar 18, 2025
Clean up some duplicated code in which we were previously iterating over
two sets of hooks and running the same code on both collections, and I'd
left a TODO to investigate the use of sequences instead to avoid a slice
allocation.

I ran the sequences experiment over in #809, and although I'd still
defend it as a worthwhile prototype, it turns out that using sequences
only becomes more efficient than allocating an extra slice once we get
to fairly large slice sizes, and even there the benefit is quite
marginal.

The benchmarks also show that at smaller slice sizes, it's actually a
tiny bit faster allocating a slice to iterate over once instead of using
two for loops. This is convenient because that sure looks a lot nicer
code-wise anyway, so here we switch over to that approach and remove the
TODOs.
@brandur
Copy link
Contributor Author

brandur commented Mar 18, 2025

Closing in favor of #810.

@brandur brandur closed this Mar 18, 2025
brandur added a commit that referenced this pull request Mar 18, 2025
#810)

Clean up some duplicated code in which we were previously iterating over
two sets of hooks and running the same code on both collections, and I'd
left a TODO to investigate the use of sequences instead to avoid a slice
allocation.

I ran the sequences experiment over in #809, and although I'd still
defend it as a worthwhile prototype, it turns out that using sequences
only becomes more efficient than allocating an extra slice once we get
to fairly large slice sizes, and even there the benefit is quite
marginal.

The benchmarks also show that at smaller slice sizes, it's actually a
tiny bit faster allocating a slice to iterate over once instead of using
two for loops. This is convenient because that sure looks a lot nicer
code-wise anyway, so here we switch over to that approach and remove the
TODOs.
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.

1 participant