Skip to content

Commit 748dfba

Browse files
authored
Add demux codec_mutex contention guidance to autoresearch prompt (#1503)
Add a "Demux contention: FFmpeg's `codec_mutex`" subsection to the optimization strategies prompt, explaining the root cause of demux scaling issues: FFmpeg's process-wide `codec_mutex` serializes `codec->init()` for decoders flagged `FF_CODEC_CAP_NOT_INIT_THREADSAFE` (external libraries with unknown thread-safety). Recommends splitting demux and decode into separate pipeline stages with at most 3 concurrency for demux, applicable to both CPU and NVDEC decode paths. Also updates the NvDec section with an MTP example showing the split pattern.
1 parent a4d9d12 commit 748dfba

1 file changed

Lines changed: 45 additions & 0 deletions

File tree

src/spdl/autoresearch/pipeline_optimization/prompts/knowledge/optimization_strategies.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,26 @@ Concurrency 6: per-item = 0.065s → throughput = 6 / 0.065 = 92 items/s ←
197197

198198
This effect is most pronounced for operations that are inherently fast (data copying, demuxing, disaggregation), where the operation itself is cheap but contention from concurrent scheduling inflates the observed per-item latency. For slow operations (network I/O, heavy CPU compute), higher concurrency is still beneficial because the parallelism gains outweigh the contention cost.
199199

200+
### Demux contention: FFmpeg's `codec_mutex`
201+
202+
Demuxing contention is not just generic thread scheduling overhead — there is a specific root cause inside FFmpeg. Every `Demuxer` construction calls `avformat_find_stream_info`, which probes each stream by opening a decoder via `avcodec_open2`. For codecs backed by external libraries (e.g. libvpx, libaom, libopus) whose thread-safety guarantees are unknown to FFmpeg, this initialization is guarded by a process-wide `codec_mutex` (`FF_CODEC_CAP_NOT_INIT_THREADSAFE` flag). At even modest concurrency (4+ threads), demux threads serialize on this lock and per-item latency rises sharply without improving throughput.
203+
204+
**Recommendation:** always split demuxing and decoding into separate pipeline stages with independent concurrency, and assign **at most 3 concurrency** to the demux stage. This allows the decode stage to use higher concurrency independently (or leverage NVDEC hardware decoders) without being bottlenecked by the demux lock:
205+
206+
```python
207+
# Split demux and decode with independent concurrency
208+
pipeline = (
209+
PipelineBuilder()
210+
.add_source(source)
211+
.pipe(fetch, concurrency=16)
212+
.pipe(demux, concurrency=3) # at most 3 — codec_mutex contention
213+
.pipe(decode, concurrency=8) # decode can scale independently
214+
.aggregate(batch_size)
215+
.pipe(collate)
216+
.add_sink(buffer_size=3)
217+
)
218+
```
219+
200220
### Search strategy
201221

202222
When tuning a stage's concurrency — especially for fast operations:
@@ -254,6 +274,31 @@ exec = ThreadPoolExecutor(max_workers=7)
254274

255275
Higher concurrency values (e.g., c=16) still work — excess tasks wait for an engine — but waste thread resources on waiting. Setting concurrency to match the hardware count (c=7) is marginally more efficient.
256276

277+
**Always split demux and decode into separate stages** (see "Demux contention" above). Demuxing should be limited to 2-3 concurrency due to FFmpeg's `codec_mutex`, while decode — whether CPU or NvDec — can scale independently. Combining them in a single stage forces decode concurrency to match the demux bottleneck. With NvDec, the MTP pattern works particularly well because demuxed packets are picklable and can cross the subprocess boundary:
278+
279+
```python
280+
# MTP backend (subprocess) — demux only, low concurrency
281+
backend = (
282+
PipelineBuilder()
283+
.add_source(source, continuous=True)
284+
.pipe(fetch, concurrency=num_fetch_threads)
285+
.pipe(demux, concurrency=3) # codec_mutex limits scaling
286+
.add_sink(buffer_size=3)
287+
)
288+
source2 = spdl.pipeline.run_pipeline_in_subprocess(backend.get_config(), ...)
289+
290+
# Frontend (main process) — NvDec decode at hardware concurrency
291+
decode_exec = ThreadPoolExecutor(max_workers=7)
292+
frontend = (
293+
PipelineBuilder()
294+
.add_source(source2, continuous=True)
295+
.pipe(nvdec_decode, concurrency=7, executor=decode_exec)
296+
.aggregate(batch_size)
297+
.pipe(collate)
298+
.add_sink(buffer_size=3)
299+
)
300+
```
301+
257302
### Video Decoder Thread Tuning
258303

259304
When the pipeline decodes video with `spdl.io.load_video` / `spdl.io.decode_packets`, the underlying FFmpeg decoder uses **a single thread by default**. This is a deliberate SPDL design choice — concurrency is expected to come from running many decoders in parallel at the pipeline level — but for some workloads (high-resolution video, low pipeline concurrency, or CPU headroom available) raising the per-decoder thread count is faster overall.

0 commit comments

Comments
 (0)