Skip to content

Skip an allocation in our hot path#1250

Open
sgrif wants to merge 1 commit into
mainfrom
sg-skip-hot-allocation
Open

Skip an allocation in our hot path#1250
sgrif wants to merge 1 commit into
mainfrom
sg-skip-hot-allocation

Conversation

@sgrif

@sgrif sgrif commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Profiling showed this allocation was as much as .5% of our wall time. This function unfortunately does need to allocate, as one of the strategies involves randomizing the elements and another involves sorting them. Neither of those are possible without allocation.

We also can't do any funky shit like re-using a thread local allocation, as candidates does need to be held past an await point.

That leaves us with stack allocation as our only remaining option. This will always take up 256 bytes on the stack, and only heap allocate when we have more elements than that. The largest deployment that we know of will have 14 targets here, so this is potentially twice as large as it needs to be. But we can start by giving ourselves some wiggle room and we can always shrink this down later.

The cost here is that the size of the future being returned by this function will be 222 bytes larger, so copying the future itself becomes a more expensive operation. In practice this should happen exactly once, as it gets moved to the heap when spawned, so it should be a net improvement over a random heap allocation. But profiling should be done to confirm.

Profiling showed this allocation was as much as .5% of our wall time.
This function unfortunately does need to allocate, as one of the
strategies involves randomizing the elements and another involves
sorting them. Neither of those are possible without allocation.

We also can't do any funky shit like re-using a thread local allocation,
as `candidates` does need to be held past an await point.

That leaves us with stack allocation as our only remaining option. This
will always take up 256 bytes on the stack, and only heap allocate when
we have more elements than that. The largest deployment that we know of
will have 14 targets here, so this is potentially twice as large as it
needs to be. But we can start by giving ourselves some wiggle room and
we can always shrink this down later.

The cost here is that the size of the future being returned by this
function will be 222 bytes larger, so copying the future itself becomes
a more expensive operation. In practice this should happen exactly once,
as it gets moved to the heap when spawned, so it should be a net
improvement over a random heap allocation. But profiling should be done
to confirm.
@sgrif
sgrif requested a review from levkk July 21, 2026 20:24
@codecov

codecov Bot commented Jul 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

RoundRobin => {
let first = self.round_robin.fetch_add(1, Ordering::Relaxed) % candidates.len();
let mut reshuffled = vec![];
let mut reshuffled = SmallVec::with_capacity(candidates.len());

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Literally had to look up the code to understand that it infers the size of the new vec from the type of the original. This is legit a cool lib and a really cool use of the type system.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah, it's wild that they managed to make this work before const generics (2.0 uses const generics but it's still alpha)

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