Skip to content

Commit 5ad8742

Browse files
authored
Merge pull request #2256 from ljedrz/perf/optimize_solutions_split
Optimize the split into (un)verified solutions
2 parents 0f32452 + 95af1d2 commit 5ad8742

File tree

2 files changed

+80
-21
lines changed

2 files changed

+80
-21
lines changed

ledger/src/advance.rs

Lines changed: 62 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,61 @@ impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
107107
}
108108
}
109109

110+
/// Splits candidate solutions into a collection of accepted ones and aborted ones.
111+
pub fn split_candidate_solutions<T, F>(
112+
mut candidate_solutions: Vec<T>,
113+
max_solutions: usize,
114+
verification_fn: F,
115+
) -> (Vec<T>, Vec<T>)
116+
where
117+
T: Sized + Send,
118+
F: Fn(&T) -> bool + Send + Sync,
119+
{
120+
// Separate the candidate solutions into valid and aborted solutions.
121+
let mut valid_candidate_solutions = Vec::with_capacity(max_solutions);
122+
let mut aborted_candidate_solutions = Vec::new();
123+
// Reverse the candidate solutions in order to be able to chunk them more efficiently.
124+
candidate_solutions.reverse();
125+
// Verify the candidate solutions in chunks. This is done so that we can potentially
126+
// perform these operations in parallel while keeping the end result deterministic.
127+
let chunk_size = 16;
128+
while !candidate_solutions.is_empty() {
129+
// Check if the collection of valid solutions is full.
130+
if valid_candidate_solutions.len() >= max_solutions {
131+
// If that's the case, mark the rest of the candidates as aborted.
132+
aborted_candidate_solutions.extend(candidate_solutions.into_iter().rev());
133+
break;
134+
}
135+
136+
// Split off a chunk of the candidate solutions.
137+
let candidates_chunk = if candidate_solutions.len() > chunk_size {
138+
candidate_solutions.split_off(candidate_solutions.len() - chunk_size)
139+
} else {
140+
std::mem::take(&mut candidate_solutions)
141+
};
142+
143+
// Verify the solutions in the chunk.
144+
let verification_results: Vec<_> = cfg_into_iter!(candidates_chunk)
145+
.rev()
146+
.map(|solution| {
147+
let verified = verification_fn(&solution);
148+
(solution, verified)
149+
})
150+
.collect();
151+
152+
// Process the results of the verification.
153+
for (solution, is_valid) in verification_results.into_iter() {
154+
if is_valid && valid_candidate_solutions.len() < max_solutions {
155+
valid_candidate_solutions.push(solution);
156+
} else {
157+
aborted_candidate_solutions.push(solution);
158+
}
159+
}
160+
}
161+
162+
(valid_candidate_solutions, aborted_candidate_solutions)
163+
}
164+
110165
impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
111166
/// Constructs a block template for the next block in the ledger.
112167
#[allow(clippy::type_complexity)]
@@ -127,28 +182,14 @@ impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
127182
let coinbase_verifying_key = self.coinbase_puzzle.coinbase_verifying_key();
128183
// Retrieve the latest epoch challenge.
129184
let latest_epoch_challenge = self.latest_epoch_challenge()?;
130-
// TODO: For mainnet - Add `aborted_solution_ids` to the block. And optimize this logic.
131-
// Verify the candidate solutions.
132-
let verification_results: Vec<_> = cfg_into_iter!(candidate_solutions)
133-
.map(|solution| {
134-
(
135-
solution,
136-
solution
137-
.verify(coinbase_verifying_key, &latest_epoch_challenge, self.latest_proof_target())
138-
.unwrap_or(false),
139-
)
140-
})
141-
.collect();
185+
// TODO: For mainnet - Add `aborted_solution_ids` to the block.
142186
// Separate the candidate solutions into valid and aborted solutions.
143-
let mut valid_candidate_solutions = Vec::with_capacity(N::MAX_SOLUTIONS);
144-
let mut aborted_candidate_solutions = Vec::new();
145-
for (solution, is_valid) in verification_results.into_iter() {
146-
if is_valid && valid_candidate_solutions.len() < N::MAX_SOLUTIONS {
147-
valid_candidate_solutions.push(solution);
148-
} else {
149-
aborted_candidate_solutions.push(solution);
150-
}
151-
}
187+
let (valid_candidate_solutions, _aborted_candidate_solutions) =
188+
split_candidate_solutions(candidate_solutions, N::MAX_SOLUTIONS, |solution| {
189+
solution
190+
.verify(coinbase_verifying_key, &latest_epoch_challenge, self.latest_proof_target())
191+
.unwrap_or(false)
192+
});
152193

153194
// Check if there are any valid solutions.
154195
match valid_candidate_solutions.is_empty() {

ledger/src/tests.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
// limitations under the License.
1414

1515
use crate::{
16+
advance::split_candidate_solutions,
1617
test_helpers::{CurrentLedger, CurrentNetwork},
1718
RecordsFilter,
1819
};
@@ -828,3 +829,20 @@ finalize foo2:
828829
assert!(ledger.vm.transaction_store().contains_transaction_id(&deployment_1_id).unwrap());
829830
assert!(ledger.vm.block_store().contains_rejected_or_aborted_transaction_id(&deployment_2_id).unwrap());
830831
}
832+
833+
#[test]
834+
fn test_split_candidate_solutions() {
835+
let rng = &mut TestRng::default();
836+
837+
let max_solutions = CurrentNetwork::MAX_SOLUTIONS;
838+
839+
const ITERATIONS: usize = 1_000;
840+
841+
for _ in 0..ITERATIONS {
842+
let num_candidates = rng.gen_range(0..max_solutions * 2);
843+
let candidate_solutions: Vec<u8> = rng.sample_iter(Standard).take(num_candidates).collect();
844+
845+
let (_accepted, _aborted) =
846+
split_candidate_solutions(candidate_solutions, max_solutions, |candidate| candidate % 2 == 0);
847+
}
848+
}

0 commit comments

Comments
 (0)