feat(sp20): parallelise per-file MBP-10 trade extraction in mbp10_to_imbalance_bars
Bottleneck A of two: the loop in `mbp10_to_imbalance_bars` that calls `extract_trades_from_dbn_file` + `filter_front_month_mbp10` per file ran serially across the 9 quarterly DBN files. Each file is independent (different contract universe per quarter), the front-month filter is purely intra-file, and the final `all_trades.sort_by(|a, b| timestamp)` re-sequences across files — so reduce order across files is irrelevant to correctness. Switched the loop to `dbn_files.par_iter().filter_map(...).collect()`, mirroring the trades_loader-side pattern in `precompute_features.rs:551-565`. Per-file logging (raw count → filtered front-month count) preserved verbatim. On the `ci-compile-cpu` 28-core node decoding 9 .dbn.zst files, the file-decoder/zstd-decompress phase should drop from ~9× single-file latency to ~1× — bounded by the slowest single file. This is the trivial half of the parallelisation. Bottleneck B (the sequential `ImbalanceBarSampler` pass over the concatenated trade list) follows in the next commit with time-bucket sharding + warmup overlap + bit-equivalence test, mirroring the SP20 OFI pattern at `crates/ml-features/src/ofi_calculator.rs::compute_ofi_per_bar_parallel`. Build: `cargo check -p ml-features` clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -681,28 +681,46 @@ pub fn mbp10_to_imbalance_bars(
|
|||||||
// gate. Mirror the per-file pattern in `precompute_features.rs:347-365`
|
// gate. Mirror the per-file pattern in `precompute_features.rs:347-365`
|
||||||
// (uses `filter_front_month` on `DbnTrade` slices); same logic, applied
|
// (uses `filter_front_month` on `DbnTrade` slices); same logic, applied
|
||||||
// to `Mbp10Trade.instrument_id`.
|
// to `Mbp10Trade.instrument_id`.
|
||||||
let mut all_trades: Vec<Mbp10Trade> = Vec::new();
|
//
|
||||||
for file_path in &dbn_files {
|
// SP20 parallelism: `extract_trades_from_dbn_file` is a pure
|
||||||
match extract_trades_from_dbn_file(file_path) {
|
// file → Vec<Mbp10Trade> function and the 9 quarterly files are fully
|
||||||
Ok(trades) => {
|
// independent (each carries its own contract universe). Mirror the
|
||||||
let raw_count = trades.len();
|
// par_iter pattern in `precompute_features.rs:551-565` so a 28-core
|
||||||
let filtered = filter_front_month_mbp10(&trades);
|
// CPU node can decode the files concurrently. Front-month filter is
|
||||||
tracing::info!(
|
// applied per-file as before — semantics unchanged. Final ordering
|
||||||
" {} -> {} trades, {} front-month",
|
// is enforced by the `all_trades.sort_by(|a, b| ...)` step below, so
|
||||||
file_path.file_name().unwrap_or_default().to_string_lossy(),
|
// file-level reduce order is irrelevant for correctness.
|
||||||
raw_count,
|
let per_file_trades: Vec<Vec<Mbp10Trade>> = {
|
||||||
filtered.len()
|
use rayon::prelude::*;
|
||||||
);
|
dbn_files
|
||||||
all_trades.extend(filtered);
|
.par_iter()
|
||||||
}
|
.filter_map(|file_path| match extract_trades_from_dbn_file(file_path) {
|
||||||
Err(e) => {
|
Ok(trades) => {
|
||||||
tracing::warn!(
|
let raw_count = trades.len();
|
||||||
"Failed to extract trades from {}: {}",
|
let filtered = filter_front_month_mbp10(&trades);
|
||||||
file_path.display(),
|
tracing::info!(
|
||||||
e
|
" {} -> {} trades, {} front-month",
|
||||||
);
|
file_path.file_name().unwrap_or_default().to_string_lossy(),
|
||||||
}
|
raw_count,
|
||||||
}
|
filtered.len()
|
||||||
|
);
|
||||||
|
Some(filtered)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!(
|
||||||
|
"Failed to extract trades from {}: {}",
|
||||||
|
file_path.display(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
|
let mut all_trades: Vec<Mbp10Trade> =
|
||||||
|
Vec::with_capacity(per_file_trades.iter().map(Vec::len).sum());
|
||||||
|
for chunk in per_file_trades {
|
||||||
|
all_trades.extend(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
if all_trades.is_empty() {
|
if all_trades.is_empty() {
|
||||||
|
|||||||
Reference in New Issue
Block a user