1use subms_arena_allocator::Bump;
13
14#[derive(Copy, Clone)]
15struct Level {
16 price_ticks: u64,
17 qty: u32,
18}
19
20fn main() {
21 base_per_tick_scratch();
22
23 #[cfg(feature = "typed")]
24 typed_snapshot();
25
26 #[cfg(feature = "growable")]
27 growable_deep_book();
28
29 #[cfg(feature = "stats")]
30 stats_sizing();
31
32 #[cfg(feature = "aligned")]
33 aligned_price_scan();
34}
35
36fn base_per_tick_scratch() {
42 println!("== base: per-tick order-book scratch ==");
43 let mut scratch = Bump::with_capacity(4096);
44 let cap = scratch.capacity();
45
46 let ticks: [&[(u64, u32, bool)]; 2] = [
47 &[
48 (9998, 5, true),
49 (9997, 8, true),
50 (10002, 4, false),
51 (10003, 9, false),
52 ],
53 &[(9999, 3, true), (10001, 7, false)],
54 ];
55
56 for (t, updates) in ticks.iter().enumerate() {
57 let (mut best_bid, mut best_ask) = (0u64, u64::MAX);
58 let (mut bid_qty, mut ask_qty) = (0u64, 0u64);
59 for &(price, qty, is_bid) in updates.iter() {
60 let level = scratch.alloc_copy(Level {
61 price_ticks: price,
62 qty,
63 });
64 if is_bid {
65 best_bid = best_bid.max(level.price_ticks);
66 bid_qty += level.qty as u64;
67 } else {
68 best_ask = best_ask.min(level.price_ticks);
69 ask_qty += level.qty as u64;
70 }
71 }
72 let mid = (best_bid + best_ask) / 2;
73 let imbalance = bid_qty as i64 - ask_qty as i64;
74 println!(
75 " tick {t}: {} levels, mid={mid} imbalance={imbalance:+} used={}B",
76 updates.len(),
77 scratch.used(),
78 );
79 assert!(scratch.used() > 0, "levels consumed scratch");
80 scratch.reset();
81 assert_eq!(scratch.used(), 0, "reset rewinds the cursor");
82 assert_eq!(scratch.capacity(), cap, "no reallocation between ticks");
83 }
84 println!(" -> steady-state chunk stays at {cap}B across all ticks");
85}
86
87#[cfg(feature = "typed")]
93fn typed_snapshot() {
94 use subms_arena_allocator::TypedArena;
95 println!("\n== typed: per-tick levels with slot reuse ==");
96 let mut book = TypedArena::<Level>::with_capacity(64);
97 let mut live = Vec::new();
98 for &(price, qty) in &[(9998u64, 5u32), (9997, 8), (10002, 4), (10003, 9)] {
99 live.push(book.alloc(Level {
100 price_ticks: price,
101 qty,
102 }));
103 }
104 let resting: u64 = live.iter().map(|s| book.get(s).qty as u64).sum();
105 let top = live.iter().map(|s| book.get(s).price_ticks).max().unwrap();
106 println!(
107 " {} levels, {resting} resting, top price {top}",
108 book.len()
109 );
110 assert_eq!(book.len(), 4);
111 assert_eq!(resting, 26);
112
113 let cancelled = live.pop().expect("a level to cancel");
114 let freed_index = cancelled.index();
115 book.free(cancelled);
116 let replacement = book.alloc(Level {
117 price_ticks: 10_004,
118 qty: 2,
119 });
120 println!(
121 " cancelled level {freed_index} reused by the replacement: {} ({} reuse hits)",
122 replacement.index() == freed_index,
123 book.reuse_hits(),
124 );
125 assert_eq!(replacement.index(), freed_index, "freed slot came back");
126 assert_eq!(book.reuse_hits(), 1);
127 assert_eq!(book.len(), 4, "cancel + replace is footprint-neutral");
128
129 book.reset();
130 assert!(book.is_empty(), "reset recycles the snapshot storage");
131}
132
133#[cfg(feature = "growable")]
137fn growable_deep_book() {
138 use subms_arena_allocator::GrowableBump;
139 println!("\n== growable: a deep-book tick that outgrows the chunk ==");
140 let mut scratch = GrowableBump::with_capacity(256);
141 for i in 0..200u64 {
142 scratch.alloc_copy(Level {
143 price_ticks: 10_000 + i,
144 qty: 1,
145 });
146 }
147 let grown = scratch.chunk_count();
148 println!(
149 " 200 levels -> {grown} chunks, {}B retained",
150 scratch.total_capacity()
151 );
152 assert!(grown > 1, "deep book forced a grow");
153 scratch.reset();
154 assert_eq!(
155 scratch.chunk_count(),
156 1,
157 "reset keeps only the largest chunk"
158 );
159 let cap_after = scratch.total_capacity();
160 for i in 0..50u64 {
161 scratch.alloc_copy(Level {
162 price_ticks: 10_000 + i,
163 qty: 1,
164 });
165 }
166 assert_eq!(
167 scratch.total_capacity(),
168 cap_after,
169 "steady-state tick is grow-free"
170 );
171}
172
173#[cfg(feature = "stats")]
177fn stats_sizing() {
178 use subms_arena_allocator::StatsBump;
179 println!("\n== stats: size the arena from real load ==");
180 let mut scratch = StatsBump::with_capacity(4096);
181 for _tick in 0..1_000 {
182 for i in 0..8u64 {
183 scratch.alloc_copy(Level {
184 price_ticks: 10_000 + i,
185 qty: 1,
186 });
187 }
188 scratch.reset();
189 }
190 let s = scratch.stats();
191 println!(
192 " {} allocs over 1000 ticks, peak {}B, wasted {}B",
193 s.allocations, s.peak_bytes, s.bytes_wasted,
194 );
195 assert_eq!(s.allocations, 8_000, "counters survive reset");
196 assert!(s.peak_bytes > 0, "peak recorded");
197}
198
199#[cfg(feature = "aligned")]
203fn aligned_price_scan() {
204 use subms_arena_allocator::AlignedBump;
205 println!("\n== aligned: cache-line scratch for a price scan ==");
206 let mut scratch = AlignedBump::with_capacity(1024);
207 let region = scratch.alloc_aligned(64, 64);
208 assert_eq!(region.as_ptr() as usize % 64, 0, "cache-line aligned");
209 for (i, b) in region.iter_mut().enumerate() {
210 *b = i as u8;
211 }
212 let checksum: u32 = region.iter().map(|&b| b as u32).sum();
213 println!(
214 " 64B aligned scratch, checksum {checksum}, used {}B",
215 scratch.used()
216 );
217 assert_eq!(checksum, (0..64u32).sum::<u32>());
218 scratch.reset();
219 assert_eq!(scratch.used(), 0);
220}