7 · The number Draft

The strategy loop

3 min read

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
            }
        }
    }
}