FFmpeg threads benchmark, 8 threads is 3x not 8x
Compare FFmpeg libx264 thread counts from 1 to 8 over five runs each. Eight threads was 2.99x faster than one, and pinned threads made the timings repeatable.
Eight libx264 threads encoded 2.99x faster than one thread, not 8x, and four threads already delivered 2.41x. The more useful result is about measurement: with -threads pinned, five runs of the same encode landed within a few percent of each other, while the default auto setting swung 85%. Pin the thread count before you benchmark anything. The shared clip and setup are on how we benchmark.
ffmpeg -i sample.mp4 -c:v libx264 -crf 23 -preset medium -threads 4 -an -t 5 out.mp4Each variant changes only the -threads value: 1, 2, 4, 8 and 0 (auto).
| Variant | Encode | Size | Cost |
|---|---|---|---|
| 1 thread | 3644 ms | 387.8 KB | $0.0053 |
| 2 threads | 2122 ms | 387.9 KB | $0.0036 |
| 4 threads | 1510 ms | 387.7 KB | $0.0029 |
| 8 threads | 1220 ms | 387.2 KB | $0.0026 |
| auto (0) | 1033 ms fastest | 433.0 KB | $0.0024 |
The component above prints one time and one cost per variant. For this sweep the time is the median of five runs and the cost is from the first run. Its footer note about single samples describes the other sweeps on the site, not this one. The ranges below come from the same five runs, each of which is a separate recorded job.
Four threads is the knee
| Threads | Median | Speedup vs 1 |
|---|---|---|
| 1 | 3,644 ms | 1.00x |
| 2 | 2,122 ms | 1.72x |
| 4 | 1,510 ms | 2.41x |
| 8 | 1,220 ms | 2.99x |
Each doubling bought less. One to two threads gave 1.72x, two to four another 1.4x, four to eight only 1.24x more.
That is the shape Amdahl’s law predicts. Part of an encode is serial: rate control decisions depend on earlier frames, and the bitstream is written in order. Threads speed up the rest, so the curve flattens. If you are sizing a container or a worker, four threads gets most of what eight does on this kind of encode.
A 5-second clip is the hard case for threading, since the serial setup is a large share of the work. Longer inputs should scale better, so treat 2.99x as a floor for eight threads on this preset.
Pinned threads repeat, auto does not
| Threads | Range over 5 runs | Spread |
|---|---|---|
| 1 | 3,590 to 3,657 ms | 1.9% |
| 2 | 2,108 to 2,181 ms | 3.5% |
| 4 | 1,490 to 1,511 ms | 1.4% |
| 8 | 1,183 to 1,288 ms | 8.9% |
| auto | 918 to 1,699 ms | 85% |
With a pinned count, repeated runs agree to within a few percent. With auto, the same work took anywhere from 918 to 1,699 ms.
On auto, x264 picks its thread count from the CPUs it can see, roughly 1.5 threads per core. On a shared host that is likely more threads than the container’s CPU share. The count itself was stable: all five auto runs wrote the same 443,351 bytes, and a different count writes different bytes (see the next section). So the workload did not change. How quickly those threads got scheduled did, because it depends on what else the host was running.
This is also the explanation behind a 2.13x spread I found in one unpinned command run ten times, which was enough to retract a published claim. If your FFmpeg timings wander, pin the threads before you look anywhere else.
An earlier version of this section said x264’s default “asks the machine how many cores are free and sizes itself accordingly”, so it did a different amount of work each run. The byte-identical output rules that out, and the paragraph above replaces it.
Auto was fastest, and wrote a different file
auto had the fastest median at 1,033 ms, ahead of eight explicit threads. That fits auto starting more than eight threads on this host, although I did not log the count it chose.
It also wrote a different file. Auto produced 443,351 bytes. The pinned counts all produced between 396,520 and 397,244 bytes, so the auto file was about 12% larger. Each variant’s size was identical across its own five runs, so this is a real difference, not noise.
I do not have a confident explanation. x264 threads by frames by default, and each extra frame thread starts a frame before the frames it references are finished, which restricts the search a little. If that cost is non-linear it could stay negligible up to eight threads (one and eight threads came out within 0.15% of each other) and appear at whatever larger count auto chose. It stays a guess until someone logs the auto thread count.
The practical point holds without the mechanism: pinning threads changes your output, not just your speed. If you pin threads in production and benchmark on auto, you are not measuring what you ship.
What I would set
On a dedicated machine running one encode, leave it on auto and let x264 use the box.
When encodes run concurrently, pin the count to each job’s share of the CPUs. I have not measured concurrent throughput, so this rests on the contention shown above, not on a benchmark of it.
When benchmarking, always pin it. Running this sweep through the API made that cheap: each thread count was one job with the encode step timed apart from the download and upload, and five repeats of every variant ran in parallel.
Thread count is one of the settings in FFmpeg encoding settings compared. Of all of them, it is the one I would pin first, because it is the only one that decides whether the other numbers can be trusted.
Frequently asked questions
Does -threads 0 mean one thread in FFmpeg?
No. For libx264, -threads 0 means auto, and it wrote exactly the same file as leaving the flag out. Auto lets x264 choose a thread count from the CPUs it can see.
Should I pin threads when several FFmpeg jobs share a machine?
I would, though I have not measured it. Several encodes that each start threads for every core compete for the same CPUs. Giving each job a share, such as -threads 2 for four jobs on eight cores, is the usual advice.
