From bf965a7974332df2e43be0e9f5b927dd47008c13 Mon Sep 17 00:00:00 2001 From: vad Date: Mon, 9 Feb 2026 15:24:54 +0100 Subject: [PATCH 1/4] ledger: Build the grouped slot leaders manually 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. --- leader-schedule/Cargo.toml | 3 +-- leader-schedule/src/vote_keyed.rs | 39 ++++++++++++++++++++++--------- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/leader-schedule/Cargo.toml b/leader-schedule/Cargo.toml index 0dc14348e27..4e2be25c3b4 100644 --- a/leader-schedule/Cargo.toml +++ b/leader-schedule/Cargo.toml @@ -24,14 +24,13 @@ agave-random = { workspace = true } itertools = { workspace = true } rand_chacha = { workspace = true } solana-clock = { workspace = true } -solana-pubkey = { workspace = true } +solana-pubkey = { workspace = true, features = ["rand"] } solana-vote = { workspace = true } [dev-dependencies] bs58 = { workspace = true, features = ["alloc"] } rand = { workspace = true } sha2 = { workspace = true } -solana-pubkey = { workspace = true, features = ["rand"] } solana-vote = { path = "../vote", features = ["agave-unstable-api", "dev-context-only-utils"] } test-case = { workspace = true } diff --git a/leader-schedule/src/vote_keyed.rs b/leader-schedule/src/vote_keyed.rs index eae93815668..e3fe4d8e0d4 100644 --- a/leader-schedule/src/vote_keyed.rs +++ b/leader-schedule/src/vote_keyed.rs @@ -1,8 +1,7 @@ use { super::{SlotLeader, stake_weighted_slot_leaders}, - itertools::Itertools, solana_clock::Epoch, - solana_pubkey::Pubkey, + solana_pubkey::{Pubkey, PubkeyHasherBuilder}, solana_vote::vote_account::VoteAccountsHashMap, std::{collections::HashMap, iter, num::NonZeroUsize, ops::Index}, }; @@ -11,7 +10,7 @@ use { pub struct LeaderSchedule { slot_leaders: Vec, // Inverted index from leader id to indices where they are the leader. - leader_slots_map: HashMap>, + leader_slots_map: HashMap, PubkeyHasherBuilder>, repeat: NonZeroUsize, } @@ -47,11 +46,18 @@ impl LeaderSchedule { }) .collect(); let slot_leaders = stake_weighted_slot_leaders(slot_leader_stakes, epoch, len, repeat); - Self::new_from_schedule(slot_leaders, repeat) + Self { + leader_slots_map: Self::invert_slot_leaders( + &slot_leaders, + Some(vote_accounts_map.len()), + ), + slot_leaders, + repeat, + } } pub fn new_from_schedule(slot_leaders: Vec, repeat: NonZeroUsize) -> Self { - let leader_slots_map = Self::invert_slot_leaders(&slot_leaders); + let leader_slots_map = Self::invert_slot_leaders(&slot_leaders, None); Self { slot_leaders, leader_slots_map, @@ -59,12 +65,23 @@ impl LeaderSchedule { } } - fn invert_slot_leaders(slot_leaders: &[SlotLeader]) -> HashMap> { - slot_leaders - .iter() - .enumerate() - .map(|(i, leader)| (leader.id, i)) - .into_group_map() + fn invert_slot_leaders( + slot_leaders: &[SlotLeader], + nodes_len: Option, + ) -> HashMap, PubkeyHasherBuilder> { + let mut grouped_slot_leaders = match nodes_len { + Some(nodes_len) => { + HashMap::with_capacity_and_hasher(nodes_len, PubkeyHasherBuilder::default()) + } + None => HashMap::with_hasher(PubkeyHasherBuilder::default()), + }; + for (slot, leader) in slot_leaders.iter().enumerate() { + grouped_slot_leaders + .entry(leader.id) + .and_modify(|slots: &mut Vec| slots.push(slot)) + .or_insert(vec![slot]); + } + grouped_slot_leaders } pub fn get_slot_leaders(&self) -> impl Iterator { From 167615f68e6a76304178dff59cb3d134995a264e Mon Sep 17 00:00:00 2001 From: vad Date: Wed, 25 Mar 2026 14:53:17 +0100 Subject: [PATCH 2/4] leader-schedule: Use `HashMap::default()` It works even with a custom hasher. --- leader-schedule/src/vote_keyed.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/leader-schedule/src/vote_keyed.rs b/leader-schedule/src/vote_keyed.rs index e3fe4d8e0d4..85198583008 100644 --- a/leader-schedule/src/vote_keyed.rs +++ b/leader-schedule/src/vote_keyed.rs @@ -73,7 +73,7 @@ impl LeaderSchedule { Some(nodes_len) => { HashMap::with_capacity_and_hasher(nodes_len, PubkeyHasherBuilder::default()) } - None => HashMap::with_hasher(PubkeyHasherBuilder::default()), + None => HashMap::default(), }; for (slot, leader) in slot_leaders.iter().enumerate() { grouped_slot_leaders From fc1c4a28911afa6a0aef0408a8995c1a9752e980 Mon Sep 17 00:00:00 2001 From: vad Date: Wed, 25 Mar 2026 14:54:40 +0100 Subject: [PATCH 3/4] leader-schedule: Change `&mut Vec` to `&mut Vec<_>` --- leader-schedule/src/vote_keyed.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/leader-schedule/src/vote_keyed.rs b/leader-schedule/src/vote_keyed.rs index 85198583008..ba42976941a 100644 --- a/leader-schedule/src/vote_keyed.rs +++ b/leader-schedule/src/vote_keyed.rs @@ -78,7 +78,7 @@ impl LeaderSchedule { for (slot, leader) in slot_leaders.iter().enumerate() { grouped_slot_leaders .entry(leader.id) - .and_modify(|slots: &mut Vec| slots.push(slot)) + .and_modify(|slots: &mut Vec<_>| slots.push(slot)) .or_insert(vec![slot]); } grouped_slot_leaders From 34d5d1aaf36b6d8be245d5c2de394dc299ca2f89 Mon Sep 17 00:00:00 2001 From: vad Date: Wed, 25 Mar 2026 14:56:07 +0100 Subject: [PATCH 4/4] leader-schedule: Rename `slot` to `index` where appropriate In the context of enumeration in leader schedule, `index` is a more appropriate name. --- leader-schedule/src/vote_keyed.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/leader-schedule/src/vote_keyed.rs b/leader-schedule/src/vote_keyed.rs index ba42976941a..c82467566fe 100644 --- a/leader-schedule/src/vote_keyed.rs +++ b/leader-schedule/src/vote_keyed.rs @@ -75,11 +75,11 @@ impl LeaderSchedule { } None => HashMap::default(), }; - for (slot, leader) in slot_leaders.iter().enumerate() { + for (index, leader) in slot_leaders.iter().enumerate() { grouped_slot_leaders .entry(leader.id) - .and_modify(|slots: &mut Vec<_>| slots.push(slot)) - .or_insert(vec![slot]); + .and_modify(|indices: &mut Vec<_>| indices.push(index)) + .or_insert(vec![index]); } grouped_slot_leaders }