perf: index instruments by symbol id
This commit is contained in:
@@ -1301,6 +1301,7 @@ impl BenchmarkPriceSeries {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DataSet {
|
||||
instruments: Arc<HashMap<String, Instrument>>,
|
||||
instruments_by_symbol_id: Arc<Vec<Option<Instrument>>>,
|
||||
calendar: Arc<TradingCalendar>,
|
||||
market_by_date: Arc<BTreeMap<NaiveDate, Vec<DailyMarketSnapshot>>>,
|
||||
market_symbol_ids_by_date: Arc<BTreeMap<NaiveDate, Vec<u32>>>,
|
||||
@@ -1766,6 +1767,12 @@ impl DataSet {
|
||||
&factor_by_date,
|
||||
&candidate_by_date,
|
||||
);
|
||||
let mut instruments_by_symbol_id = vec![None; symbol_id_by_code.len()];
|
||||
for (symbol, instrument) in &instruments {
|
||||
if let Some(symbol_id) = symbol_id_by_code.get(symbol).copied() {
|
||||
instruments_by_symbol_id[symbol_id as usize] = Some(instrument.clone());
|
||||
}
|
||||
}
|
||||
let market_symbol_ids_by_date =
|
||||
build_group_symbol_ids(&market_by_date, &symbol_id_by_code, |item| {
|
||||
item.symbol.as_str()
|
||||
@@ -1818,6 +1825,7 @@ impl DataSet {
|
||||
|
||||
Ok(Self {
|
||||
instruments: Arc::new(instruments),
|
||||
instruments_by_symbol_id: Arc::new(instruments_by_symbol_id),
|
||||
calendar: Arc::new(calendar),
|
||||
market_by_date: Arc::new(market_by_date),
|
||||
market_symbol_ids_by_date: Arc::new(market_symbol_ids_by_date),
|
||||
@@ -1887,6 +1895,12 @@ impl DataSet {
|
||||
self.instruments.get(symbol)
|
||||
}
|
||||
|
||||
pub(crate) fn instrument_by_symbol_id(&self, symbol_id: u32) -> Option<&Instrument> {
|
||||
self.instruments_by_symbol_id
|
||||
.get(symbol_id as usize)?
|
||||
.as_ref()
|
||||
}
|
||||
|
||||
pub fn symbol_id(&self, symbol: &str) -> Option<u32> {
|
||||
self.symbol_id_by_code.get(symbol).copied()
|
||||
}
|
||||
@@ -4888,6 +4902,11 @@ mod tests {
|
||||
for symbol in ["000001.SZ", "600000.SH"] {
|
||||
let symbol_id = data.symbol_id(symbol).unwrap();
|
||||
let day = data.daily_snapshot_view(date);
|
||||
assert_eq!(
|
||||
data.instrument_by_symbol_id(symbol_id)
|
||||
.map(|row| row.symbol.as_str()),
|
||||
Some(symbol)
|
||||
);
|
||||
assert_eq!(
|
||||
data.market_by_symbol_id(date, symbol_id)
|
||||
.map(|row| row.symbol.as_str()),
|
||||
@@ -4923,6 +4942,11 @@ mod tests {
|
||||
assert!(data.factor_by_symbol_id(date, signal_id).is_none());
|
||||
assert!(data.candidate_by_symbol_id(date, signal_id).is_none());
|
||||
assert!(day.candidate(signal_id).is_none());
|
||||
assert_eq!(
|
||||
data.instrument_by_symbol_id(signal_id)
|
||||
.map(|row| row.symbol.as_str()),
|
||||
Some("000300.SH")
|
||||
);
|
||||
|
||||
// `get_factor` must use the same symbol-id index as direct snapshot
|
||||
// lookups. Sparse factor rows must not accidentally select another
|
||||
@@ -4945,6 +4969,81 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "manual release-mode instrument lookup benchmark"]
|
||||
fn benchmark_instrument_symbol_id_lookup() {
|
||||
let rows = (0..6_000u32)
|
||||
.map(|symbol_id| {
|
||||
let symbol = format!("{:06}.SZ", symbol_id);
|
||||
let instrument = Instrument {
|
||||
symbol: symbol.clone(),
|
||||
name: symbol.clone(),
|
||||
board: "SZ".to_string(),
|
||||
round_lot: 100,
|
||||
listed_at: None,
|
||||
delisted_at: None,
|
||||
status: "active".to_string(),
|
||||
};
|
||||
(symbol, symbol_id, instrument)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let map = rows
|
||||
.iter()
|
||||
.map(|(symbol, _, instrument)| (symbol.clone(), instrument.clone()))
|
||||
.collect::<HashMap<_, _>>();
|
||||
let mut dense = vec![None; rows.len()];
|
||||
for (_, symbol_id, instrument) in &rows {
|
||||
dense[*symbol_id as usize] = Some(instrument.clone());
|
||||
}
|
||||
let iterations = 1_000usize;
|
||||
let mut map_nanos = 0u128;
|
||||
let mut dense_nanos = 0u128;
|
||||
let mut map_checksum = 0u64;
|
||||
let mut dense_checksum = 0u64;
|
||||
|
||||
for iteration in 0..iterations {
|
||||
if iteration % 2 == 0 {
|
||||
let started = std::time::Instant::now();
|
||||
for (symbol, _, _) in &rows {
|
||||
map_checksum += map.get(symbol).unwrap().round_lot as u64;
|
||||
}
|
||||
map_nanos += started.elapsed().as_nanos();
|
||||
let started = std::time::Instant::now();
|
||||
for (_, symbol_id, _) in &rows {
|
||||
dense_checksum += dense[*symbol_id as usize].as_ref().unwrap().round_lot as u64;
|
||||
}
|
||||
dense_nanos += started.elapsed().as_nanos();
|
||||
} else {
|
||||
let started = std::time::Instant::now();
|
||||
for (_, symbol_id, _) in &rows {
|
||||
dense_checksum += dense[*symbol_id as usize].as_ref().unwrap().round_lot as u64;
|
||||
}
|
||||
dense_nanos += started.elapsed().as_nanos();
|
||||
let started = std::time::Instant::now();
|
||||
for (symbol, _, _) in &rows {
|
||||
map_checksum += map.get(symbol).unwrap().round_lot as u64;
|
||||
}
|
||||
map_nanos += started.elapsed().as_nanos();
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(map_checksum, dense_checksum);
|
||||
let map_seconds = map_nanos as f64 / 1_000_000_000.0;
|
||||
let dense_seconds = dense_nanos as f64 / 1_000_000_000.0;
|
||||
eprintln!(
|
||||
"{}",
|
||||
serde_json::json!({
|
||||
"schemaVersion": "fidc-instrument-symbol-id-lookup-benchmark/v1",
|
||||
"rows": rows.len(),
|
||||
"iterations": iterations,
|
||||
"mapSeconds": map_seconds,
|
||||
"denseSeconds": dense_seconds,
|
||||
"speedup": map_seconds / dense_seconds,
|
||||
"checksum": map_checksum,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "manual component benchmark"]
|
||||
fn benchmark_daily_snapshot_view_lookup() {
|
||||
|
||||
Reference in New Issue
Block a user