Take a census walk seen running for over 2 s as a torn read and charge nothing to it.

The review of c05e6528 found a window in perf.c ft_frame: it loads walk_t0 and then walk_t1, while perf_census_walk(1)
stores walk_t1 = 0 and then the new start. A walk that starts between the two loads leaves ft_frame with the previous
walk's start and an end of 0, so perf_census.h census_charge took that walk as running since then, the whole frame as
overlapped, and moved all its replay and unattributed time to "census". The window is a few nanoseconds, but the error
is a whole frame. A walk takes 100-260 ms, so census_charge now returns 0 for a walk seen running for longer than
CENSUS_WALK_MAX_NS (2 s).

tests/census_charge_check.c gets a torn read (a start 10 s back with an end of 0), a walk running for 1.9 s, which still
counts, and one running for 2.001 s, which does not; its random frames now reach walks more than 2 s old and check the
moved time exactly (the overlap capped by the frame's room and its replay and unattributed parts) instead of only its
bound. Built against the header before this change, the check fails the torn read, the 2.001 s walk and the first such
random frame; with it, it passes. docs/building.md names the new case.

Diagnostics only: the census part exists only with NX2NAVI_MEM_CENSUS = 1. Not reproduced in a run (the window is a
few nanoseconds). perf.c compiles with the dev flags and adds no -Wall -Wextra warnings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
chrg6
2026-11-13 17:50:59 -05:00
parent b7c678540e
commit 28f420bf03
3 changed files with 29 additions and 9 deletions

View File

@@ -723,9 +723,9 @@ and that the lines of older logs, without those fields, still parse. Any Python,
A change to what the frame-time summary charges to an address-space walk of `NX2NAVI_MEM_CENSUS`
(`runtime/perf_census.h` `census_charge`, `runtime/perf.c` `ft_frame`) passes `python tests\census_charge_check.py`,
which builds the header alone: a walk before, after or around a frame, one still running, the charge taken from the
replay part first, capped by the overlap and the parts, and 100,000 random frames in which the census part never passes
the frame and the parts keep their sum. No GPU, no game files.
which builds the header alone: a walk before, after or around a frame, one still running, a torn read of the last walk's
start (taken as no walk), the charge taken from the replay part first, capped by the overlap and the parts, and 100,000
random frames in which the census part never passes the frame and the parts keep their sum. No GPU, no game files.
A change to how the on/off switches read their value (`runtime/perf.c` `rt_knob`) passes `python tests\rt_knob_check.py`,
which cuts the function out of the file with its warning table and builds it with stand-ins for the settings and the

View File

@@ -8,12 +8,18 @@
#pragma once
#include <stdint.h>
// A walk takes 100-260 ms, so one seen running for longer than this is a torn read: perf.c ft_frame loaded the last
// walk's start, then the 0 that perf.c perf_census_walk(1) stores as the end before it stores the next start. Taken as
// running, that walk would cover the whole frame.
#define CENSUS_WALK_MAX_NS 2000000000ull
// The frame is [now - d, now]; the walk [w0, w1]: w0 0 when no walk was seen, w1 0 (or older than w0, the end of the
// walk before) while it runs, then reaching now. Returns the time moved into *census.
static inline uint64_t census_charge(uint64_t now, uint64_t d, uint64_t w0, uint64_t w1, uint64_t *replay,
uint64_t *other, uint64_t *census) {
if (!w0 || !d || d > now) return 0;
if (w1 && w1 < w0) w1 = 0;
if (!w1 && now - w0 > CENSUS_WALK_MAX_NS) return 0;
uint64_t lo = now - d > w0 ? now - d : w0, hi = w1 && w1 < now ? w1 : now;
if (hi <= lo) return 0;
uint64_t ov = hi - lo, room = d > *census ? d - *census : 0;

View File

@@ -46,6 +46,16 @@ int main(void) {
p = charge(1000, 40, 985, 700, 30, 10, 0);
CHECK(p.moved == 15 * MS && p.replay == 15 * MS, "a running walk with the last one's end: moved %llu",
(unsigned long long)p.moved);
// a torn read (the last walk's start, 10 s back, with the 0 stored before the next start) moves nothing; a walk
// running for 1.9 s still counts, one running for just over 2 s (CENSUS_WALK_MAX_NS) does not
p = charge(20000, 40, 10000, 0, 30, 10, 0);
CHECK(p.moved == 0 && p.replay == 30 * MS && p.other == 10 * MS && p.census == 0, "a torn read: moved %llu",
(unsigned long long)p.moved);
p = charge(3000, 40, 1100, 0, 30, 10, 0);
CHECK(p.moved == 40 * MS && p.replay == 0 && p.other == 0 && p.census == 40 * MS,
"a walk running for 1.9 s: moved %llu", (unsigned long long)p.moved);
p = charge(3000, 40, 999, 0, 30, 10, 0);
CHECK(p.moved == 0 && p.census == 0, "a walk running for 2.001 s: moved %llu", (unsigned long long)p.moved);
// the overlap caps the charge; the parts cap it too (nothing taken from the other parts of the frame)
p = charge(1000, 100, 900, 0, 5, 3, 0);
CHECK(p.moved == 8 * MS && p.replay == 0 && p.other == 0 && p.census == 8 * MS, "parts smaller than the overlap: "
@@ -54,17 +64,21 @@ int main(void) {
p = charge(1000, 40, 900, 0, 30, 10, 35);
CHECK(p.moved == 5 * MS && p.census == 40 * MS && p.replay == 25 * MS, "census at the frame's length: moved %llu, "
"census %llu", (unsigned long long)p.moved, (unsigned long long)p.census);
// random frames: the census part never passes the frame, the moved time never passes the overlap, and the sum of
// the three parts is kept
// random frames: the census part never passes the frame, the moved time is the overlap capped by the frame's room
// and its replay and unattributed parts (none for a walk seen running for over 2 s), and the sum of the three parts
// is kept
srand(7);
for (int i = 0; i < 100000; i++) {
uint64_t now = 1000 + (uint64_t)(rand() % 1000), d = 1 + (uint64_t)(rand() % 300);
uint64_t w0 = (uint64_t)(rand() % 2000), w1 = rand() % 3 ? w0 + (uint64_t)(rand() % 400) : 0;
uint64_t now = 1000 + (uint64_t)(rand() % 4000), d = 1 + (uint64_t)(rand() % 300);
uint64_t w0 = (uint64_t)(rand() % 5000), w1 = rand() % 3 ? w0 + (uint64_t)(rand() % 400) : 0;
uint64_t census = (uint64_t)(rand() % (int)d), replay = (uint64_t)(rand() % 300), other = (uint64_t)(rand() % 300);
p = charge(now, d, w0, w1, replay, other, census);
uint64_t lo = now - d > w0 ? now - d : w0, hi = w1 && w1 < now ? w1 : now;
uint64_t ov = w0 && hi > lo ? (hi - lo) * MS : 0;
int ok = p.census <= d * MS && p.moved <= ov && p.replay + p.other + p.census == (replay + other + census) * MS &&
int torn = !w1 && (w0 > now || now - w0 > 2000);
uint64_t ov = w0 && !torn && hi > lo ? (hi - lo) * MS : 0, want = ov;
if (want > (d - census) * MS) want = (d - census) * MS;
if (want > (replay + other) * MS) want = (replay + other) * MS;
int ok = p.census <= d * MS && p.moved == want && p.replay + p.other + p.census == (replay + other + census) * MS &&
p.census == census * MS + p.moved && (p.other == other * MS || p.replay == 0);
CHECK(ok, "case %d: now %llu d %llu walk %llu-%llu: moved %llu of overlap %llu, census %llu", i,
(unsigned long long)now, (unsigned long long)d, (unsigned long long)w0, (unsigned long long)w1,