FFmpeg benchmark variance
Read what happens when one FFmpeg command runs ten times. Output was byte-identical, encode time varied 2.13x, and one of our own claims did not survive.
We did not set out to measure this. It fell out of running the same source through ten different benchmark sweeps and noticing that one command appeared in several of them.
Short version. FFmpeg is deterministic and our stopwatch is not. The same command produced the same bytes every time, to the byte, while taking anywhere from 898 ms to 1,912 ms to do it. That is wide enough to have invalidated a claim we had already published, and this post is partly a retraction.
The command
ffmpeg -i sample.mp4 -c:v libx264 -crf 23 -preset medium -an -t 5 out.mp4It appears in ten of our sweeps, because libx264 -crf 23 -preset medium is the natural control to compare everything else against. Here is every run:
| Sweep | Encode | Cost | Output |
|---|---|---|---|
| presets / medium | 1,912 ms | $0.0039 | 443,351 B |
| ratecontrol / CRF 23 | 1,596 ms | $0.0031 | 443,351 B |
| crf / CRF 23 | 982 ms | $0.0025 | 443,351 B |
| gop / -g 250 | 945 ms | $0.0024 | 443,351 B |
| ladder / 720p | 944 ms | $0.0023 | 443,351 B |
| threads / auto | 924 ms | $0.0023 | 443,351 B |
| gpu / libx264 medium | 913 ms | $0.0030 | 443,351 B |
| tune / no tune | 912 ms | $0.0025 | 443,351 B |
| pixfmt / yuv420p | 899 ms | $0.0024 | 443,351 B |
| codecs / H.264 | 898 ms | $0.0024 | 443,351 B |
443,351 bytes, ten times out of ten. Not approximately. Identically.
898 ms to 1,912 ms, a 2.13x spread on the same work.
Why the output is identical and the time is not
x264 with a fixed seed and fixed settings is a deterministic function. Same input, same flags, same bytes. That is a genuinely useful property and it is why the size column on every benchmark on this site can be trusted without repeating anything.
The time is a property of the machine, not the codec. These jobs run on shared infrastructure, so an encode competes for CPU with whatever else is scheduled, and the container may be warm or cold. None of that changes the output. All of it changes the clock.
Cost follows time, because compute is billed per second. So the cost column inherits the same noise: $0.0023 to $0.0039, a 1.72x spread for identical work.
What this cost us
We had published, in NVENC vs libx264 compared, that hardware encoding was not faster than libx264 on a short clip. The evidence was NVENC at 1,225 ms against libx264 at 913 ms.
913 ms is the bottom of a band that runs to 1,912 ms. NVENC’s 1,225 ms sits inside it. The comparison cannot be made from these samples, and the claim has been retracted on that page.
The cost claim on the same page survived, but only in a weaker form. We had said hevc_nvenc was 3.8x cheaper. Against a CPU cost that ranges $0.0023 to $0.0039, the true multiple is somewhere between 2.9x and 4.9x. hevc_nvenc at $0.0008 is below the entire band, so the direction is safe. The decimal was not.
The rule we now use
A difference smaller than about 2x in a single timing sample is not a finding. It is within the noise of one machine on one day.
Applied to what is already published here:
ultrafastat 238 ms againstveryslowat 4,635 ms is a 19x gap. Safe.- Adjacent x264 presets separated by a few hundred milliseconds are not separable. That page now says to read the size column and not the time column.
- Every size comparison on this site stands unchanged, because sizes do not move.
- The scaling-algorithm sweep, where five algorithms landed within 17 ms of each other, was already reported as “no measurable difference in time”. That conclusion is stronger now, not weaker: the differences were inside the noise floor and we said so.
Why publish the retraction
Because the alternative is a site full of numbers nobody can check.
Every benchmark on the internet is one sample unless it says otherwise, and almost none of them say otherwise. We only caught this because the same command happened to appear in ten sweeps and the sizes were suspiciously identical while the times were not.
The honest version of a benchmark states which of its columns are repeatable. Ours are: sizes are exact, timings are indicative, costs track timings. Any page here that draws a conclusion from a small timing difference is a page that needs fixing, and we would rather fix them than defend them.
Then we found the cause
The obvious fix is to repeat each variant and report a median. So we did, five runs per variant, on a sweep whose entire subject is timing. The result was more interesting than a median.
| Threads | Median | Range | Spread |
|---|---|---|---|
| 1 | 3,644 ms | 3,590 to 3,657 | 1.9% |
| 2 | 2,122 ms | 2,108 to 2,181 | 3.5% |
| 4 | 1,510 ms | 1,490 to 1,511 | 1.4% |
| 8 | 1,220 ms | 1,183 to 1,288 | 8.9% |
| auto | 1,033 ms | 918 to 1,699 | 85% |
With the thread count pinned, FFmpeg timings are reproducible to within a few percent. The wild variance lives almost entirely in auto.
That makes sense once stated. x264’s default threading adapts to the machine: it asks how many cores are available and sizes itself accordingly. On shared infrastructure that answer changes between runs, so the encoder is doing a different amount of parallel work each time. Pin -threads and you have pinned the workload.
Every one of those ten control runs used default threading. The 2.13x spread was not a mystery about measurement. It was the encoder correctly adapting to a machine whose spare capacity kept moving.
The rule, updated
If a sweep’s conclusion depends on timing, pin -threads and repeat it. Under those conditions single-digit percentage differences are real.
If it does not, single samples are fine for size, which is deterministic regardless.
That is cheaper than repeating everything. The size-led sweeps on this site, which is most of them, need no repetition at all. The timing-led ones get five runs and a pinned thread count, and pay for it.
Frequently asked questions
Is FFmpeg output deterministic?
Yes. The same command with the same input and settings produced byte-identical output on all ten runs, 443,351 bytes every time. Only the clock moved.
Why do FFmpeg benchmark timings vary so much?
Default threading. x264 asks the machine how many cores are free and sizes itself accordingly, so on shared infrastructure it does a different amount of parallel work each run. Pinning -threads removed almost all of the variance.
How many times should I repeat an encoding benchmark?
For size, once, because the encoder is deterministic. For timing, repeat it with -threads pinned, and treat single-sample differences under about 2x as noise.
Does encode cost vary too?
Yes, because compute is billed by the second and cost tracks time. Identical work ranged $0.0023 to $0.0039, a 1.72x spread.
