From 1f02e78b2400c73c5f05351b0beff16d6c724e2a Mon Sep 17 00:00:00 2001 From: boris Date: Sat, 5 Sep 2026 00:48:12 +0800 Subject: [PATCH] perf: reuse duplicate rolling lookbacks --- crates/fidc-core/src/data.rs | 87 +++++++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-) diff --git a/crates/fidc-core/src/data.rs b/crates/fidc-core/src/data.rs index a699e37..f7bf966 100644 --- a/crates/fidc-core/src/data.rs +++ b/crates/fidc-core/src/data.rs @@ -688,7 +688,17 @@ impl AdjustedCloseSeries { end: usize, lookbacks: &[usize; N], ) -> [Option; N] { - std::array::from_fn(|index| self.moving_average_at_end(end, lookbacks[index])) + let mut values = [None; N]; + for index in 0..N { + if let Some(previous) = + (0..index).find(|previous| lookbacks[*previous] == lookbacks[index]) + { + values[index] = values[previous]; + } else { + values[index] = self.moving_average_at_end(end, lookbacks[index]); + } + } + values } fn moving_average_at_end(&self, end: usize, lookback: usize) -> Option { @@ -5399,6 +5409,81 @@ mod tests { ); } + #[test] + #[ignore = "manual release-mode duplicate rolling lookback benchmark"] + fn benchmark_adjusted_close_reuses_duplicate_lookbacks() { + use std::hint::black_box; + use std::time::Instant; + + let data = volume_contract_data(Some([1.0, 1.0, 1.0])); + let symbol_id = data.symbol_id("000001.SZ").expect("symbol id"); + let series = data + .adjusted_close_series_by_symbol_id(symbol_id) + .expect("adjusted close series"); + let lookbacks = [1usize, 2, 3, 1, 2, 3, 1]; + let end = 3usize; + let iterations = 3_000_000usize; + let mut repeated_nanos = 0u128; + let mut reused_nanos = 0u128; + let mut repeated_checksum = 0.0; + let mut reused_checksum = 0.0; + + for sample in 0..6 { + let measure_repeated = || { + let started = Instant::now(); + let mut checksum = 0.0; + for _ in 0..iterations { + let values = std::array::from_fn::<_, 7, _>(|index| { + series.moving_average_at_end(black_box(end), black_box(lookbacks[index])) + }); + checksum += black_box(values).into_iter().flatten().sum::(); + } + (started.elapsed().as_nanos(), checksum) + }; + let measure_reused = || { + let started = Instant::now(); + let mut checksum = 0.0; + for _ in 0..iterations { + checksum += black_box( + series.moving_averages_at_end(black_box(end), black_box(&lookbacks)), + ) + .into_iter() + .flatten() + .sum::(); + } + (started.elapsed().as_nanos(), checksum) + }; + let (baseline, candidate) = if sample % 2 == 0 { + (measure_repeated(), measure_reused()) + } else { + let candidate = measure_reused(); + let baseline = measure_repeated(); + (baseline, candidate) + }; + repeated_nanos += baseline.0; + repeated_checksum += baseline.1; + reused_nanos += candidate.0; + reused_checksum += candidate.1; + } + + assert_eq!(repeated_checksum, reused_checksum); + let repeated_seconds = repeated_nanos as f64 / 1_000_000_000.0; + let reused_seconds = reused_nanos as f64 / 1_000_000_000.0; + eprintln!( + "{}", + serde_json::json!({ + "schemaVersion": "fidc-adjusted-close-duplicate-lookback-benchmark/v1", + "samples": 6, + "iterationsPerSample": iterations, + "lookbacks": lookbacks, + "repeatedSeconds": repeated_seconds, + "reusedSeconds": reused_seconds, + "speedup": repeated_seconds / reused_seconds, + "checksum": repeated_checksum, + }) + ); + } + #[test] #[ignore = "manual component benchmark"] fn benchmark_daily_snapshot_view_lookup() {