Benchmark video

This example measures the performance of video decoding with different concurrency settings.

It benchmarks spdl.io.load_video() across multiple dimensions:

  • Various video resolutions (SD: 640x480, HD: 1920x1080, 4K: 3840x2160)

  • Different worker thread counts (1, 2, 4, 8) for parallel video processing

  • Different decoder thread counts (1, 2, 4) for FFmpeg’s internal threading

The benchmark evaluates how throughput changes with:

  1. Worker-level concurrency: Number of videos processed concurrently (via num_workers)

  2. Decoder-level concurrency: FFmpeg’s internal threading (via decoder_options={"threads": "X"})

Example

$ numactl --membind 0 --cpubind 0 python benchmark_video.py --output video_benchmark_results.csv
# Plot results
$ python benchmark_video_plot.py --input video_benchmark_results.csv --output video_benchmark_plot.png

Result

In many cases, when decoding H264 videos, using 2 threads give a good performance.

../_static/data/example-benchmark-video.png

Source

Source

Click here to see the source.
  1# Copyright (c) Meta Platforms, Inc. and affiliates.
  2# All rights reserved.
  3#
  4# This source code is licensed under the BSD-style license found in the
  5# LICENSE file in the root directory of this source tree.
  6
  7
  8"""This example measures the performance of video decoding with different concurrency settings.
  9
 10It benchmarks :py:func:`spdl.io.load_video` across multiple dimensions:
 11
 12- Various video resolutions (SD: 640x480, HD: 1920x1080, 4K: 3840x2160)
 13- Different worker thread counts (1, 2, 4, 8) for parallel video processing
 14- Different decoder thread counts (1, 2, 4) for FFmpeg's internal threading
 15
 16The benchmark evaluates how throughput changes with:
 17
 181. **Worker-level concurrency**: Number of videos processed concurrently (via ``num_workers``)
 192. **Decoder-level concurrency**: FFmpeg's internal threading (via ``decoder_options={"threads": "X"}``)
 20
 21**Example**
 22
 23.. code-block:: shell
 24
 25   $ numactl --membind 0 --cpubind 0 python benchmark_video.py --output video_benchmark_results.csv
 26   # Plot results
 27   $ python benchmark_video_plot.py --input video_benchmark_results.csv --output video_benchmark_plot.png
 28
 29**Result**
 30
 31In many cases, when decoding H264 videos, using 2 threads give a good performance.
 32
 33.. image:: ../../_static/data/example-benchmark-video.png
 34
 35"""
 36
 37__all__ = [
 38    "BenchmarkConfig",
 39    "create_video_data",
 40    "load_video_with_config",
 41    "main",
 42]
 43
 44import argparse
 45import os
 46import subprocess
 47import tempfile
 48from dataclasses import dataclass
 49
 50import spdl.io
 51
 52try:
 53    from examples.benchmark_utils import (  # pyre-ignore[21]
 54        BenchmarkResult,
 55        BenchmarkRunner,
 56        ExecutorType,
 57        get_default_result_path,
 58        save_results_to_csv,
 59    )
 60except ImportError:
 61    from spdl.examples.benchmark_utils import (
 62        BenchmarkResult,
 63        BenchmarkRunner,
 64        ExecutorType,
 65        get_default_result_path,
 66        save_results_to_csv,
 67    )
 68
 69
 70DEFAULT_RESULT_PATH: str = get_default_result_path(__file__)
 71
 72
 73@dataclass(frozen=True)
 74class BenchmarkConfig:
 75    """BenchmarkConfig()
 76
 77    Configuration for a single video decoding benchmark run."""
 78
 79    resolution: str
 80    """Video resolution label (e.g., "SD", "HD", "4K")"""
 81
 82    width: int
 83    """Video width in pixels"""
 84
 85    height: int
 86    """Video height in pixels"""
 87
 88    duration_seconds: float
 89    """Duration of the video in seconds"""
 90
 91    num_workers: int
 92    """Number of concurrent worker threads"""
 93
 94    decoder_threads: int
 95    """Number of FFmpeg decoder threads"""
 96
 97    iterations: int
 98    """Number of iterations per run"""
 99
100    num_runs: int
101    """Number of runs for statistical analysis"""
102
103
104def create_video_data(
105    width: int = 1920,
106    height: int = 1080,
107    duration_seconds: float = 5.0,
108    fps: int = 30,
109) -> bytes:
110    """Create a mock H.264 video file in memory for benchmarking.
111
112    Args:
113        width: Video width in pixels
114        height: Video height in pixels
115        duration_seconds: Duration of video in seconds
116        fps: Frames per second
117
118    Returns:
119        Video file as bytes (H.264 encoded in MP4 container)
120    """
121    with tempfile.NamedTemporaryFile(suffix=".mp4") as tmp_file:
122        output_path = tmp_file.name
123
124        cmd = [
125            "ffmpeg",
126            "-f",
127            "lavfi",
128            "-i",
129            f"testsrc=duration={duration_seconds}:size={width}x{height}:rate={fps}",
130            "-c:v",
131            "libx264",
132            "-preset",
133            "ultrafast",
134            "-pix_fmt",
135            "yuv420p",
136            "-y",
137            output_path,
138        ]
139
140        subprocess.run(
141            cmd,
142            check=True,
143            stdout=subprocess.DEVNULL,
144            stderr=subprocess.DEVNULL,
145        )
146
147        with open(output_path, "rb") as f:
148            video_data = f.read()
149
150        return video_data
151
152
153def load_video_with_config(
154    video_data: bytes, decoder_threads: int
155) -> spdl.io.CPUBuffer:
156    """Load video data using spdl.io.load_video with specified decoder threads.
157
158    Args:
159        video_data: Video file data as bytes
160        decoder_threads: Number of threads for FFmpeg decoder
161
162    Returns:
163        Decoded video frames as CPUBuffer
164    """
165    decode_config = spdl.io.decode_config(
166        decoder_options={"threads": str(decoder_threads)}
167    )
168    return spdl.io.load_video(video_data, decode_config=decode_config)
169
170
171def _parse_args() -> argparse.Namespace:
172    """Parse command line arguments for the benchmark script.
173
174    Returns:
175        Parsed command line arguments
176    """
177    parser = argparse.ArgumentParser(description="Benchmark video decoding performance")
178    parser.add_argument(
179        "--output",
180        type=lambda p: os.path.realpath(p),
181        default=DEFAULT_RESULT_PATH,
182        help="Output file path.",
183    )
184    return parser.parse_args()
185
186
187def main() -> None:
188    """Run comprehensive benchmark suite for video decoding performance.
189
190    Benchmarks video decoding across different resolutions (SD, HD, 4K),
191    worker thread counts (1, 2, 4, 8), and decoder thread counts (1, 2, 4).
192    """
193    args = _parse_args()
194
195    video_configs = [
196        ("SD", 640, 480, 5.0),
197        ("HD", 1920, 1080, 5.0),
198        ("4K", 3840, 2160, 5.0),
199    ]
200
201    worker_counts = [1, 2, 4, 8]
202    decoder_thread_counts = [1, 2, 4]
203
204    results: list[BenchmarkResult[BenchmarkConfig]] = []
205
206    for resolution, width, height, duration in video_configs:
207        print(f"\nCreating {resolution} video ({width}x{height}, {duration}s)...")
208        video_data = create_video_data(
209            width=width, height=height, duration_seconds=duration
210        )
211        print(f"Video size: {len(video_data) / 1024 / 1024:.2f} MB")
212
213        print(f"\n{resolution} ({width}x{height})")
214        print("Workers,Decoder Threads,QPS,CI Lower,CI Upper,CPU %")
215
216        for num_workers in worker_counts:
217            with BenchmarkRunner(
218                executor_type=ExecutorType.THREAD,
219                num_workers=num_workers,
220            ) as runner:
221                for decoder_threads in decoder_thread_counts:
222                    config = BenchmarkConfig(
223                        resolution=resolution,
224                        width=width,
225                        height=height,
226                        duration_seconds=duration,
227                        num_workers=num_workers,
228                        decoder_threads=decoder_threads,
229                        iterations=num_workers * 2,
230                        num_runs=5,
231                    )
232
233                    result, output = runner.run(
234                        config,
235                        lambda data=video_data,
236                        threads=decoder_threads: load_video_with_config(data, threads),
237                        config.iterations,
238                        num_runs=config.num_runs,
239                    )
240
241                    results.append(result)
242
243                    print(
244                        f"{num_workers},{decoder_threads},"
245                        f"{result.qps:.2f},{result.ci_lower:.2f},{result.ci_upper:.2f},"
246                        f"{result.cpu_percent:.1f}"
247                    )
248
249    save_results_to_csv(results, args.output)
250    print(
251        f"\nBenchmark complete. To generate plots, run:\n"
252        f"python benchmark_video_plot.py --input {args.output} "
253        f"--output {args.output.replace('.csv', '.png')}"
254    )
255
256
257if __name__ == "__main__":
258    main()

API Reference

Functions

create_video_data(width: int = 1920, height: int = 1080, duration_seconds: float = 5.0, fps: int = 30) bytes[source]

Create a mock H.264 video file in memory for benchmarking.

Parameters:
  • width – Video width in pixels

  • height – Video height in pixels

  • duration_seconds – Duration of video in seconds

  • fps – Frames per second

Returns:

Video file as bytes (H.264 encoded in MP4 container)

load_video_with_config(video_data: bytes, decoder_threads: int) CPUBuffer[source]

Load video data using spdl.io.load_video with specified decoder threads.

Parameters:
  • video_data – Video file data as bytes

  • decoder_threads – Number of threads for FFmpeg decoder

Returns:

Decoded video frames as CPUBuffer

main() None[source]

Run comprehensive benchmark suite for video decoding performance.

Benchmarks video decoding across different resolutions (SD, HD, 4K), worker thread counts (1, 2, 4, 8), and decoder thread counts (1, 2, 4).

Classes

class BenchmarkConfig[source]

Configuration for a single video decoding benchmark run.

decoder_threads: int

Number of FFmpeg decoder threads

duration_seconds: float

Duration of the video in seconds

height: int

Video height in pixels

iterations: int

Number of iterations per run

num_runs: int

Number of runs for statistical analysis

num_workers: int

Number of concurrent worker threads

resolution: str

Video resolution label (e.g., “SD”, “HD”, “4K”)

width: int

Video width in pixels