---
title: "Allocations, constant memory and I/O, 0.8"
url: https://recursiveai.org/onemedia/docs/bench-memory-io-0-8/
description: "The 0.8 allocation, memory and I/O tables: zero bytes per packet on the pull path for 41 files, peak memory from a small file to one past 4 GiB for demux, remux, packaging, repair and tag edits, and bytes read per probe."
version: "0.8.0-preview.2"
source: https://github.com/recursive-ai-oss/onemedia-csharp/blob/16456e4d405f037d1b029e967dfc7d028ae3a0d5/docs/bench-results/0.8-allocations-memory-io.md
---

# Allocations, constant memory and I/O, 0.8

Measured on the x64 worker (AMD Ryzen 9 7950X3D, .NET 10.0.13); method in [bench.md](https://recursiveai.org/onemedia/docs/bench/) and [harness.md](https://recursiveai.org/onemedia/docs/harness/). The speed rows are in [0.8-x64.md](https://recursiveai.org/onemedia/docs/bench-x64-0-8/) and [0.8-arm64.md](https://recursiveai.org/onemedia/docs/bench-arm64-0-8/).

## Allocation gate: zero bytes per packet on the pull path

`bench pullalloc FILE...`: after a full first pass the packet loop `TryReadPacket(out PacketView)` runs again (from the start, or over the second half of the stream when the source cannot seek) with `GC.GetAllocatedBytesForCurrentThread` read around it. The gate is 0 bytes; **all 41 files, one or more per reader, pass**. The owned path (`TryReadOwnedPacket` with the shared `MemoryPool<byte>`, each packet disposed at once) is reported: 24 bytes per packet is the `IMemoryOwner` wrapper; the AVI, Ogg and RF64 figures above 24 are the pool growing for packets larger than any before.

| File | Reader | Packets | Pull path B/packet | Owned path B/packet |
|---|---|---:|---:|---:|
| blender/BigBuckBunny_320x180.mp4 | ISO BMFF | 42264 | 0.0 | 24.0 |
| blender/big_buck_bunny_480p_h264.mov | ISO BMFF (QuickTime) | 42276 | 0.0 | 24.0 |
| generated/editlist/audio-aac-priming.m4a | ISO BMFF (audio) | 189 | 0.0 | 24.0 |
| generated/3gp/h263-amrnb.3gp | ISO BMFF (3GP) | 261 | 0.0 | 24.0 |
| blender/BigBuckBunny_640x360.m4v | ISO BMFF | 40004 | 0.0 | 24.0 |
| generated/mkv/av1-vorbis.mkv | Matroska | 309 | 0.0 | 24.0 |
| generated/mkv-live/vp9-opus-unknown-size.webm | WebM (unknown-size clusters) | 321 | 0.0 | 24.0 |
| generated/mka/audio-aac.mka | Matroska audio | 189 | 0.0 | 24.0 |
| blender/big_buck_bunny_480p_stereo.avi | AVI | 39145 | 0.0 | 37.5 |
| generated/asf/wmv1-wmav1.wmv | ASF | 214 | 0.0 | 24.0 |
| generated/asf/mp3-tagged-unicode.wma | ASF | 168 | 0.0 | 24.0 |
| generated/asf/msmpeg4v3-mp3.asf | ASF | 288 | 0.0 | 24.0 |
| generated/mpeg/large/ts-2gb-seed.ts | MPEG-TS | 6750 | 0.0 | 24.0 |
| generated/mpeg/m2ts/h264-aac.m2ts | M2TS | 289 | 0.0 | 24.0 |
| samples/avchd/dean00007.mts | AVCHD M2TS | 1219 | 0.0 | 24.0 |
| generated/mpeg/ps/dvd-ac3-ntsc.vob | MPEG-PS | 263 | 0.0 | 24.0 |
| generated/mpeg/ps/h264-mp2.mpg | MPEG-PS | 267 | 0.0 | 24.0 |
| generated/xiph-flv/flv/aac-mono-22k.flv | FLV | 88 | 0.0 | 24.0 |
| blender/big_buck_bunny_480p_stereo.ogg | Ogg | 50290 | 0.0 | 32.8 |
| generated/ogg/audio-opus.opus | Ogg Opus | 201 | 0.0 | 24.0 |
| generated/xiph-flv/ogg/theora.ogv | Ogg Theora | 120 | 0.0 | 24.0 |
| generated/xiph-flv/flac/192k-24bit.flac | FLAC | 47 | 0.0 | 24.0 |
| generated/pcm/rf64-pcm_s16le-stereo48k.wav | RF64/WAV | 36 | 0.0 | 24.0 |
| generated/pcm/w64-pcm_f32le-stereo48k.w64 | Wave64 | 36 | 0.0 | 24.0 |
| generated/pcm/aiff-pcm_f32be-stereo48k.aiff | AIFF | 282 | 0.0 | 24.0 |
| generated/pcm/caf-alac-mono44k.caf | CAF | 17 | 0.0 | 24.0 |
| generated/audio-es/aac/lc-32k-stereo.aac | ADTS | 95 | 0.0 | 24.0 |
| generated/audio-es/ac3/ac3-32k-stereo-96k.ac3 | AC-3 | 63 | 0.0 | 24.0 |
| generated/audio-es/ac3/eac3-32k-stereo-96k.eac3 | E-AC-3 | 63 | 0.0 | 24.0 |
| samples/mpeg/H264_artifacts_motion.h264 | Annex B H.264 | 600 | 0.0 | 24.0 |
| generated/mpeg/es/av1-1.ivf | IVF | 72 | 0.0 | 24.0 |
| generated/mpeg/es/av1-1.obu | AV1 OBU | 72 | 0.0 | 24.0 |
| generated/dv/dv25-ntsc.dv | DV | 89 | 0.0 | 24.0 |
| generated/audio-es/mp3/long-60s.mp3 | MP3 | 2298 | 0.0 | 24.0 |
| generated/mpeg/ts/mpeg2-interlaced.ts | MPEG-TS | 267 | 0.0 | 24.0 |
| generated/bench/frag-bbb-1080p.mp4 | ISO BMFF | 42276 | 0.0 | 24.0 |
| generated/fmp4/audio-aac.mp4 | ISO BMFF | 189 | 0.0 | 24.0 |
| generated/audio-es/amr/nb-10200.amr | AMR | 151 | 0.0 | 24.0 |
| generated/mp4/audio-aac-51.mp4 | ISO BMFF | 189 | 0.0 | 24.0 |
| generated/asf/wmv1-wmav1.wmv | ASF | 214 | 0.0 | 24.0 |
| generated/asf/wmv2-wmav2.wmv | ASF | 214 | 0.0 | 24.0 |

Fixed on the way: the ASF reader allocated a reassembly buffer for most video objects of `wmv1-wmav1.wmv` (77 B per packet) because its buffer pool threw away smaller buffers when a larger one was asked for; it now hands out the smallest buffer that fits and rounds new ones up, and the file reads at 0.

## Constant working set

`bench memcheck AREA FILE OUTDIR` runs one operation on one file in a fresh process and prints the process's peak resident set (`bench/memory.sh` runs the matrix; each output, up to 4 GiB, is deleted after its run). The files past 4 GiB come from `tools/corpus/generate.sh` on the worker and are never committed. The resident set before any work is 28.9 MiB. Remux goes to Matroska on disk, packaging to HLS (CMAF) on disk, repair rebuilds the index into a new file, and the tag edit is `SaveAs` with four common fields changed.

| Area | Input | Input size | Output size | Peak MiB | Seconds |
|---|---|---:|---:|---:|---:|
| demux | video.mp4 | 26.6 MiB | 0.0 MiB | 35.5 | 0.0 |
| demux | over4gb-moov-at-end.mp4 | 4173.2 MiB | 0.0 MiB | 37.6 | 0.3 |
| demux | h264-aac.mkv | 0.2 MiB | 0.0 MiB | 36.1 | 0.0 |
| demux | over4gb.mkv | 4239.9 MiB | 0.0 MiB | 41.0 | 0.5 |
| demux | tagged-mpeg4-mp3.avi | 0.4 MiB | 0.0 MiB | 35.7 | 0.0 |
| demux | odml-4p6gb.avi | 4431.7 MiB | 0.0 MiB | 37.6 | 0.4 |
| demux | wav-pcm_s16le-mono44k.wav | 0.3 MiB | 0.0 MiB | 35.0 | 0.0 |
| demux | over4gb-rf64.wav | 4211.4 MiB | 0.0 MiB | 35.9 | 11.8 |
| demux | mpeg2-interlaced.ts | 1.1 MiB | 0.0 MiB | 35.8 | 0.0 |
| demux | ts-2gb.ts | 2262.1 MiB | 0.0 MiB | 46.7 | 0.8 |
| remux | video.mp4 | 26.6 MiB | 25.8 MiB | 48.5 | 0.1 |
| remux | over4gb-moov-at-end.mp4 | 4173.2 MiB | 4172.9 MiB | 67.8 | 1.5 |
| remux | h264-aac.mkv | 0.2 MiB | 0.2 MiB | 49.5 | 0.1 |
| remux | over4gb.mkv | 4239.9 MiB | 4240.1 MiB | 84.4 | 5.4 |
| remux | tagged-mpeg4-mp3.avi | 0.4 MiB | 0.4 MiB | 50.4 | 0.1 |
| remux | odml-4p6gb.avi | 4431.7 MiB | 4431.6 MiB | 64.5 | 5.2 |
| package | video.mp4 | 26.6 MiB | 25.8 MiB | 46.9 | 0.1 |
| package | over4gb-moov-at-end.mp4 | 4173.2 MiB | 4172.8 MiB | 112.3 | 2.5 |
| package | h264-aac.mkv | 0.2 MiB | 0.2 MiB | 44.1 | 0.1 |
| package | over4gb.mkv | 4239.9 MiB | 4240.2 MiB | 130.3 | 2.8 |
| repair | video.mp4 | 26.6 MiB | 26.6 MiB | 37.8 | 0.0 |
| repair | over4gb-moov-at-end.mp4 | 4173.2 MiB | 4173.2 MiB | 50.7 | 1.1 |
| repair | h264-aac.mkv | 0.2 MiB | 0.2 MiB | 41.7 | 0.0 |
| repair | over4gb.mkv | 4239.9 MiB | 4239.9 MiB | 57.0 | 1.3 |
| repair | tagged-mpeg4-mp3.avi | 0.4 MiB | 0.4 MiB | 34.8 | 0.0 |
| repair | odml-4p6gb.avi | 4431.7 MiB | 4431.7 MiB | 42.7 | 1.3 |
| tag | video.mp4 | 26.6 MiB | 26.6 MiB | 38.7 | 0.1 |
| tag | over4gb-moov-at-end.mp4 | 4173.2 MiB | 4173.2 MiB | 39.2 | 3.3 |
| tag | h264-aac.mkv | 0.2 MiB | 0.2 MiB | 40.9 | 0.1 |
| tag | over4gb.mkv | 4239.9 MiB | 4239.9 MiB | 44.2 | 3.7 |
| tag | wav-pcm_s16le-mono44k.wav | 0.3 MiB | 0.3 MiB | 37.7 | 0.0 |
| tag | over4gb-rf64.wav | 4211.4 MiB | 4211.4 MiB | 39.1 | 3.6 |

Demux, repair and tag edits peak within 10 MiB of their small-file figure at 4 GiB. Remux grows from about 50 to 65-85 MiB. **Packaging follows the segment, not the input** (u76; the first run of the table above had 173 and 189 MiB for the two 4 GiB inputs and 50 for the 27 MB file). The packager holds one segment per track, and a segment is as large as the bit rate times the segment duration: these two test files run at 43 Mbit/s, so a 6 s segment is 32 MiB. The cause of the extra memory was copies of that segment, not the segments written so far: the muxer kept the open fragment in one array that grew by doubling through the shared pool, and the segment buffer doubled beside it, leaving a chain of dead arrays. The fragment is now kept in reusable 256 KiB chunks and the segment buffer is sized once from the muxer's byte count and reused, so the 4 GiB inputs peak at 112 and 130 MiB (two runs each, within 0.3 MiB) and the 27 MB file at 47. The 4.2 GB MKV at other segment lengths: 2 s (10.7 MiB segments) 92 MiB, 6 s 130, 12 s 192, so the peak is about 29 MiB plus 2.5 times the largest segment; a file of any length with the same bit rate peaks the same. The buffer is bounded by `MediaLimits.MaxSegmentBytes` (256 MiB default; a larger segment raises `MediaLimitExceededException`) and documented in [packaging.md](https://recursiveai.org/onemedia/docs/packaging/). The live-mode soak is below.

## Structural-only reads (probe and metadata)

[io-results.txt](https://github.com/recursive-ai-oss/onemedia-csharp/blob/16456e4d405f037d1b029e967dfc7d028ae3a0d5/docs/io-results.txt) lists every file: bytes read and calls through strace on the file, and bytes / HTTP range requests through the local range server, next to ffprobe 7.0.2, MediaInfo 26.10 and ExifTool 13.60 on the same files (1,000+ files, 13 families). "Ours" opens the file and reads its tags and basic facts. Summary of the `ours/http` column against the two tools that can read over HTTP:

| Family | Files | Ours bytes median / max | Ours requests median / max | ffprobe bytes median / max | MediaInfo bytes median / max |
|---|---:|---|---|---|---|
| isobmff | 131 | 8,143 / 1,634,188 | 8 / 11 | 234,829 / 70,064,401 | 334,634 / 185,319,424 |
| matroska | 94 | 37,163 / 4,705,632 | 12 / 16 | 180,224 / 5,210,112 | 238,726 / 194,398,837 |
| asf | 55 | 36,864 / 36,864 | 3 / 3 | 180,224 / 5,128,192 | 379,050 / 6,750,914 |
| mpa | 92 | 53,046 / 282,624 | 3 / 4 | 49,353 / 360,576 | 48,950 / 196,768 |
| adts | 11 | 41,342 / 88,935 | 3 / 3 | 111,738 / 254,517 | 37,406 / 84,999 |
| ac3 | 14 | 64,433 / 244,736 | 3 / 3 | 60,337 / 180,224 | 60,337 / 180,224 |
| amr | 17 | 8,166 / 13,255 | 3 / 3 | 4,083 / 9,159 | 4,083 / 9,159 |
| xiph | 72 | 69,666 / 143,360 | 4 / 5 | 67,301 / 638,747 | 55,832 / 409,600 |
| flv | 30 | 69,632 / 69,647 | 3 / 5 | 180,224 / 2,293,760 | 215,071 / 5,866,523 |
| pcm | 225 | 20,480 / 36,864 | 3 / 4 | 95,276 / 5,095,424 | 40,936 / 425,984 |
| avi | 70 | 114,509 / 273,716 | 9 / 13 | 404,606 / 5,357,568 | 572,786 / 12,648,448 |
| dv | 14 | 69,632 / 69,632 | 3 / 3 | 4,816,896 / 5,226,496 | 114,688 / 393,376 |
| mpeg | 151 | 266,240 / 7,080,536 | 3 / 26 | 368,640 / 14,294,576 | 227,292 / 118,964,404 |

Ours is structural: the median probe reads 8 KB (ISO BMFF, AMR) to 266 KB (MPEG), in 3 to 12 requests, on files from 0.2 MB to 4.4 GB, and a movie box at the end of a 4.4 GB file costs one seek (1,069,403 bytes in 7 requests). Where the figure is above 1 MiB the format forces a scan; each case:

| Family | File | File size | Ours bytes | Requests | Why |
|---|---|---:|---:|---:|---|
| mpeg | foxksaz.ts | 30,527,488 | 7,080,536 | 26 | front 5 MB plus the 1 MiB tail; the search for program tables may run to 6 MB (docs/mpeg.md), no range is read twice |
| mpeg | vid_0x80.ts | 8,400,000 | 6,818,388 | 25 | front 5 MB plus the 1 MiB tail; the search for program tables may run to 6 MB (docs/mpeg.md), no range is read twice |
| mpeg | HD-h264.ts | 126,511,028 | 6,294,272 | 23 | front 5 MB plus the 1 MiB tail; the search for program tables may run to 6 MB (docs/mpeg.md), no range is read twice |
| mpeg | test-ebu-422.40000.pakets.ts | 7,520,000 | 7,080,488 | 26 | front 5 MB plus the 1 MiB tail; the search for program tables may run to 6 MB (docs/mpeg.md), no range is read twice |
| matroska | aac-sbr-ffaac-slow.mkv | 15,728,640 | 4,705,632 | 16 | ffprobe reads the same 4.7 MB of aac-sbr-ffaac-slow.mkv (MediaInfo reads the whole file): the file needs the cluster headers to describe its AAC track |
| mpeg | libmpeg2bug.ts | 5,242,880 | 2,363,128 | 8 | front 5 MB plus the 1 MiB tail; the search for program tables may run to 6 MB (docs/mpeg.md), no range is read twice |
| matroska | ticket-a_aac.mkv | 6,580,813 | 2,124,327 | 16 | ffprobe reads the same 4.7 MB of aac-sbr-ffaac-slow.mkv (MediaInfo reads the whole file): the file needs the cluster headers to describe its AAC track |
| mpeg | res_change_ffmpeg_aspect.ts | 1,678,669 | 1,576,993 | 5 | front 5 MB plus the 1 MiB tail; the search for program tables may run to 6 MB (docs/mpeg.md), no range is read twice |
| isobmff | quicktime.mov | 280,986,395 | 1,634,188 | 6 | the movie box itself is 1.0-1.6 MB (ffprobe reads 2.0 MB of quicktime.mov): one range, read once |
| mpeg | ts-2gb-seed.ts | 182,561,724 | 1,576,976 | 5 | no index in the format: docs/mpeg.md bounds the probe to 5,000,000 bytes from the front plus 1 MiB at the tail |
| mpeg | ts-2gb.ts | 2,371,987,540 | 1,576,976 | 5 | no index in the format: docs/mpeg.md bounds the probe to 5,000,000 bytes from the front plus 1 MiB at the tail |
| mpeg | mpeg2-svcd.mpg | 1,264,256 | 1,575,104 | 5 | no index in the format: docs/mpeg.md bounds the probe to 5,000,000 bytes from the front plus 1 MiB at the tail |
| mpeg | dvd-ac3-ntsc.vob | 2,308,096 | 1,572,878 | 5 | no index in the format: docs/mpeg.md bounds the probe to 5,000,000 bytes from the front plus 1 MiB at the tail |
| mpeg | dvd-pcm.vob | 1,368,064 | 1,572,878 | 5 | no index in the format: docs/mpeg.md bounds the probe to 5,000,000 bytes from the front plus 1 MiB at the tail |

36 of the 1,000+ files read more than 1 MiB. The ISO BMFF and Matroska cases read about as much as ffprobe does. The MPEG cases below the documented bound are the front and tail windows; **The four transport streams that read past the documented bound in the first run are fixed (u76).** Per stream, ours over HTTP before and after (bytes / requests), next to ffprobe 7.0.2 and MediaInfo 26.10 over HTTP:

| Stream (size) | Before | After | ffprobe 7.0.2 | MediaInfo 26.10 | What it was |
|---|---|---|---|---|---|
| foxksaz.ts (30,527,488) | 25,425,648 / 96 | 7,080,536 / 26 | 9,818,256 / 4 | 30,527,488 / 1 | no tables in the file: the search for them ran 24 MB; it now stops at 6 MB and says so (`ts.tables-not-found`) |
| vid_0x80.ts (8,400,000) | 12,059,900 / 45 | 6,818,388 / 25 | 14,294,576 / 5 | 8,400,000 / 1 | its one PAT is at 5.57 MB; the 5.77 MB front was read, then read again |
| HD-h264.ts (126,511,028) | 11,535,784 / 43 | 6,294,272 / 23 | 6,049,936 / 4 | 118,866,100 / 2 | tables after the first payloads: the 5.2 MB front was read twice |
| test-ebu-422.40000.pakets.ts (7,520,000) | 9,096,976 / 34 | 7,080,488 / 26 | 10,686,608 / 4 | 7,520,000 / 1 | no tables: the whole file was read, then a rewind read the front again |

The cause was the rewind: after the tables are found, or after the streams are guessed, the probe goes back to the start of the stream so each stream's first frame describes it, and that second pass went to the source again. It is now answered from a copy of the first pass (at most 6.5 MB, held only while the stream opens), so no range is requested twice, and the table search is bounded at 6 MB. What remains above 5 MB + 1 MiB is exactly the table search: `vid_0x80.ts` needs 5.77 MB to reach its PAT and PMT, and a stream without tables costs the full 6 MB before it is guessed (docs/mpeg.md). The whole MPEG family (151 files) was measured again: only these four, `libmpeg2bug.ts` and `res_change_ffmpeg_aspect.ts` (both a little lower) changed; the medians are the same (ours / ffprobe / MediaInfo / ExifTool, bytes and reads: transport streams 476,352 B in 3 / 470,524 B in 23 / 240,264 B in 4 / 58,864 B in 9; M2TS 836,352 in 3 / 850,760 in 34 / 582,144 in 9 / 91,888 in 13; program streams 1,472,526 in 4 / 1,198,224 in 46 / 1,316,160 in 20 / 284,400 in 36; elementary streams 143,531 in 2 / 65,536 in 2 / 110,562 in 2). The full table is [io-results.txt](https://github.com/recursive-ai-oss/onemedia-csharp/blob/16456e4d405f037d1b029e967dfc7d028ae3a0d5/docs/io-results.txt).

**AVI changed in this stage:** the header list is read through a 32 KiB front cache and a read-ahead window. Counted as read-class system calls on the file made by the `avi-probe` bench cell (open, track list, duration, tags; `bench/iocount.sh`, strace), a probe makes 7 to 9 reads of the file instead of 85 to 94: the 4.6 GB OpenDML file `odml-4p6gb.avi` 94 before (commit 72741b0) and 7 after (172,032 bytes), `tagged-mpeg4-mp3.avi` 89 and 9, `GoneNutty.avi` 85 before. The 0.8.0-preview.1 tag and the current code give the same 7 and 9; the "50" and "48" first written for the 4.6 GB file in this file and in the 0.8 CHANGELOG entry do not reproduce and were wrong. The io table above counts something else: `ours/strace` in [io-results.txt](https://github.com/recursive-ai-oss/onemedia-csharp/blob/16456e4d405f037d1b029e967dfc7d028ae3a0d5/docs/io-results.txt) is the number of reads our reader asks of the file source while it opens the file and reads its tags and basic facts (`io --metadata`: 10 for the 4.6 GB file, 8 without the metadata), and `ours/http` the HTTP range requests of the same run (9 requests, 172,044 bytes). The avi-probe row went from 1.95x to 0.52x of the fastest comparator. Over HTTP the median probe is 114,509 B in 9 requests (before: 4,559 B in 32 requests; the whole table is above). It trades bytes for round trips; an 8 KiB window gave 45 KB in 10 requests and is left as a follow-up because the speed rows were taken with this build.
