7 · The number Draft
The strategy loop
On this page
7.8 The loop, line by line
The strategy thread’s loop is forty lines, and every one of them is there because an earlier chapter put a price on doing it some other way. Below is the function the strategy thread runs on core 3, as the repository has it, with its use lines left out. The first line inside the loop is the hook for --stall-every, a fault the harness injects at the top of the loop, which does nothing unless the flag is given. The function takes the book and the queue as type parameters, B and Q, by default the ArrayBook and the ring, so that each swap changes one piece and no line of the loop; the book chapter’s FeedHandler is built field by field so that what it holds is on the page. Three small types come from lib.rs: Run, what the main thread prepared before spawning the thread; Hists, the histograms and the list of the worst 100; and Batch, a block of preallocated arrays. A line under #[cfg(feature = "stages")] exists only in the build with the stages feature, the breakdown build described with the capstone.
pub fn strategy<B: NewBook, Q: QueueRx>(mut rx: Q, // data-structures chapter: in, from core 2
mut tx: Producer<StampedOrder, 1024>, // data-structures chapter: out, to core 6
mut run: Run) -> Report { // this chapter: what main prepared
if let Some(id) = run.core { assert!(core_affinity::set_for_current(CoreId { id })); } // OS chapter: pin, then allocate
let mut handler = FeedHandler { book: B::new(run.base), // book chapter: 65,536 levels a side
state: BookState::Live, next_seq: run.first_seq, // book chapter: the file starts in order
buffer: Vec::with_capacity(4096) }; // book chapter: the gap buffer, reserved now
let mut h = Hists::new(); // this chapter: histograms, worst 100
let mut b = Batch::boxed(); // this chapter: stamps for 4,096 messages
loop { // threads chapter: spin, never sleep
run.stall(); // this chapter: --stall-every, off unless given
let fin = run.done.load(Acquire); // threads chapter: the flag, then the ring
let (mut n, mut top) = (0, None); // data-structures chapter: a new batch
rx.drain(|s| { // data-structures chapter: all, publish once
#[cfg(feature = "stages")] { b.t2[n] = now(); b.t1[n] = s.t1; } // this chapter: t2
top = handler.on_update(s.u).or(top); // book chapter: check, apply, compare
b.t0[n] = s.t0; b.seq[n] = s.u.seq; n += 1; // this chapter: a store, not a record
});
if n == 0 && fin { return Report { h, state: handler.state, log: run.log }; } // threads chapter: done
if n == 0 { spin_loop(); continue; } // threads chapter: pause, look again
if !matches!(handler.state, BookState::Live) { top = None; } // book chapter: quote only if Live
let orders = top.map_or([None, None], |t| run.quoter.on_top(t)); // book chapter: one quote
let t3 = now(); // this chapter: the decision, once a batch
for o in orders.into_iter().flatten() { // book chapter: zero, one or two orders
if tx.try_push(StampedOrder { t3, o }).is_err() { h.dropped += 1; } // data-structures chapter
}
run.log.push(b.seq[n - 1], handler.book.top(), run.quoter.resting); // this chapter: decisions file
for i in 0..n { // this chapter: records, after t3
if b.seq[i] < run.warm_seq { continue; } // this chapter: warm-up stays out
let total = run.ns(t3, b.t0[i]); // this chapter: wire to decision, ns
h.total.saturating_record(total); // this chapter: clamps, never allocates
h.worst.offer(total, b.seq[i]); // this chapter: keep the slow ones' seqs
#[cfg(feature = "stages")] { // this chapter: the breakdown build only
h.decode.saturating_record(run.ns(b.t1[i], b.t0[i]), b.seq[i]); // this chapter: t1 − t0
h.hop.saturating_record(run.ns(b.t2[i], b.t1[i]), b.seq[i]); // this chapter: t2 − t1
h.think.saturating_record(run.ns(t3, b.t2[i]), b.seq[i]); // this chapter: t3 − t2
}
}
}
}