Repository navigation
Fix mix transition reserving 9.2 MB per instance - #1332
Conversation
transition_mix embedded two float[MAX_SAMPLES * MAX_CHANNELS] buffers in its struct, so every mix transition was a 9.2 MB calloc(). Once glibc's dynamic mmap threshold rises, each one comes from the heap and is zeroed in full: a project with a few thousand audio crossfades needs tens of GB and seconds to build its graph. Allocate src_buffer and dest_buffer with mlt_pool_alloc() on demand instead. They only ever grow, by at least doubling up to the old maximum, so there is no allocation in the audio path once they have reached the size the frames need. New space is zeroed and the old contents are kept, so reads see exactly what they saw in the zero-initialised arrays, and the existing overflow handling (which addresses the end of the full-size array) is unchanged: it simply makes the buffer grow to the full size first. Measured with 500 mix transitions created per round (4 rounds), RelWithDebInfo: before, round 3: 1504 ms and 4448 MB RSS; after, every round 2-3 ms and 54 MB RSS (the baseline process size). Output is unchanged: melt renders of 14 mix configurations (default, sum, start/end, linear and constant-power crossfade, reverse, combine, ducking, mixed sample rates and channel counts) and a harness driving transition_get_audio with random frames (changing channel counts, frame sizes, discontinuities, paused flushes and the buffer overflow paths) are byte-identical to master, including under ASan/UBSan. Fixes mltframework#1316.
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
Address the potential peak-memory regression from power-of-two pooled allocations and retained pool blocks.
Review effort: Lite
Findings: None
What changed in this PR
This PR reduces mix transition memory usage by replacing embedded audio buffers with lazy dynamic allocation.
Changes:
- Adds growable, zero-initialized
mlt_poolbuffers. - Preserves existing audio data and limits.
- Releases buffers during transition cleanup.
- Requires addressing pooled allocation overhead for maximum-size buffers.
| File | Summary |
|---|---|
src/modules/core/transition_mix.c |
Implements dynamic audio buffer allocation and cleanup. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
As suggested in review. CLAMP(old * 2, floats, MAX_FLOATS) is the same size as before: callers never ask for more than MAX_FLOATS.
|
Switched to |
Fixes #1316, following the direction in the discussion there (dynamic allocation, using
mlt_pool).transition_mixembedded twoMAX_SAMPLES * MAX_CHANNELSfloat buffers in its struct, so everymixwas a ~9.2 MBcalloc(). This allocatessrc_bufferanddest_bufferwithmlt_pool_alloc()on demand instead. They only grow (at least doubling, up to the old maximum), the new space is zeroed and the old contents are kept, so reads see what they saw in the zero-initialised arrays. After they reach the size the frames need there is no allocation in the audio path. They are released intransition_close().MAX_CHANNELSis unchanged.Measured (RelWithDebInfo, 500
mixtransitions created per round, repro), time / RSS:Output is unchanged: 14
meltrenders of differentmixconfigurations (default,sum,start/end, linear and constant-power crossfade,reverse,combine, ducking, mixed sample rates and channel counts, chained mixes) are byte-identical to master. So is a harness drivingtransition_get_audio()with random frames (changing channel counts and frame sizes, discontinuities, paused flushes, and the buffer-overflow paths), 28,848 per-frame hashes, also clean under ASan/UBSan.clang-formatreports nothing on the changed lines (it reports 3 warnings on lines already in master, with a newer clang-format than CI's).A note, not fixed here:
*samples = MIN(src_count + samples_b0, dest_count + samples_a0)is computed beforesamples_a/samples_bare clamped, so a single frame above ~1.15M floats on both sides would read pastcount. It is unreachable with real frames, and master has the same issue.🤖 Generated with Claude Code