-
Notifications
You must be signed in to change notification settings - Fork 797
ledger: Build the grouped slot leaders manually #8451
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
base: master
Are you sure you want to change the base?
ledger: Build the grouped slot leaders manually #8451
Conversation
e42e215 to
cb8fedb
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #8451 +/- ##
=========================================
- Coverage 83.2% 83.1% -0.1%
=========================================
Files 846 846
Lines 368550 368566 +16
=========================================
- Hits 306656 306536 -120
- Misses 61894 62030 +136 🚀 New features to boost your workflow:
|
37b32a9 to
b758fe5
Compare
| slot_leaders: Vec<Pubkey>, | ||
| // Inverted index from pubkeys to indices where they are the leader. | ||
| leader_slots_map: HashMap<Pubkey, Arc<Vec<usize>>>, | ||
| leader_slots_map: HashMap<Pubkey, Vec<usize>, PubkeyHasherBuilder>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for pointing this!
I tried to remove this cloned() and make it work with Vec<usize> (without Arc), but the problem is that the returned iterator is used outside of the LeaderSchedule, so then compiler was rightfully complaining about lifetimes and returning references to local values.
So I decided to put the Arc back. Now I can see its purpose.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW, adding Arc back unfortunately increases the execution time from 5.2ms to 9.9ms. 🥲 But I have no better ideas and that's still better than cloning.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks reasonable to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find a way to remove Arc.
The Arc was needed because get_leader_upcoming_slots returned a boxed iterator requiring ownership of the Vec. By modifying get_leader_upcoming_slots to return a borrowed iterator (Box<dyn Iterator + '_>), and collecting the Arc references upfront in next_leader_slot, the schedules stay alive for the entire iterator chain, eliminating the need for Arc wrapping and avoiding intermediate Vec allocations.
|
Excellent work! I have just one comment! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Please wait for other reviewer's approval before merge.
|
I'm going to bow out and defer to the other reviewers since I'm OOO. |
|
We should probably review our uses of |
I'm yet to profile all uses of let mut grouped = HashMap::with_capacity_and_hasher(cap, hasher);
for (key, value) in input {
grouped_slot_leaders
.entry(value)
.and_modify(|keys| {
keys.push(key);
})
.or_insert(vec![key]);
}Perhaps I should already move it to some common function in Given the lack of elasticity of itertools (no way of providing capacities and hashers), I think its usage is a footgun. I tried to make it accept custom hashers, but the PR got closed: rust-itertools/itertools#1057. |
| let slots = Arc::get_mut(slots).expect("should be the only reference"); | ||
| slots.push(slot) | ||
| }) | ||
| .or_insert(Arc::new(vec![slot])); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could also experiment with smallvec, with just a slot value you could keep like 2-3 elements inline without allocating
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea. Or maybe even arrayvec.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in the profile, I think you see grow taking a lot of time, so if anything you should allocate with higher capacity?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh it's Vec<usize>, yeah use arrayvec
| } | ||
| None => HashMap::with_hasher(PubkeyHasherBuilder::default()), | ||
| }; | ||
| for (slot, leader) in slot_leaders.iter().enumerate() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Kamil's comment made me realize that slot_leaders will always be setup so that the same leader will always the same for 4 consecutive slots. We could use that fact to do more optimizing
Good point — applying the same pattern to staked_nodes in stake_cache gave us a 3–4× speed-up as well: #8516 |
Building them with `itertools::into_group_map` takes 20.9ms. Building them manually with a pre-allocated hash map, using pubkey hasher, takes 5.2ms.
cd7b5b6 to
80b2e04
Compare

Problem
Building them with
itertools::into_group_maptakes 20.9ms.Summary of Changes
Building them manually with a pre-allocated hash map, using pubkey hasher, takes 5.2ms.
Ref: #8280