Skip to content

Benchmarks

dnjulek edited this page Jul 7, 2026 · 5 revisions

Benchmarks

vszipcl vs its installed reference filter at 1080p YUV420 in three sample types (u16 = YUV420P16, f16 = YUV420PH, f32 = YUV420PS). All numbers are frames/second (higher is better), produced by the repo's benchmark/ harness.

Method

Reproduce with:

pwsh benchmark/run.ps1                 # sweeps u16/f16/f32 x num_streams {1,2}, 2000 frames each
  • Hardware: NVIDIA GeForce RTX 3070 Ti (OpenCL / CUDA). The EEDI3 CPU reference runs on an AMD Ryzen 7 5700X3D (8C/16T). Same machine for all rows.
  • Build: zig build -Doptimize=ReleaseFast; VapourSynth R77.
  • Filter-bound source: a flat 1080p BlankClip (decode ≈ 0; none of these filters early-out on flat content, so the fps is the filter's own throughput).
  • Timing: each *.vpy sets one output node and is rendered with vspipe file.vpy . (output discarded) over 2000 frames; the fps is vspipe's own. core.num_threads = 8, so vspipe pulls frames concurrently — that is what lets num_streams (GPU) and CPU threads scale.
  • num_streams: shown at 1 and 2 where the filter/reference exposes the knob. References without it (placebo, knlm, CPU vszip) show a single row.
  • means the reference does not accept that sample type (see per-filter notes).
  • Matched work: vszipcl was ported to mirror each reference, so both run equivalent params (listed per filter). vszipcl's default num_streams is 1.

One machine's numbers; ratios matter more than absolutes, and a different GPU/driver will shift them. GPU boost clocks drift ~±5% run to run.


Bilateral — vs bilateralgpu.Bilateral (CUDA)

sigma_spatial=3, sigma_color=0.02

Implementation u16 f16 f32
vszipcl (s=1) 312.8 296.0 205.6
vszipcl (s=2) 391.1 391.3 289.9
bilateralgpu (s=1) 99.3 199.9
bilateralgpu (s=2) 181.2 244.6

vszipcl is ~2.2× bilateralgpu on u16 (it moves 1 byte/sample and converts on-device, while bilateralgpu converts on the CPU and moves f32 over PCIe) and ~1.2× on f32. bilateralgpu rejects half-float, so f16 is vszipcl-only. This is also the filter where num_streams=2 helps most (+25–41% for vszipcl, up to +82% for bilateralgpu on u16).

Deband — vs placebo.Deband (libplacebo)

iterations=1, threshold=4, radius=16, grain=6, planes=1

Implementation u16 f16 f32
vszipcl (s=1) 882.8 889.3 458.3
vszipcl (s=2) 877.2 887.3 458.7
placebo 569.5 585.9 337.2

vszipcl is ~1.35–1.55× placebo across formats. The single deband kernel already saturates the GPU, so num_streams=2 is neutral (placebo has no streams knob).

GaussBlur — no installed reference

Three sigmas (no comparable GPU Gaussian is installed — this compares the sigma / code paths): sigma=1 is the fused small-radius smem kernel; sigma=100/250 take the large separable path.

Implementation u16 f16 f32
σ=1 (s=1) 522.2 544.2 320.9
σ=1 (s=2) 501.8 503.6 326.7
σ=100 (s=1) 266.1 264.1 203.8
σ=100 (s=2) 268.2 273.4 213.5
σ=250 (s=1) 148.1 153.0 128.7
σ=250 (s=2) 153.4 156.3 133.8

Throughput falls with radius (larger kernel = more work per pixel). num_streams=2 is neutral for the saturated small-σ kernel and gives only a few percent on the large separable path.

EEDI3 / EEDI3H — vs CPU vszip.EEDI3 / vszip.EEDI3H

field=1, mdis=40, nrad=3

EEDI3 (vertical)

Implementation u16 f16 f32
vszipcl (s=1) 76.1 76.3 69.9
vszipcl (s=2) 78.3 77.9 73.6
vszip (CPU) 54.2

EEDI3H (horizontal)

Implementation u16 f16 f32
vszipcl (s=1) 76.2 76.6 70.4
vszipcl (s=2) 79.5 78.7 75.3
vszip (CPU) 56.1

On the only head-to-head format (f32) vszipcl is ~1.35× the CPU EEDI3 — and it also accepts u16/f16, which the CPU reference does not (it is 32-bit-float only). The barrier-bound DP means a second stream adds little (~+3–5%).

NLMeans — vs nlm_cuda.NLMeans (CUDA) and knlm.KNLMeansCL (OpenCL)

d=1, a=2, s=4, h=1.2

Implementation u16 f16 f32
vszipcl (s=1) 151.0 145.8 117.8
vszipcl (s=2) 155.3 154.7 121.2
nlm_cuda (s=1) 83.2 64.3
nlm_cuda (s=2) 88.3 73.3
knlm 75.8 75.5 62.4

vszipcl is ~1.65–1.76× nlm_cuda and ~1.95–2.05× knlm on the shared formats. nlm_cuda rejects half-float (f16 is vszipcl-vs-knlm only); knlm has no streams knob.


Takeaways

  • When num_streams>1 helps: occupancy/transfer-bound filters — Bilateral most of all (+25–41% for vszipcl, up to +82% for bilateralgpu on u16), then NLMeans and large-σ GaussBlur (a few percent). Deband and small-σ GaussBlur are neutral (a single kernel already saturates the GPU), and EEDI3's barrier-bound DP scales little (~+3–5%). That's why the default is 1 — raise it where it pays.
  • Narrow io wins: u16/f16 beat f32 for the transfer-bound filters because vszipcl moves the wire format end-to-end and converts on-device (half the PCIe/VRAM traffic).
  • vszipcl leads every head-to-head comparison here; the remaining gap to native CUDA (Bilateral f32) is inherent — NVIDIA's OpenCL compiler vs nvcc plus CUDA's async smem loads.

Clone this wiki locally