Historical recipe — not current setup guidance. Measurements and implementation links below belong to their original workload and revision. Deployment labels and defaults may be obsolete. Use the reviewed performance recipes; browse historical evidence.

AVPlumber · Mixer optimization cookbook

Ideas we measured and dropped

Implementation links are pinned to develop at c20464a5 (2026-10-09). The mixer has changed since; this page was checked against develop at 8fa15c92 on 2026-10-13. Measurements retain their original workload and scope. Watch the current 192-input L4 demo.

Current scope. These are historical decisions under the workloads shown. The demo now reads raw NV12 and P010 files with one node, raw_video_file_cuda, which uploads from page-locked memory on a private stream as the pinned filter does; the earlier custom uploader nodes are gone. Input normalization has since been removed from the mixer without skipping native input work.

Every source is a unique, independent input of at least 1080p25, processed every frame; twelve ideas broke that rule, saved almost nothing, or made cuts, pictures or the pipeline slower, so we dropped them.

Status: Rejected

The rule, and where each shortcut would cut it

One real source chain: unique 1080p25 input, decode or capture every frame, convert, compositor, encode and send. Red callouts mark the five shortcuts that would break the rule; an amber callout marks idling off-screen sources. Kept: every source has its own chain and its own media, decoded by itself 6 · Idle off-screen sources direct cuts 80–120 ms slower Unique input 1080p25 file/page Decode one chain each Convert full-size frame Compositor new frame each tick Encode, send every output 3 · 720p pages or test clips fewer real pixels 2 · One chain per file NVDEC 40 → 7 4 · Resize in the decoder 1080×608 kept 5 · Loop kept on the GPU no new frames 7 · Pause unwatched ~1 s black to resume
Red: the shortcut makes the demo lighter than a real show, so capacity numbers would lie. Amber: a real trade, but it costs cut latency. Green: what the demo keeps. Idea 1 is not on this chain; it is the next diagram. Ideas 8–12 are not on this chain.

Why reusing compositor B's picture costs more

The Program preview's PVW tile drawn two ways: copying compositor B's picture fills 100 % of the tile; re-compositing the scene fills only about 49 %, because contain letterboxing leaves bars. Idea: copy B's picture bars drawn too 100 % of tile pixels Live: re-composite the scene bars skipped ~49 % of tile pixels (average) Cost follows drawn pixels × 4 bilinear taps not layer count
Schematic, not to scale. A portrait tile of letterboxed 16:9 boxes. Only the green area costs work when the compositor draws the scene itself.

The twelve ideas

The cost model said no

  1. 1Subcomposition reuse

    IdeaDraw the Program preview's PVW quadrant from compositor B's picture. Share scaled renditions between compositors.

    DroppedCompositor cost follows drawn output pixels, not layer count. Scenes cover only ~49 % of the portrait canvas on average, so copying B's full quadrant draws more pixels than re-compositing. Best case, reuse saves ~0.01 GFLOP/s of the T4's 8.1 TFLOPS. A whole full-canvas compositor costs ~0.56 GFLOP/s.

    KeptThe PGM tile reuses the finished program. Compositor B idles when no preview is armed.

They would break the rule

  1. 2Share identical media between sources

    IdeaOne decode chain per file, fanned out to every source that plays it.

    DroppedIt would cut NVDEC sessions from 40 to 7 and save 1.5–4 GB. Real inputs are all different, so the saving is fake.

  2. 3720p browser pages or 720p test clips

    DroppedReal pixels stay 1080p. Smaller inputs make every stage look cheaper than a real show.

  3. 4Decode-side resize to 1080×608

    IdeaLet NVDEC write a 1920×1080 clip at the size a full-canvas box shows it on a 1080×1920 canvas.

    DroppedIt keeps fewer pixels than the source. Dropped for the capacity demo; it is not in the code.

  4. 5Keep looped test clips on the GPU

    DroppedReal inputs deliver a new frame every tick. Replaying held frames skips decode work a real show must pay.

  5. 7Pause outputs nobody watches

    IdeaA Janus viewer poller stops the monitor and clean-feed encoders while no one views them.

    DroppedIt saved encoder work only while unwatched, and hid real load. A viewer saw ~1 s of black or frozen picture on resume. A clean-feed consumer that is not a Janus viewer, such as a recorder, would lose picture. More moving parts. Removed from the code.

  6. 10Browser sources at 30 fps in a 60 fps show

    DroppedEvery demo source is a real 60 fps input. A production knob, not a capacity result.

Ruled out for the 60 fps show

  1. 8__GL_YIELD=USLEEP

    DroppedChromium's GPU processes made 0 sched_yield calls in 5 s: no spinning. Their CPU is ~12k futex/s thread hand-offs and ~1.1k ioctl/s.

  2. 9Fewer, bigger browser workers

    DroppedCost is per window per delivered frame, not per process; fewer workers concentrate VizCompositor load (Browser CPU is per painted frame).

    SinceA later audit found that 36–44 % of the cost in the GPU and browser processes is per tick, not per delivered frame. Whether that part is per window or per worker cannot be told while every worker holds 8 windows, so the idea was not re-opened and not re-tested (Per tick and per paint).

  3. 11cpuset split between containers

    DroppedCores pinned to one side idle while the other is short: it idles capacity.

  4. 12FFmpeg hwupload for raw sources at 60 fps

    Dropped69 % of program frames missed their deadline at 68 inputs; NVDEC fell from ~85 to ~26 % because the pipeline stalled (Raw frames read straight into the memory the GPU reads).

The trade was cut latency

  1. 6Narrow prewarm, idle off-screen sources

    DroppedDirect cuts get 80–120 ms slower. Without prewarm, a cut resets the incoming source's playout queue and waits for fresh frames.

At a glance

An earlier proposal: shorter decoded frame queues

Not one of the twelve: it was never built or measured. The proposal was to plan each NVDEC source's decoded edge at capacity 1 instead of the instance default of 3 when a show runs at 30 fps or below. NVDEC decodes ahead of real time, so that edge is always full, and two fewer parked 1080p frames per source were estimated to free about 255 MB across 40 sources. It was not selected: one frame leaves 33 to 40 ms of slack before a decode hiccup repeats a frame, and the comparison of repeat rates the proposal asked for was not run. The NVDEC CUarray path then saved 4.88 GiB in the matched 192-input, 20-output comparison while keeping all three decoded queue slots. Every NVDEC source's decoded edge still has the default of 3 (planCapacity("*", 3) in application.py). A browser window's frame queue is a different queue: Browser frames go back one per new frame.

Since 14 October the proposal exists as an option on an avplumber branch, canvas.decoded_frames_ahead. Run at 1 on the 192-source show it did not lower GPU memory (20.1 GB, within the previous build's 19.3 to 20.1): on the CUDA array path a waiting frame is a reference to one of the decoder's fixed arrays, not a buffer of its own. The comparison of repeat rates has still not been run (GPU memory: frames no tick can show any more).

Measured on 13 and 14 October, with no gain

Where in the code

The GFLOP/s figures and the NVDEC and memory savings are model estimates for the T4 test host, not measurements. The ~49 % coverage is an average over scene layouts; single scenes vary.