The bus is 4x idle while the CPU is pinned: price the trade
The codec was designed when bytes were scarce, so every decision in it trades
cycles to save bytes. That is now backwards: sasi spends 110 KB/s of a 488 KB/s
pipe while missing 31% of frames on CPU.
The cheapest thing a 68000 can be handed is the most expensive thing to store.
Measured, per pixel: row-linear copy from word-expanded memory 9.08 cycles,
block-order 12.98, V1 codebook 18.74, RAW byte literals 25.03. So the 1024-byte
stride costs 43% and unpacking bytes to words costs more than the write itself.
Pricing one new mode -- a per-row span of word-expanded literals movem.l'd
straight from the stream buffer -- against the UNCHANGED mode maps:
sasi median 74.4% -> 43.0%, worst 136.2% -> 106.2%, misses 37 -> 8/120,
101.7 -> 453.2 KB/s
scsi median 94.9% -> 69.4%, misses 51 -> 18/120, 272 -> 479.7 KB/s
scsi gains less precisely because it has less idle bandwidth left to trade.
Two consequences worth flagging. A word-expanded literal block derives to ~240
cycles, cheaper than V1's measured 299.9 and pixel-exact -- so every codebook
mode is CPU-dominated by a literal, and the codebook is a byte optimisation
that now costs cycles. And 28.5's "a scene cut cannot fit at 12fps" reopens:
CPU needs >=19% of the frame as spans, the bus allows <=39%, and that interval
is not empty.
DERIVED, NOT MEASURED, and labelled as such everywhere. The 9.08 cycles/pixel
is real but was measured at full row width with 12-register bursts, so short
spans are flattered. Measuring one span on the 68000 is now step 0 of the next
session, ahead of the cost-aware mode decision, because it changes the mode set
that decision optimises over.
FINDINGS 29. tools/analysis/12_span_tradeoff.py.
Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
@@ -1423,3 +1423,114 @@ Caveat: this ordering is a property of *this* decoder, not of the codec. V4's
|
|||||||
cost is four indexed `movem.l` lookups; pairing sub-block rows into
|
cost is four indexed `movem.l` lookups; pairing sub-block rows into
|
||||||
`movem.l d0/d2,(a4)` would save ~16 of 448 cycles, which narrows the gap to RAW
|
`movem.l d0/d2,(a4)` would save ~16 of 448 cycles, which narrows the gap to RAW
|
||||||
without closing it.
|
without closing it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 29. Trading bytes for cycles: the bus has 4x the headroom the CPU has (session 7)
|
||||||
|
|
||||||
|
> **STATUS: DERIVED, NOT MEASURED.** No 68000 has executed a span decoder. The
|
||||||
|
> per-pixel figure it rests on *is* measured (FINDINGS 24 V1) but at full row
|
||||||
|
> width; the per-span overhead is hand-derived. Treat every number below as a
|
||||||
|
> hypothesis with a test attached, not as a result. FINDINGS 4 is why.
|
||||||
|
|
||||||
|
FINDINGS 28 leaves the project CPU-bound while the **bus sits 4x idle**: `sasi`
|
||||||
|
spends 110 KB/s of a 488 KB/s pipe. That asymmetry is exploitable, because the
|
||||||
|
codec was designed when bytes were the scarce thing and every one of its
|
||||||
|
decisions trades cycles to save them.
|
||||||
|
|
||||||
|
### 29.1 The decoder pays per changed PIXEL; the disk pays per BYTE
|
||||||
|
Per-pixel costs, all measured:
|
||||||
|
|
||||||
|
| what | cycles/pixel | source |
|
||||||
|
|---|---:|---|
|
||||||
|
| write-only floor (no source read) | 4.59 | FINDINGS 24 V3 |
|
||||||
|
| **row-linear copy from word-expanded RAM** | **9.08** | FINDINGS 24 V1 |
|
||||||
|
| block-order copy, same bytes | 12.98 | FINDINGS 24 V4 |
|
||||||
|
| V1 codebook block | 18.74 | FINDINGS 28.2 |
|
||||||
|
| RAW, byte literals unpacked to words | 25.03 | FINDINGS 28.2 |
|
||||||
|
| naive per-pixel byte expansion | 26.13 | FINDINGS 24 V2 |
|
||||||
|
|
||||||
|
Two structural facts fall out. **The 1024-byte stride costs 43%** — the same
|
||||||
|
bytes cost 12.98 cycles/px in 4x4 block order against 9.08 row-linear, because
|
||||||
|
the stride breaks the `movem.l` burst. And **unpacking bytes to words costs more
|
||||||
|
than the write itself**: 25.03 against 9.08.
|
||||||
|
|
||||||
|
So the two cheapest things a decoder can be handed are *word-expanded* pixels
|
||||||
|
in *row-linear runs* — and both cost bytes on disc, which is what we have.
|
||||||
|
|
||||||
|
### 29.2 Codebooks are a byte optimisation that now costs cycles
|
||||||
|
A word-expanded literal 4x4 block, `movem.l (a0)+,d0-d7` straight from the
|
||||||
|
stream buffer into GVRAM, derives to **~240 cycles** — cheaper than V1's
|
||||||
|
measured 299.9, and pixel-exact. V1 is dearer *because* it is compressed: it
|
||||||
|
pays an index decode and an indexed `movem.l` that a literal does not, and then
|
||||||
|
does exactly the same four writes. It buys 31 bytes and spends 60 cycles.
|
||||||
|
|
||||||
|
**Every codebook mode is CPU-dominated by a literal.** V4 was already dominated
|
||||||
|
by RAW (28.8); with word-expanded literals available, so is V1. The VQ codebook
|
||||||
|
earns its place only while bytes are scarce.
|
||||||
|
|
||||||
|
### 29.3 Row-linear literal spans, priced against the real mode maps
|
||||||
|
Replace the per-block escape with a per-row **span**: `(x, count, word-expanded
|
||||||
|
pixels)`, decoded with `movem.l` bursts. A run of L horizontally adjacent dirty
|
||||||
|
blocks becomes 4 spans of 4L pixels, deriving to `4 * (50 + 4L * 9.08)` cycles
|
||||||
|
against `300L` for V1 — **cheaper for any run of 2 blocks or more**, at 32 bytes
|
||||||
|
per block instead of 1.
|
||||||
|
|
||||||
|
Applied greedily (buy the best cycles-saved-per-byte until the bus budget is
|
||||||
|
gone) to the *unchanged* mode maps of the `sasi` Singe window:
|
||||||
|
|
||||||
|
| | today | + literal spans |
|
||||||
|
|---|---:|---:|
|
||||||
|
| median frame | 74.4% | **43.0%** |
|
||||||
|
| p90 frame | 115.1% | **83.6%** |
|
||||||
|
| worst frame | 136.2% | **106.2%** |
|
||||||
|
| frames missing the budget | **37/120** | **8/120** |
|
||||||
|
| bitrate | 101.7 KB/s | 453.2 KB/s (bus 488) |
|
||||||
|
|
||||||
|
And the fit is structural rather than lucky: **spans get cheaper exactly where
|
||||||
|
blocks get expensive.** A span amortises its overhead over a long run, and long
|
||||||
|
runs are what a high-change frame is made of. The frames that miss today are the
|
||||||
|
frames spans help most.
|
||||||
|
|
||||||
|
### 29.4 This reopens 28.5, which said a scene cut cannot fit
|
||||||
|
28.5 concluded that no mode assignment fits a 100%-changed frame at 12fps,
|
||||||
|
because the cheapest full redraw available — all-V1 — is 110.5%. That was true
|
||||||
|
of *the mode set the codec has*. Adding a byte-expensive, cycle-cheap mode
|
||||||
|
changes the arithmetic: mixing a fraction `x` of the frame as spans against V1
|
||||||
|
for the rest,
|
||||||
|
|
||||||
|
- CPU needs `x >= 0.19`
|
||||||
|
- the 40,977 B/frame bus budget allows `x <= 0.39`
|
||||||
|
|
||||||
|
**The interval is not empty.** A scene cut fits at 12fps if roughly a quarter to
|
||||||
|
a third of it arrives as word-expanded row-linear literals. 28.5's "structural
|
||||||
|
ceiling" was a ceiling of the bitstream, not of the machine.
|
||||||
|
|
||||||
|
### 29.5 What has to be measured before any of this is believed
|
||||||
|
1. **Span cost on the 68000.** The 50-cycle per-span overhead is derived, and
|
||||||
|
the 9.08 cycles/px is measured at *full row width* with 12-register bursts —
|
||||||
|
a short or oddly-aligned span cannot burst as well, so short spans are
|
||||||
|
flattered here. Extend `tools/bench/blit.s` with a span variant and measure
|
||||||
|
it against run length. **This is the load-bearing number.**
|
||||||
|
2. **Re-run the ring-buffer simulation at ~450 KB/s.** FINDINGS 21's zero
|
||||||
|
required prefill was established at 110 and 280 KB/s against a 488 KB/s pipe.
|
||||||
|
At 453 the margin is a tenth of what it was, and 21's own caveat was that the
|
||||||
|
test is cumulative — it needs redoing, not extrapolating.
|
||||||
|
3. **Confirm the 4 Mbps figure**, which is user-supplied with no recorded
|
||||||
|
provenance and which this design would run at 93% of. It has been a "would be
|
||||||
|
nice" since session 1; a design that leans on it makes it load-bearing.
|
||||||
|
4. **Confirm DMA, not PIO** (STATUS priority 5). At 453 KB/s a PIO fallback puts
|
||||||
|
the transfer cost on the CPU we are trying to relieve. Cheapest check
|
||||||
|
available and now the most consequential.
|
||||||
|
|
||||||
|
### 29.6 The other lever, not yet costed: let the DMAC do the copy
|
||||||
|
The X68000 has an HD63450 DMAC (4 channels, `x68k.cpp:1046`). Channel 3 is
|
||||||
|
ADPCM — confirmed, `adpcm_drq_tick` asserts `drq3_w` — but memory-to-memory
|
||||||
|
transfer on a free channel would take the GVRAM copy off the CPU entirely,
|
||||||
|
leaving it only the parsing. This is the one idea here that could move the
|
||||||
|
budget without spending a single extra byte.
|
||||||
|
|
||||||
|
It cannot be settled in MAME: like the SCSI/SASI devices (BENCHMARK.md), the
|
||||||
|
HD63450 is a functional model, so a timing number out of it would measure the
|
||||||
|
emulator's scheduler. It needs hand-derivation against the datasheet plus real
|
||||||
|
hardware — the same three-tier approach the disk benchmark already documents.
|
||||||
|
|||||||
+50
-17
@@ -1,16 +1,42 @@
|
|||||||
# Status & next-session handoff — end of session 7 (2026-08-23)
|
# Status & next-session handoff — end of session 7 (2026-08-23)
|
||||||
|
|
||||||
## NEXT SESSION: make the mode decision cost-aware
|
## NEXT SESSION: measure a span, then make the mode decision cost-aware
|
||||||
|
|
||||||
The decoder exists, it is pixel-exact, and **it does not fit**. On the worst
|
The decoder exists, it is pixel-exact, and **it does not fit**. On the worst
|
||||||
sustained window at the shipping `sasi` profile it costs a mean of **81.7% of a
|
sustained window at `sasi` it costs a mean of **81.7% of a 12fps frame** and
|
||||||
12fps frame budget** and **31% of frames exceed 100%** (`scsi`: 94.9% median,
|
**31% of frames exceed 100%** (`scsi`: 94.9% median, 42% miss). FINDINGS 28.
|
||||||
42% of frames miss). FINDINGS 28. CPU is now the binding constraint — the first
|
CPU is the binding constraint now — the first time in this project.
|
||||||
time in this project that it has been.
|
|
||||||
|
|
||||||
The fix is not assembly micro-optimisation. It is that **`vq_hybrid.decide()`
|
**Two levers, and the cheap one has to be measured first.**
|
||||||
minimises `D + lam*R` — distortion against BYTES — on a machine where the
|
|
||||||
binding budget is CYCLES**, and the two are not proportional:
|
*Lever A — spend bandwidth to buy cycles.* The bus sits 4x idle: `sasi` uses 110
|
||||||
|
KB/s of 488. Every codec decision was made when bytes were scarce, so each one
|
||||||
|
trades cycles to save them, and the cheapest thing a 68000 can be handed is the
|
||||||
|
most expensive thing to store — **word-expanded pixels in row-linear runs**.
|
||||||
|
Adding one mode, a per-row span of literal words `movem.l`-ed straight from the
|
||||||
|
stream buffer into GVRAM, prices out at (FINDINGS 29, `12_span_tradeoff.py`):
|
||||||
|
|
||||||
|
| | today | + literal spans |
|
||||||
|
|---|---:|---:|
|
||||||
|
| median frame | 74.4% | **43.0%** |
|
||||||
|
| worst frame | 136.2% | **106.2%** |
|
||||||
|
| frames missing | **37/120** | **8/120** |
|
||||||
|
| bitrate | 101.7 KB/s | 453.2 KB/s (bus 488) |
|
||||||
|
|
||||||
|
**This is DERIVED, not measured, and it is load-bearing — so measure it first.**
|
||||||
|
Extend `tools/bench/blit.s` with a span variant and time it against run length.
|
||||||
|
The 9.08 cycles/pixel it rests on is real (FINDINGS 24 V1) but was measured at
|
||||||
|
full row width with 12-register bursts; short and oddly-aligned spans cannot
|
||||||
|
burst as well and are flattered by the model. If spans come in near the derived
|
||||||
|
figure, the whole mode set changes and lever B optimises over different modes —
|
||||||
|
which is exactly why this goes first. FINDINGS 29.5 lists the other three things
|
||||||
|
that have to hold, of which **confirming DMA vs PIO is the cheapest and now the
|
||||||
|
most consequential**: at 453 KB/s a PIO fallback puts the transfer back on the
|
||||||
|
CPU this is trying to relieve.
|
||||||
|
|
||||||
|
*Lever B — stop buying modes the CPU cannot afford.* `vq_hybrid.decide()`
|
||||||
|
minimises `D + lam*R` — distortion against BYTES — on a machine whose binding
|
||||||
|
budget is CYCLES, and the two are not proportional:
|
||||||
|
|
||||||
| mode | payload bytes | measured cycles | cycles per byte |
|
| mode | payload bytes | measured cycles | cycles per byte |
|
||||||
|---|---:|---:|---:|
|
|---|---:|---:|---:|
|
||||||
@@ -18,13 +44,18 @@ binding budget is CYCLES**, and the two are not proportional:
|
|||||||
| V1 | 1 | 300 | 300 |
|
| V1 | 1 | 300 | 300 |
|
||||||
| V4 | 4 | 448 | 112 |
|
| V4 | 4 | 448 | 112 |
|
||||||
| RAW | 16 | 400 | 25 |
|
| RAW | 16 | 400 | 25 |
|
||||||
|
| *word-expanded literal block* | *32* | *~240 (derived)* | *7.5* |
|
||||||
|
|
||||||
V4 is **25% of blocks and 50% of the cycles**. The lagrangian charges it 4x a V1
|
V4 is **25% of blocks and 50% of the cycles**. The lagrangian charges it 4x a V1
|
||||||
block; the CPU charges it 1.49x. So the encoder currently buys V4 whenever it is
|
block; the CPU charges it 1.49x. Note the last row: a literal block is cheaper
|
||||||
worth 4 bytes, with no idea what it costs to draw.
|
than **every** codebook mode, and pixel-exact — the codebook is a byte
|
||||||
|
optimisation that now costs cycles (FINDINGS 29.2).
|
||||||
|
|
||||||
**The work, in order:**
|
**The work, in order:**
|
||||||
|
|
||||||
|
0. **Measure the span cost on the 68000** (lever A above). Cheap, and everything
|
||||||
|
below optimises over whatever mode set it leaves.
|
||||||
|
|
||||||
1. **Add a cycle term to the mode decision.** `decide()` already builds a cost
|
1. **Add a cycle term to the mode decision.** `decide()` already builds a cost
|
||||||
matrix of `error + lam * bytes` per mode per block; add `+ mu * cycles`,
|
matrix of `error + lam * bytes` per mode per block; add `+ mu * cycles`,
|
||||||
with the per-mode cycles measured in FINDINGS 28.2.
|
with the per-mode cycles measured in FINDINGS 28.2.
|
||||||
@@ -66,13 +97,15 @@ worth 4 bytes, with no idea what it costs to draw.
|
|||||||
`sasi` cannot afford it, so expect the cycle ceiling to cost `sasi` more
|
`sasi` cannot afford it, so expect the cycle ceiling to cost `sasi` more
|
||||||
quality even though it costs `sasi` fewer cycles. FINDINGS 28.8.
|
quality even though it costs `sasi` fewer cycles. FINDINGS 28.8.
|
||||||
|
|
||||||
4. **28.5 may not be solvable by the encoder at all.** An all-V1 frame — the
|
4. **Scene cuts: 28.5 said impossible, 29.4 reopened it.** An all-V1 frame —
|
||||||
cheapest possible full redraw — is **110.5%** of the budget. A scene cut
|
the cheapest full redraw the *current* mode set allows — is 110.5% of budget,
|
||||||
changes 100% of the screen, so *no* mode assignment fits one at 12fps. Decide
|
so no mode assignment fits a 100%-changed frame. With literal spans the
|
||||||
deliberately: allow one late frame at a cut (the outgoing content is
|
arithmetic changes: CPU needs at least 19% of the frame sent as spans, the
|
||||||
unrelated, so it may be invisible), spread a cut over two frame times, or
|
bus allows up to 39%, **and that interval is not empty**. So 28.5 was a
|
||||||
drop to 10fps where an all-V1 frame fits. This is a design decision, not a
|
ceiling of the bitstream, not of the machine — *if* lever A measures out.
|
||||||
measurement, and it needs the user.
|
If it does not, this is still a design decision that needs the user: one late
|
||||||
|
frame at each cut (the outgoing content is unrelated, so it may be
|
||||||
|
invisible), a cut spread over two frame times, or 10fps.
|
||||||
|
|
||||||
**Do not start by hand-optimising `decode.s`.** The hand-derived timings agree
|
**Do not start by hand-optimising `decode.s`.** The hand-derived timings agree
|
||||||
with the measurements to 0.5% on V1 and 1% on RAW (FINDINGS 28.4), so the
|
with the measurements to 0.5% on V1 and 1% on RAW (FINDINGS 28.4), so the
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""What does spending the idle bus bandwidth buy back in CPU cycles?
|
||||||
|
|
||||||
|
python3 tools/analysis/12_span_tradeoff.py [container.dlx] [--bus 488]
|
||||||
|
|
||||||
|
FINDINGS 28 leaves the decoder CPU-bound at 110 KB/s on a 488 KB/s pipe. Every
|
||||||
|
codec decision was made when bytes were scarce, so each one trades cycles to
|
||||||
|
save them -- and the cheapest thing a 68000 can be handed is the most expensive
|
||||||
|
thing to store: word-expanded pixels in row-linear runs.
|
||||||
|
|
||||||
|
This prices ONE new mode against the real mode maps: a per-row SPAN of
|
||||||
|
word-expanded literals, `movem.l`-ed straight from the stream buffer into GVRAM.
|
||||||
|
A run of L horizontally adjacent dirty blocks becomes 4 spans of 4L pixels.
|
||||||
|
|
||||||
|
DERIVED, NOT MEASURED (FINDINGS 29). The 9.08 cycles/pixel is measured
|
||||||
|
(FINDINGS 24 V1) but at full row width with 12-register bursts; SPAN_OVERHEAD is
|
||||||
|
hand-derived. Short spans are therefore flattered. Measure before believing --
|
||||||
|
FINDINGS 29.5 item 1.
|
||||||
|
|
||||||
|
The mode maps are NOT re-optimised: this only re-codes regions the encoder
|
||||||
|
already chose to redraw, so it is a lower bound on what a cost-aware encoder
|
||||||
|
would find.
|
||||||
|
"""
|
||||||
|
import sys, os, argparse
|
||||||
|
sys.path.insert(0, "tools/encoder")
|
||||||
|
import numpy as np
|
||||||
|
from dlx import DLX
|
||||||
|
|
||||||
|
FRAME_CYC = 833333.0 # 12fps at 10 MHz
|
||||||
|
AUDIO_KBPS = 7.8
|
||||||
|
|
||||||
|
CYC_PX_ROWLIN = 446286 / 49152. # 9.08, FINDINGS 24 V1 (measured)
|
||||||
|
C_V1, C_V4, C_RAW = 299.9, 448.2, 400.4 # FINDINGS 28.2 (measured)
|
||||||
|
C_SKIP_CLUSTERED, C_SKIP_MIXED = 13.25, 45.0
|
||||||
|
SPAN_OVERHEAD = 50.0 # per span, DERIVED
|
||||||
|
SPAN_BYTES_PX = 2 # word-expanded: 1 pixel = 1 word
|
||||||
|
SPAN_HDR = 3 # x, count, and a byte of slack
|
||||||
|
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument("container", nargs="?",
|
||||||
|
default="tmp/rc_fr_singe_sasi_rcprofile.dlx")
|
||||||
|
ap.add_argument("--bus", type=float, default=488.0,
|
||||||
|
help="sustained KB/s the pipe delivers (FINDINGS 21)")
|
||||||
|
ap.add_argument("--fps", type=float, default=12.0)
|
||||||
|
a = ap.parse_args()
|
||||||
|
if not os.path.exists(a.container):
|
||||||
|
sys.exit(f"missing {a.container}")
|
||||||
|
|
||||||
|
BYTE_BUD = (a.bus - AUDIO_KBPS) * 1024 / a.fps
|
||||||
|
d = DLX(a.container)
|
||||||
|
BLK_C = {1: C_V1, 2: C_V4, 3: C_RAW}
|
||||||
|
BLK_B = {1: 1, 2: 4, 3: 16}
|
||||||
|
|
||||||
|
rows = []
|
||||||
|
for f in range(d.nframes):
|
||||||
|
mode = d.modes(f)
|
||||||
|
g = mode.reshape(-1, 4)
|
||||||
|
allskip = (g == 0).all(1)
|
||||||
|
base = allskip.sum() * 4 * C_SKIP_CLUSTERED
|
||||||
|
mm = g[~allskip]
|
||||||
|
base += (mm == 0).sum() * C_SKIP_MIXED
|
||||||
|
for k, c in BLK_C.items():
|
||||||
|
base += (mm == k).sum() * c
|
||||||
|
base_b = d.mode_bytes + sum(BLK_B.get(int(x), 0) for x in mode)
|
||||||
|
|
||||||
|
m = mode.reshape(d.nby, d.nbx)
|
||||||
|
cand = []
|
||||||
|
for by in range(d.nby):
|
||||||
|
dirty = m[by] != 0
|
||||||
|
i = 0
|
||||||
|
while i < d.nbx:
|
||||||
|
if not dirty[i]:
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
j = i
|
||||||
|
while j < d.nbx and dirty[j]:
|
||||||
|
j += 1
|
||||||
|
L = j - i
|
||||||
|
cur_c = sum(BLK_C[int(b)] for b in m[by][i:j])
|
||||||
|
cur_b = sum(BLK_B[int(b)] for b in m[by][i:j])
|
||||||
|
span_c = 4 * (SPAN_OVERHEAD + 4 * L * CYC_PX_ROWLIN)
|
||||||
|
span_b = 4 * (SPAN_HDR + 4 * L * SPAN_BYTES_PX)
|
||||||
|
if span_c < cur_c:
|
||||||
|
cand.append((cur_c - span_c, span_b - cur_b, L))
|
||||||
|
i = j
|
||||||
|
|
||||||
|
cand.sort(key=lambda s: -(s[0] / max(s[1], 1))) # best cycles per byte
|
||||||
|
cyc, byt, taken = base, base_b, 0
|
||||||
|
for dc, db, L in cand:
|
||||||
|
if byt + db <= BYTE_BUD:
|
||||||
|
cyc -= dc; byt += db; taken += 1
|
||||||
|
rows.append((base, cyc, base_b, byt, len(cand), taken))
|
||||||
|
|
||||||
|
base, new, bb, nb, ncand, ntaken = map(np.array, list(zip(*rows)))
|
||||||
|
pc = lambda v: 100 * v / FRAME_CYC
|
||||||
|
|
||||||
|
print(f"{a.container}: {d.nframes} frames")
|
||||||
|
print(f"bus {a.bus:.0f} KB/s - {AUDIO_KBPS} audio -> {BYTE_BUD:,.0f} B/frame "
|
||||||
|
f"at {a.fps:g}fps\n")
|
||||||
|
print(f"{'':<26}{'today':>12}{'+ literal spans':>18}")
|
||||||
|
for label, fn in (("median frame", np.median),
|
||||||
|
("p90 frame", lambda v: np.percentile(v, 90)),
|
||||||
|
("worst frame", np.max)):
|
||||||
|
print(f" {label:<24}{pc(fn(base)):>11.1f}%{pc(fn(new)):>17.1f}%")
|
||||||
|
print(f" {'frames missing budget':<24}{int((base>FRAME_CYC).sum()):>8}/{d.nframes}"
|
||||||
|
f"{int((new>FRAME_CYC).sum()):>14}/{d.nframes}")
|
||||||
|
print(f" {'bitrate':<24}{bb.mean()*a.fps/1024:>10.1f} KB/s"
|
||||||
|
f"{nb.mean()*a.fps/1024:>13.1f} KB/s")
|
||||||
|
print(f"\nspans taken: {ntaken.sum()} of {ncand.sum()} candidate runs "
|
||||||
|
f"({100*ntaken.sum()/max(ncand.sum(),1):.0f}%) -- the rest priced out by the bus")
|
||||||
|
print("\nDERIVED, NOT MEASURED: see FINDINGS 29.5 before acting on this.")
|
||||||
Reference in New Issue
Block a user