Skip to main content

perf_features/
perf_features.rs

1//! Feature classification bench. Each feature's representative op is swept
2//! across three RESIDENT-TIMER counts, `classify_feature` DECIDES the category
3//! from the shape of that sweep, and the decision plus a measured `p99ByStage`
4//! is merge-written into `.subms/features/rust.json`.
5//!
6//! Resident timers is the sweep axis because it is the only thing that can make
7//! a wheel op superlinear: `schedule` and the base wheel's `cancel` are O(1) by
8//! construction, and `tick` walks one bucket, whose occupancy is
9//! resident/slots. The slot count is held fixed throughout so a slope has one
10//! cause.
11//!
12//! The workload is built so the tick measurement is honest:
13//!
14//!   - DUE_PER_TICK timers fire on EVERY measured tick. Ticking a wheel with
15//!     nothing due measures an empty bucket walk and would publish the cheap
16//!     path.
17//!   - The due rate is FIXED across sweep points. Scheduling the resident
18//!     population uniformly over the horizon instead would scale the fire rate
19//!     with N, and the sweep would be reading the workload, not the wheel.
20//!   - The resident population is scheduled BEYOND the measured window, so it
21//!     never fires and occupancy holds constant for the whole run.
22//!
23//! Run:
24//!   cargo run --release --example perf_features \
25//!       --features "harness hierarchical concurrent deadline-scheduler cron metrics"
26
27use std::collections::BTreeMap;
28use std::io::{self, Write};
29use std::path::PathBuf;
30
31use subms::{SubMsFeatureManifest, SubMsP99Source, SubMsPerfHarness, classify_feature, summarize};
32use subms_timer_wheel::TimerWheel;
33
34/// Resident (scheduled, not yet due) timers. A 16x span starting at 32768: the
35/// base wheel's bucket holds resident/SLOTS entries, so the smallest point
36/// already walks 128 of them and the per-call cost does not dominate the walk.
37const SIZES: [usize; 3] = [32_768, 131_072, 524_288];
38const CANON: usize = SIZES[SIZES.len() - 1];
39/// 256 and not the 1024 the standing bench uses. A tick's cost is a fixed
40/// per-call part (take the bucket, rebuild the survivors vec) plus a per-entry
41/// part, and the sweep only measures the second once occupancy is large enough
42/// to dominate. At 1024 slots the smallest sweep point walks 32 entries, the
43/// fixed part is two thirds of it, and the ratio compresses to the point where
44/// the Java port's `poll` measured exactly 8.5x over 16x - the classifier's
45/// threshold, decided by a rounding. 256 slots quadruples occupancy at the same
46/// resident count, which is the cheap way to start the sweep an octave up.
47const SLOTS: usize = 256;
48/// Timers due on each tick. Fixed across sweep points - see the module note.
49const DUE_PER_TICK: usize = 1;
50/// Untimed ticks before the measured window. Large enough that the Java port's
51/// tick path is fully C2-compiled before the first sweep point is measured - a
52/// point measured while still interpreted reads SLOW, which the sweep sees as a
53/// curve that falls with size. Kept identical in both ports so the workload is
54/// the same.
55const WARM_TICKS: usize = 20_000;
56const TIMED_TICKS: usize = 4_096;
57const DUE_TICKS: usize = WARM_TICKS + TIMED_TICKS;
58/// Resident delays sit above the measured window and below the hierarchical
59/// wheel's 262144-tick capacity, so one workload builder drives both wheels.
60const HORIZON: usize = 262_000;
61/// Timed reps for a keyed op. Kept well under the smallest sweep point: the
62/// timed schedules add to the resident population, and at 10k against 32768
63/// that inflation is small enough not to flatten the size axis.
64const OPS: usize = 10_000;
65/// Untimed reps before a keyed measurement, run against a scratch instance so
66/// the warm-up does not itself change the resident count being swept.
67const WARM_OPS: usize = 50_000;
68/// Ops per timed sample in a SWEEP. A single `schedule` costs tens of
69/// nanoseconds and this platform's timer quantum is 100 ns, so an unbatched p50
70/// is pinned to one or two quanta and the category is decided by rounding: two
71/// runs of unchanged code put `metrics/schedule` at p50 100 and p50 300, which
72/// classified auxiliary and then hot-path. 64 and not 16 because the base op at
73/// 16 still measures 800 ns - one quantum is 12% of that, which is the whole
74/// margin the classifier's base-delta test works in, and `metrics` flapped
75/// across it. At 64 the quantum is under 3% of the sample. Every sweep and the
76/// base op use the same batch, so the base-delta test compares like with like;
77/// `p99ByStage` is measured separately at batch 1 and stays a true per-op
78/// figure.
79const BATCH: usize = 64;
80/// Timed reps for a whole-structure op, far too slow to run OPS times. 256 and
81/// not 32: at 32 reps the reported p99 IS the max, so one preemption of a 736 us
82/// cancel published 5.2 ms and moved run to run.
83const BULK_REPS: usize = 256;
84/// Bulk warm-up is TIME-BOXED, not a fixed rep count. Rust has no JIT, but a
85/// wheel op allocates (a fresh survivors vec per tick, a fired vec per drain)
86/// and the allocator ramp does not settle in a handful of reps.
87const BULK_WARM_NANOS: u64 = 300_000_000;
88const BULK_WARM_MAX_REPS: usize = 5_000;
89
90const TICK_NS: u64 = 1_000_000;
91
92#[derive(Clone, Copy, Default)]
93struct M {
94    p50: u64,
95    p99: u64,
96    max: u64,
97}
98
99fn stat(h: &SubMsPerfHarness) -> M {
100    summarize(h)
101        .stages
102        .iter()
103        .find(|s| s.name == "op")
104        .map_or(M::default(), |s| M {
105            p50: s.p50_ns,
106            p99: s.p99_ns,
107            max: s.max_ns,
108        })
109}
110
111/// Delay for the j-th resident timer. Above the measured window so it never
112/// fires, spread evenly so bucket occupancy is uniform across the wheel.
113fn resident_delay(j: usize) -> usize {
114    let span = HORIZON - DUE_TICKS - 1;
115    DUE_TICKS + 1 + (j % span)
116}
117
118/// Loads a wheel with the due stream and `n` resident timers via the caller's
119/// schedule adapter, which is all the two wheel types differ by here.
120fn load<W>(w: &mut W, n: usize, mut sched: impl FnMut(&mut W, usize)) {
121    for t in 1..=DUE_TICKS {
122        for _ in 0..DUE_PER_TICK {
123            sched(w, t);
124        }
125    }
126    for j in 0..n {
127        sched(w, resident_delay(j));
128    }
129}
130
131/// A per-op measurement. `warm` runs against a scratch instance: warming on the
132/// measured instance would add WARM_OPS entries to the resident population and
133/// compress the size axis at the small end of the sweep.
134///
135/// The warm-up goes THROUGH the harness's timed wrapper, not around it. Warming
136/// the op alone leaves the wrapper itself cold, and at batch 64 a measurement
137/// only enters it OPS/64 times - 156, far short of what the Java port needs to
138/// compile it. That showed up as the first keyed measurements of a run reading
139/// 5400 ns and later ones 1600 ns, and as `concurrent/schedule` sweeping
140/// DOWNWARD across sizes, which is the under-warm signature rather than a
141/// feature that gets cheaper with more timers.
142fn keyed(batch: usize, mut warm: impl FnMut(usize), mut op: impl FnMut(usize)) -> M {
143    let mut wh = SubMsPerfHarness::new("timer-feature-warm", "rust");
144    let wst = wh.stage("op", WARM_OPS);
145    for i in 0..WARM_OPS {
146        wst.time(|| warm(i));
147    }
148    let samples = OPS / batch;
149    let mut h = SubMsPerfHarness::new("timer-feature", "rust");
150    let st = h.stage("op", samples);
151    for s in 0..samples {
152        let first = s * batch;
153        st.time(|| {
154            for k in 0..batch {
155                op(first + k);
156            }
157        });
158    }
159    stat(&h)
160}
161
162/// Ticks a loaded wheel. The warm ticks are untimed and the due stream covers
163/// them, so the measured region sees the same fire rate and the same occupancy
164/// as the warm region.
165fn drain<W>(batch: usize, mut w: W, mut tick: impl FnMut(&mut W)) -> M {
166    for _ in 0..WARM_TICKS {
167        tick(&mut w);
168    }
169    let samples = TIMED_TICKS / batch;
170    let mut h = SubMsPerfHarness::new("timer-feature", "rust");
171    let st = h.stage("op", samples);
172    for _ in 0..samples {
173        st.time(|| {
174            for _ in 0..batch {
175                tick(&mut w);
176            }
177        });
178    }
179    stat(&h)
180}
181
182/// A whole-structure op, repeated against one input built outside the timed
183/// region. Only safe for a NON-destructive op - every use here is a cancel of
184/// an id that does not exist, which walks the same buckets every rep.
185fn bulk<W>(mut w: W, mut op: impl FnMut(&mut W)) -> M {
186    let start = std::time::Instant::now();
187    for _ in 0..BULK_WARM_MAX_REPS {
188        op(&mut w);
189        if start.elapsed().as_nanos() as u64 >= BULK_WARM_NANOS {
190            break;
191        }
192    }
193    let mut h = SubMsPerfHarness::new("timer-feature", "rust");
194    let st = h.stage("op", BULK_REPS);
195    for _ in 0..BULK_REPS {
196        st.time(|| op(&mut w));
197    }
198    stat(&h)
199}
200
201/// Sweeps and PRINTS the curve, p50 / p99 / max at every point. The classifier
202/// reads p50; the other two are here because a ratio-compressed or
203/// non-monotonic curve classifies flat and the only way to catch one is to look
204/// at the rows.
205fn sweep(label: &str, mut at: impl FnMut(usize) -> M) -> Vec<(usize, u64)> {
206    let ms: Vec<(usize, M)> = SIZES.iter().map(|&n| (n, at(n))).collect();
207    let cells: Vec<String> = ms
208        .iter()
209        .map(|(n, m)| format!("({n}: p50 {} p99 {} max {})", m.p50, m.p99, m.max))
210        .collect();
211    eprintln!("sweep {label}: {}", cells.join(" "));
212    ms.iter().map(|(n, m)| (*n, m.p50)).collect()
213}
214
215fn base_wheel(n: usize) -> TimerWheel<u32> {
216    let mut w: TimerWheel<u32> = TimerWheel::new(SLOTS);
217    load(&mut w, n, |w, d| {
218        w.schedule(d, 0);
219    });
220    w
221}
222
223/// The baseline: base `schedule`, the O(1) per-op write every feature either
224/// decorates or replaces. Re-measured immediately before EACH feature is
225/// classified rather than once at the top. Measured once, it sits several
226/// half-million-timer builds away from the feature it is compared against, and
227/// on this host that gap moves it between 3000 and 4300 ns run to run - as large
228/// as a real feature delta. `metrics`, whose entire cost is one u64 increment,
229/// flipped between auxiliary and hot-path on that drift alone; measured
230/// adjacent, both runs land on auxiliary.
231fn base_p50() -> u64 {
232    let mut scratch: TimerWheel<u32> = TimerWheel::new(SLOTS);
233    let mut w = base_wheel(CANON);
234    let m = keyed(
235        BATCH,
236        |i| {
237            scratch.schedule(resident_delay(i), 0);
238        },
239        |i| {
240            w.schedule(resident_delay(i), 0);
241        },
242    );
243    eprintln!("base schedule: p50 {} p99 {} max {}", m.p50, m.p99, m.max);
244    m.p50
245}
246
247fn main() -> io::Result<()> {
248    let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
249        .join("..")
250        .join(".subms")
251        .join("features")
252        .join("rust.json");
253    let existing = std::fs::read_to_string(&path).unwrap_or_default();
254    let mut manifest = SubMsFeatureManifest::load_str("rust", &existing);
255    // Stamp the box these numbers came from. The bench runs wherever it is
256    // invoked, so an unstamped manifest is indistinguishable from a fleet
257    // capture; the renderer will not publish one it cannot attribute.
258    let (source, instance) = SubMsP99Source::from_env();
259    manifest.set_p99_source(source, instance.as_deref());
260
261    // Diagnostic, not a feature: the base wheel's own tick. A single-level
262    // wheel decrements the rounds counter of every entry in the bucket it
263    // walks, fired or not, so its tick is O(resident/slots) - the cost the
264    // hierarchical feature exists to remove. Printed so the feature curves
265    // below have something to be read against.
266    sweep("base/tick", |n| {
267        drain(BATCH, base_wheel(n), |w| {
268            let _ = w.tick();
269        })
270    });
271
272    // ---------- hierarchical: cascade across three 64-slot wheels ----------
273    #[cfg(feature = "hierarchical")]
274    {
275        use subms_timer_wheel::HierarchicalTimerWheel;
276
277        fn hier(n: usize) -> HierarchicalTimerWheel<u32> {
278            let mut w: HierarchicalTimerWheel<u32> = HierarchicalTimerWheel::new();
279            load(&mut w, n, |w, d| {
280                w.schedule(d as u64, 0);
281            });
282            w
283        }
284
285        // Swept on `tick`, the op the feature transforms. The cascade is the
286        // expensive path and it fires on 1 tick in 64 (level 1) and 1 in 4096
287        // (level 2), so the measured window has to be long enough to contain
288        // both: 4096 timed ticks contains 64 level-1 cascades and one level-2.
289        //
290        // The curve is flat, and that is the correct reading rather than a
291        // hidden cost: a cascade moves the entries in ONE coarse bucket, which
292        // holds the timers due in the next 64 (level 1) or 4096 (level 2)
293        // ticks. With the due rate held fixed that bucket's size is fixed too,
294        // so resident timers further out cost the tick nothing. This is exactly
295        // what the level structure buys - the base wheel's own tick, printed
296        // above, walks resident/slots entries on EVERY tick.
297        let sw = sweep("hierarchical/tick", |n| {
298            drain(BATCH, hier(n), |w| {
299                let _ = w.tick();
300            })
301        });
302
303        // `cancel` is the O(resident) op the feature introduces. It has no
304        // id->slot index (the base wheel's index would need patching on every
305        // cascade) so it sweeps all 192 buckets and every entry in them.
306        // Cancelling a MISS walks all of them and is non-destructive, which is
307        // what makes it safe to repeat against one input.
308        sweep("hierarchical/cancel-miss", |n| {
309            bulk(hier(n), |w| {
310                let _ = w.cancel(u64::MAX);
311            })
312        });
313
314        // PINNED structural on the strength of `cancel`, not of the swept op.
315        // From the source, `HierarchicalTimerWheel::cancel` iterates
316        // LEVELS * SLOTS buckets and every entry in each until it matches, so
317        // it is O(resident) - measured 29x over a 16x sweep, 1.0 ms p99 at
318        // 524288 resident. The base wheel does not have that op shape: it keeps
319        // an id->slot map and cancels in O(bucket). Classifying the feature
320        // hot-path off a flat `tick` would tell a reader every op it introduces
321        // is safe per-operation, and one of them lands on the millisecond line
322        // at half a million timers.
323        let (cat, reason) = classify_feature(
324            &sw,
325            Some(base_p50()),
326            Some(subms::SubMsFeatureCategory::Structural),
327        );
328
329        let mut p99 = BTreeMap::new();
330        p99.insert(
331            "tick".to_string(),
332            drain(1, hier(CANON), |w| {
333                let _ = w.tick();
334            })
335            .p99,
336        );
337        p99.insert(
338            "schedule".to_string(),
339            {
340                let mut scratch: HierarchicalTimerWheel<u32> = HierarchicalTimerWheel::new();
341                let mut w = hier(CANON);
342                keyed(
343                    1,
344                    |i| {
345                        scratch.schedule(resident_delay(i) as u64, 0);
346                    },
347                    |i| {
348                        w.schedule(resident_delay(i) as u64, 0);
349                    },
350                )
351            }
352            .p99,
353        );
354        p99.insert(
355            "cancel".to_string(),
356            bulk(hier(CANON), |w| {
357                let _ = w.cancel(u64::MAX);
358            })
359            .p99,
360        );
361        manifest.set_feature("hierarchical", cat, &p99, &reason);
362    }
363
364    // ---------- concurrent: short-mutex wrapper ----------
365    #[cfg(feature = "concurrent")]
366    {
367        use subms_timer_wheel::ConcurrentTimerWheel;
368
369        fn conc(n: usize) -> ConcurrentTimerWheel<u32> {
370            let mut w: ConcurrentTimerWheel<u32> = ConcurrentTimerWheel::new(SLOTS);
371            load(&mut w, n, |w, d| {
372                w.schedule(d, 0);
373            });
374            w
375        }
376
377        // Swept on `schedule` and measured single-threaded. The feature adds a
378        // lock acquire and release to every op; running it contended would
379        // measure the contention instead of the indirection, and the thread
380        // count would then be a second thing varying across the sweep.
381        let sw = sweep("concurrent/schedule", |n| {
382            let scratch: ConcurrentTimerWheel<u32> = ConcurrentTimerWheel::new(SLOTS);
383            let w = conc(n);
384            keyed(
385                BATCH,
386                |i| {
387                    scratch.schedule(resident_delay(i), 0);
388                },
389                |i| {
390                    w.schedule(resident_delay(i), 0);
391                },
392            )
393        });
394        let (cat, reason) = classify_feature(&sw, Some(base_p50()), None);
395
396        let mut p99 = BTreeMap::new();
397        p99.insert(
398            "schedule".to_string(),
399            {
400                let scratch: ConcurrentTimerWheel<u32> = ConcurrentTimerWheel::new(SLOTS);
401                let w = conc(CANON);
402                keyed(
403                    1,
404                    |i| {
405                        scratch.schedule(resident_delay(i), 0);
406                    },
407                    |i| {
408                        w.schedule(resident_delay(i), 0);
409                    },
410                )
411            }
412            .p99,
413        );
414        p99.insert(
415            "tick".to_string(),
416            drain(1, conc(CANON), |w| {
417                let _ = w.tick();
418            })
419            .p99,
420        );
421        manifest.set_feature("concurrent", cat, &p99, &reason);
422    }
423
424    // ---------- deadline-scheduler: absolute deadlines over an injected clock ----------
425    #[cfg(feature = "deadline-scheduler")]
426    {
427        use std::cell::Cell;
428        use std::rc::Rc;
429        use std::time::Duration;
430        use subms_timer_wheel::{Clock, DeadlineScheduler};
431
432        /// Time only moves when the bench moves it. A free-running clock makes
433        /// `poll` tick however many ticks the host happened to take, which is
434        /// neither repeatable nor comparable across sweep points; a frozen one
435        /// makes `poll` a no-op and publishes an empty drain as the cost.
436        struct StepClock {
437            now: Cell<u64>,
438            step: Cell<u64>,
439        }
440        struct Shared(Rc<StepClock>);
441        impl Clock for Shared {
442            fn now_nanos(&self) -> u64 {
443                self.0.now.set(self.0.now.get() + self.0.step.get());
444                self.0.now.get()
445            }
446        }
447
448        fn sched(n: usize) -> (DeadlineScheduler<u32, Shared>, Rc<StepClock>) {
449            let clock = Rc::new(StepClock {
450                now: Cell::new(0),
451                step: Cell::new(0),
452            });
453            let mut s: DeadlineScheduler<u32, Shared> = DeadlineScheduler::new(
454                SLOTS,
455                Shared(Rc::clone(&clock)),
456                Duration::from_nanos(TICK_NS),
457            );
458            load(&mut s, n, |s, d| {
459                s.schedule_at(d as u64 * TICK_NS, 0);
460            });
461            (s, clock)
462        }
463
464        // Swept on `poll`, the op the layer introduces. With the clock stepped
465        // exactly one tick per call, a poll is one wheel tick plus the deadline
466        // arithmetic, so the sweep reads the drain the layer is driving.
467        let sw = sweep("deadline-scheduler/poll", |n| {
468            let (s, clock) = sched(n);
469            clock.step.set(TICK_NS);
470            drain(BATCH, s, |s| {
471                let _ = s.poll();
472            })
473        });
474        let (cat, reason) = classify_feature(&sw, Some(base_p50()), None);
475
476        let mut p99 = BTreeMap::new();
477        p99.insert(
478            "schedule_at".to_string(),
479            {
480                let (mut scratch, _sc) = sched(0);
481                let (mut s, _c) = sched(CANON);
482                keyed(
483                    1,
484                    |i| {
485                        scratch.schedule_at(resident_delay(i) as u64 * TICK_NS, 0);
486                    },
487                    |i| {
488                        s.schedule_at(resident_delay(i) as u64 * TICK_NS, 0);
489                    },
490                )
491            }
492            .p99,
493        );
494        p99.insert(
495            "poll".to_string(),
496            {
497                let (s, clock) = sched(CANON);
498                clock.step.set(TICK_NS);
499                drain(1, s, |s| {
500                    let _ = s.poll();
501                })
502            }
503            .p99,
504        );
505        manifest.set_feature("deadline-scheduler", cat, &p99, &reason);
506    }
507
508    // ---------- cron: 5-field expression parser + next-fire search ----------
509    #[cfg(feature = "cron")]
510    {
511        use subms_timer_wheel::{CronSchedule, CronScheduler};
512        const EXPR: &str = "*/5 * * * *";
513        const EPOCH0: u64 = 1_704_067_200;
514
515        // Swept on `next_fire`, the op the feature introduces. It searches
516        // forward minute by minute from a rolling epoch and never touches a
517        // wheel, so it is expected to read FLAT against resident timers - that
518        // is the correct result for this feature, not a broken sweep.
519        let sw = sweep("cron/next_fire", |_n| {
520            let mut warm =
521                CronScheduler::new(CronSchedule::parse(EXPR).expect("valid cron expr"), EPOCH0);
522            let mut warm_epoch = EPOCH0;
523            let mut cs =
524                CronScheduler::new(CronSchedule::parse(EXPR).expect("valid cron expr"), EPOCH0);
525            let mut epoch = EPOCH0;
526            keyed(
527                BATCH,
528                |_| {
529                    if let Some(n) = warm.next_fire(warm_epoch) {
530                        warm.record_fire(n);
531                        warm_epoch = n;
532                    }
533                },
534                |_| {
535                    let next = cs.next_fire(epoch);
536                    if let Some(n) = next {
537                        cs.record_fire(n);
538                        epoch = n;
539                    }
540                },
541            )
542        });
543        let (cat, reason) = classify_feature(&sw, Some(base_p50()), None);
544
545        let mut p99 = BTreeMap::new();
546        p99.insert(
547            "parse".to_string(),
548            keyed(
549                1,
550                |_| {
551                    let _ = CronSchedule::parse(EXPR);
552                },
553                |_| {
554                    let _ = CronSchedule::parse(EXPR);
555                },
556            )
557            .p99,
558        );
559        p99.insert(
560            "next_fire".to_string(),
561            {
562                let mut cs =
563                    CronScheduler::new(CronSchedule::parse(EXPR).expect("valid cron expr"), EPOCH0);
564                let mut epoch = EPOCH0;
565                let mut warm =
566                    CronScheduler::new(CronSchedule::parse(EXPR).expect("valid cron expr"), EPOCH0);
567                let mut warm_epoch = EPOCH0;
568                keyed(
569                    1,
570                    |_| {
571                        if let Some(n) = warm.next_fire(warm_epoch) {
572                            warm.record_fire(n);
573                            warm_epoch = n;
574                        }
575                    },
576                    |_| {
577                        let next = cs.next_fire(epoch);
578                        if let Some(n) = next {
579                            cs.record_fire(n);
580                            epoch = n;
581                        }
582                    },
583                )
584            }
585            .p99,
586        );
587        manifest.set_feature("cron", cat, &p99, &reason);
588    }
589
590    // ---------- metrics: per-instance counters ----------
591    #[cfg(feature = "metrics")]
592    {
593        use subms_timer_wheel::MeteredTimerWheel;
594
595        fn metered(n: usize) -> MeteredTimerWheel<u32> {
596            let mut w: MeteredTimerWheel<u32> = MeteredTimerWheel::new(SLOTS);
597            load(&mut w, n, |w, d| {
598                w.schedule(d, 0);
599            });
600            w
601        }
602
603        // Swept on `schedule`. The counters are the feature and they sit on the
604        // per-op path; sweeping `tick` instead would measure the base wheel's
605        // bucket walk and attribute it to a pair of u64 increments. The tick
606        // number is still recorded below so it is visible.
607        let sw = sweep("metrics/schedule", |n| {
608            let mut scratch: MeteredTimerWheel<u32> = MeteredTimerWheel::new(SLOTS);
609            let mut w = metered(n);
610            keyed(
611                BATCH,
612                |i| {
613                    scratch.schedule(resident_delay(i), 0);
614                },
615                |i| {
616                    w.schedule(resident_delay(i), 0);
617                },
618            )
619        });
620        // PINNED auxiliary. From the source, `MeteredTimerWheel::schedule` is
621        // one non-atomic increment of an owned u64 field followed by the base
622        // call - no allocation, no branch, no lock. That is well under a
623        // nanosecond against a ~55 ns schedule, and nothing on this host
624        // resolves half a percent: the base op's own p50 spreads 3300-4400 ns
625        // per 64-op sample across runs, and the feature crossed the classifier's
626        // 10% band in both directions on four consecutive runs of unchanged
627        // code. Pinning states that a human read the source instead of
628        // publishing a coin toss as a measurement.
629        let (cat, reason) = classify_feature(
630            &sw,
631            Some(base_p50()),
632            Some(subms::SubMsFeatureCategory::Auxiliary),
633        );
634
635        let mut p99 = BTreeMap::new();
636        p99.insert(
637            "schedule".to_string(),
638            {
639                let mut scratch: MeteredTimerWheel<u32> = MeteredTimerWheel::new(SLOTS);
640                let mut w = metered(CANON);
641                keyed(
642                    1,
643                    |i| {
644                        scratch.schedule(resident_delay(i), 0);
645                    },
646                    |i| {
647                        w.schedule(resident_delay(i), 0);
648                    },
649                )
650            }
651            .p99,
652        );
653        p99.insert(
654            "tick".to_string(),
655            drain(1, metered(CANON), |w| {
656                let _ = w.tick();
657            })
658            .p99,
659        );
660        manifest.set_feature("metrics", cat, &p99, &reason);
661    }
662
663    std::fs::create_dir_all(path.parent().unwrap())?;
664    std::fs::write(&path, manifest.to_json())?;
665    io::stdout().write_all(manifest.to_json().as_bytes())?;
666    Ok(())
667}