Skip to content

Commit 2905703

Browse files
committed
PR review: Optimise all_block_ranges_in
Previous naive implementation was failing with very large intervals (larger than several millions) since the whole interval was loaded in memory. In order to avoid this side effect an iterator type, `BlockRangesSequence`, is now yielded. Allowing to lazely iterate a sequence of block ranges.
1 parent 23dfabc commit 2905703

File tree

6 files changed

+148
-25
lines changed

6 files changed

+148
-25
lines changed

mithril-aggregator/src/services/cardano_transactions_importer.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,8 @@ impl CardanoTransactionsImporter {
137137
};
138138

139139
debug!(
140-
self.logger,
141-
"TransactionsImporter - computing Block Range Roots";
142-
"start_block" => block_ranges.first().map(|br| br.start).unwrap_or(0),
143-
"end_block" => block_ranges.last().map(|br| br.end).unwrap_or(0),
140+
self.logger, "TransactionsImporter - computing Block Range Roots";
141+
"start_block" => block_ranges.start(), "end_block" => block_ranges.end(),
144142
);
145143

146144
let mut block_ranges_with_merkle_root: Vec<(BlockRange, MKTreeNode)> = vec![];

mithril-common/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ include = ["**/*.rs", "Cargo.toml", "README.md", ".gitignore", "openapi.yaml"]
1212
[lib]
1313
crate-type = ["lib", "cdylib", "staticlib"]
1414

15+
[[bench]]
16+
name = "block_range"
17+
harness = false
18+
1519
[[bench]]
1620
name = "digester"
1721
harness = false

mithril-common/benches/block_range.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
2+
3+
use mithril_common::entities::BlockRange;
4+
5+
fn all_block_ranges_in(c: &mut Criterion) {
6+
let mut group = c.benchmark_group("all_block_ranges_in");
7+
for end_bound in [
8+
BlockRange::LENGTH * 100,
9+
BlockRange::LENGTH * 10_000,
10+
BlockRange::LENGTH * 10_000_000,
11+
BlockRange::LENGTH * 10_000_000_000,
12+
] {
13+
group.bench_with_input(
14+
BenchmarkId::from_parameter(format!("0..{end_bound}")),
15+
&end_bound,
16+
|b, &end_bound| {
17+
b.iter(|| BlockRange::all_block_ranges_in(0..end_bound));
18+
},
19+
);
20+
}
21+
group.finish();
22+
}
23+
24+
criterion_group!(
25+
name = benches;
26+
config = Criterion::default().sample_size(100);
27+
targets = all_block_ranges_in
28+
);
29+
criterion_main!(benches);

mithril-common/src/entities/block_range.rs

Lines changed: 110 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,8 @@ impl BlockRange {
5959
}
6060

6161
/// Get all [BlockRange] strictly contained in the given interval
62-
pub fn all_block_ranges_in(interval: Range<BlockNumber>) -> Vec<BlockRange> {
63-
let all_numbers: Vec<BlockNumber> =
64-
interval.skip_while(|i| i % Self::LENGTH != 0).collect();
65-
all_numbers
66-
.chunks_exact(Self::LENGTH as usize)
67-
.map(|chunk| Self::from_block_number(chunk[0]))
68-
.collect()
62+
pub fn all_block_ranges_in(interval: Range<BlockNumber>) -> BlockRangesSequence {
63+
BlockRangesSequence::new(interval)
6964
}
7065

7166
/// Create a BlockRange from a block number
@@ -149,6 +144,76 @@ impl From<BlockRange> for MKTreeNode {
149144

150145
impl MKMapKey for BlockRange {}
151146

147+
/// A continuous iterable sequence of [block ranges][BlockRange].
148+
///
149+
/// Yielded block ranges are sized by [BlockRange::LENGTH], and always have
150+
/// bounds that are multiples of [BlockRange::LENGTH].
151+
#[derive(Debug, Clone, Eq, PartialEq)]
152+
pub struct BlockRangesSequence {
153+
start: BlockNumber,
154+
end: BlockNumber,
155+
}
156+
157+
impl BlockRangesSequence {
158+
/// Build the [BlockRangesSequence] strictly contained in the given interval.
159+
///
160+
/// The interval bounds will be corrected to be multiples of [BlockRange::LENGTH].
161+
pub fn new(interval: Range<BlockNumber>) -> Self {
162+
let start = if (interval.start % BlockRange::LENGTH) == 0 {
163+
interval.start
164+
} else {
165+
BlockRange::start(interval.start) + BlockRange::LENGTH
166+
};
167+
let end = BlockRange::start(interval.end);
168+
169+
Self { start, end }
170+
}
171+
172+
/// Returns the start of the block ranges sequence.
173+
pub fn start(&self) -> BlockNumber {
174+
self.start
175+
}
176+
177+
/// Returns the end of the block ranges sequence.
178+
pub fn end(&self) -> BlockNumber {
179+
self.end
180+
}
181+
182+
/// Returns `true` if the block ranges sequence contains no elements.
183+
pub fn is_empty(&self) -> bool {
184+
self.len() == 0
185+
}
186+
187+
/// Consume `self` into a new Vec
188+
pub fn into_vec(self) -> Vec<BlockRange> {
189+
self.into_iter().collect()
190+
}
191+
}
192+
193+
impl Iterator for BlockRangesSequence {
194+
type Item = BlockRange;
195+
196+
fn next(&mut self) -> Option<Self::Item> {
197+
if self.start >= self.end {
198+
return None;
199+
}
200+
201+
let block_range = BlockRange::from_block_number(self.start);
202+
self.start = block_range.end;
203+
Some(block_range)
204+
}
205+
206+
fn size_hint(&self) -> (usize, Option<usize>) {
207+
(self.start as usize, Some(self.end as usize))
208+
}
209+
}
210+
211+
impl ExactSizeIterator for BlockRangesSequence {
212+
fn len(&self) -> usize {
213+
((self.end - self.start) / BlockRange::LENGTH) as usize
214+
}
215+
}
216+
152217
#[cfg(test)]
153218
mod tests {
154219
use std::ops::Not;
@@ -214,28 +279,28 @@ mod tests {
214279

215280
#[test]
216281
fn test_block_range_all_block_ranges_in() {
217-
assert_eq!(BlockRange::all_block_ranges_in(0..0), vec![]);
218-
assert_eq!(BlockRange::all_block_ranges_in(0..1), vec![]);
219-
assert_eq!(BlockRange::all_block_ranges_in(0..14), vec![]);
220-
assert_eq!(BlockRange::all_block_ranges_in(1..15), vec![]);
282+
assert_eq!(BlockRange::all_block_ranges_in(0..0).into_vec(), vec![]);
283+
assert_eq!(BlockRange::all_block_ranges_in(0..1).into_vec(), vec![]);
284+
assert_eq!(BlockRange::all_block_ranges_in(0..14).into_vec(), vec![]);
285+
assert_eq!(BlockRange::all_block_ranges_in(1..15).into_vec(), vec![]);
221286
assert_eq!(
222-
BlockRange::all_block_ranges_in(0..15),
287+
BlockRange::all_block_ranges_in(0..15).into_vec(),
223288
vec![BlockRange::new(0, 15)]
224289
);
225290
assert_eq!(
226-
BlockRange::all_block_ranges_in(0..16),
291+
BlockRange::all_block_ranges_in(0..16).into_vec(),
227292
vec![BlockRange::new(0, 15)]
228293
);
229294
assert_eq!(
230-
BlockRange::all_block_ranges_in(14..30),
295+
BlockRange::all_block_ranges_in(14..30).into_vec(),
231296
vec![BlockRange::new(15, 30)]
232297
);
233298
assert_eq!(
234-
BlockRange::all_block_ranges_in(14..31),
299+
BlockRange::all_block_ranges_in(14..31).into_vec(),
235300
vec![BlockRange::new(15, 30)]
236301
);
237302
assert_eq!(
238-
BlockRange::all_block_ranges_in(14..61),
303+
BlockRange::all_block_ranges_in(14..61).into_vec(),
239304
vec![
240305
BlockRange::new(15, 30),
241306
BlockRange::new(30, 45),
@@ -244,6 +309,35 @@ mod tests {
244309
);
245310
}
246311

312+
#[test]
313+
fn test_block_ranges_sequence_is_empty() {
314+
assert!(BlockRange::all_block_ranges_in(0..0).is_empty());
315+
assert!(BlockRange::all_block_ranges_in(0..1).is_empty());
316+
assert!(BlockRange::all_block_ranges_in(0..14).is_empty());
317+
assert!(BlockRange::all_block_ranges_in(1..15).is_empty());
318+
assert!(BlockRange::all_block_ranges_in(0..15).is_empty().not());
319+
assert!(BlockRange::all_block_ranges_in(0..16).is_empty().not());
320+
assert!(BlockRange::all_block_ranges_in(14..30).is_empty().not());
321+
assert!(BlockRange::all_block_ranges_in(14..31).is_empty().not());
322+
assert!(BlockRange::all_block_ranges_in(14..61).is_empty().not());
323+
}
324+
325+
#[test]
326+
fn test_block_ranges_sequence_len() {
327+
assert_eq!(
328+
BlockRange::all_block_ranges_in(0..(BlockRange::LENGTH - 1)).len(),
329+
0
330+
);
331+
assert_eq!(
332+
BlockRange::all_block_ranges_in(0..(BlockRange::LENGTH)).len(),
333+
1
334+
);
335+
assert_eq!(
336+
BlockRange::all_block_ranges_in(0..(BlockRange::LENGTH * 15)).len(),
337+
15
338+
);
339+
}
340+
247341
#[test]
248342
fn test_block_range_from_number() {
249343
assert_eq!(BlockRange::from_block_number(0), BlockRange::new(0, 15));

mithril-common/src/entities/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ mod snapshot;
2424
mod time_point;
2525
mod type_alias;
2626

27-
pub use block_range::{BlockRange, BlockRangeLength};
27+
pub use block_range::{BlockRange, BlockRangeLength, BlockRangesSequence};
2828
pub use cardano_chain_point::{BlockHash, BlockNumber, ChainPoint, SlotNumber};
2929
pub use cardano_db_beacon::CardanoDbBeacon;
3030
pub use cardano_network::CardanoNetwork;

mithril-signer/src/cardano_transactions_importer.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,8 @@ impl CardanoTransactionsImporter {
137137
};
138138

139139
debug!(
140-
self.logger,
141-
"TransactionsImporter - computing Block Range Roots";
142-
"start_block" => block_ranges.first().map(|br| br.start).unwrap_or(0),
143-
"end_block" => block_ranges.last().map(|br| br.end).unwrap_or(0),
140+
self.logger, "TransactionsImporter - computing Block Range Roots";
141+
"start_block" => block_ranges.start(), "end_block" => block_ranges.end(),
144142
);
145143

146144
let mut block_ranges_with_merkle_root: Vec<(BlockRange, MKTreeNode)> = vec![];

0 commit comments

Comments
 (0)