Reading less of the file
A multicore analysis pipeline was reading entire multi-gigabyte media files to compute information the container format already stored.
- ProRes 2160p, 17.6 GB
22.3% / 28.6%
ProRes 2160p, 17.6 GB
- ProRes 1080p, 2.5 GB
14.7% / 24.4%
ProRes 1080p, 2.5 GB
- DNxHD 1080p, 20.3 GB
21.1% / 21.5%
DNxHD 1080p, 20.3 GB
Percentages are gain against the disabled build and against the previous release respectively.
My contribution
I designed and implemented the metadata-driven chunk mapping for MOV/MP4 and the index-table-driven equivalent for MXF, including the buffer sizing work that made the MXF path viable.
Context
The analysis engine runs multicore. A dedicated reader thread pulls essence data off the file into per-track buffers; separate threads build random access points from that data; each access point is then handed to whichever CPU is free. It is a clean producer/consumer arrangement and it scales with cores.
To build access points, the pipeline needed a map of where each chunk of essence lives in the file: its offset and its length. That map was constructed by the reader thread as it went, which meant building the map required reading the file.
The problem
Chunk offsets and lengths are not derived from the essence. They are recorded in the container. The pipeline was reading tens of gigabytes of video to recover information the file header already knew.
Locally that cost was mostly hidden by the page cache. Over object storage there is no page cache, and every unnecessary byte is a byte pulled across a network.
Building the chunk map from metadata
For MOV and MP4, the sample tables in the container describe exactly this: chunk offsets, sample sizes, the mapping from samples to chunks, and the edit list. I added a step that runs before the reader thread starts and constructs the chunk map directly from those tables, after which the tracks it covers can be dropped from the read list entirely.
For ProRes and DNxHD, that removes the essence read completely: the access points are built from the map, and the data itself is never touched at this stage.
- Chunk offset tables, for where each chunk begins
- Sample size and sample-to-chunk tables, for how long each chunk runs
- Edit list, for how the samples map onto the presentation timeline
Before: build the chunk map by reading essence. After: build it from the container tables, and skip the read for codecs that do not need it.
The codec that would not cooperate
AVC-Intra could not be handled the same way. Building access points for it needs the sequence and picture parameter sets, and those are properties of the bitstream, not the container, and no amount of metadata reading recovers them.
But the H.264 specification places both at the start of each sample. So instead of skipping the read, I bounded it: the reader fetches only the first 5000 bytes of each sample and zero-fills the remainder of the buffer. The parameter sets are always inside that window, and the rest of every sample stays where it is.
MXF, where interleaving breaks the approach
MXF stores its essence interleaved across partitions, which defeats the MOV strategy: even if you know exactly which bytes you want, reading around them pulls in the neighbouring tracks you were trying to avoid.
The way through is the index table, together with the random index pack that locates each partition. From a partition pack you can derive where essence begins, accounting for the header and index byte counts, the partition offset, and the alignment grid the format uses to pad KLV elements onto byte boundaries. From the index table you can then locate an individual picture item within an edit unit, whether edit units are fixed-size or variable, and derive its length by computing the offset of the next element and subtracting.
Each picture item and its trailing filler becomes one chunk. A format-specific step then skips the filler when populating track buffers, so the padding that makes the arithmetic clean never reaches the analysis code.
Essence offset is derived from the partition pack; picture position within an edit unit comes from the index table. Neither requires reading essence.
The buffer that had to shrink last
MXF analysis used a 256 KB internal read buffer. With interleaved data, a buffer that large reads neighbouring tracks by accident, so the obvious move was to shrink it.
I tried 10 KB, 50 KB and 100 KB. 50 KB was the best of the three and all of them were slower than the original 256 KB. The buffer was not the problem: while the pipeline still had to read essence speculatively to build chunk information, a large buffer was doing useful work by amortising round trips, and shrinking it just added round trips.
Only once chunk construction came from the index table did a small buffer make sense: with reads now precisely targeted, 64 KB at both the storage and format layers stopped pulling in data nothing would use. The ordering mattered: the same change that failed on its own succeeded once the reads around it became precise.
The hard part
Every codec needed a different answer
There was no single optimization here. ProRes and DNxHD in MOV could skip the read entirely. AVC-Intra could not, and needed a bounded read shaped by where H.264 puts its parameter sets. MXF could not use the MOV approach at all because of interleaving, and needed an index-driven derivation plus a filler-skipping read path plus a buffer change that only worked in that order.
The general principle — build the map from metadata, not from data — held throughout. Every concrete application of it was different, and each one had to be verified against real files rather than assumed from the specification.
Measured on a 4-core, 16 GB cloud instance. Each row compares the optimization enabled against the same build with it disabled, and against the previous release.
- Previous release
- Optimization off
- Optimization on
ProRes 2160p
17.6 GB · 3 min content
ProRes 1080p
2.5 GB · 2 min 5 s content
DNxHD 1080p
20.3 GB · 14 min 31 s content
Lower is faster. Each group is scaled independently, because the three files differ by an order of magnitude in duration.
Analysis time with the optimization enabled, against the same build with it disabled and against the previous release.
Internal benchmark · 4-core, 16 GB cloud instance
- ProRes 2160p, 17.6 GB
- 796 s previous release, 731 s disabled, 568 s enabled
- 22.3% / 28.6%
- ProRes 1080p, 2.5 GB
- 131 s previous release, 116 s disabled, 99 s enabled
- 14.7% / 24.4%
- DNxHD 1080p, 20.3 GB
- 1301 s previous release, 1294 s disabled, 1021 s enabled
- 21.1% / 21.5%
Percentages are gain against the disabled build and against the previous release respectively.
Engineering takeaway
The fastest read is the one that never happens. Most of the win came from noticing the data was already described somewhere cheaper to read.