Skip to content

feat(bin): enable the lib's rayon feature#33

Open
Gheop wants to merge 3 commits into
rust-av:mainfrom
Gheop:enable-rayon-in-bin
Open

feat(bin): enable the lib's rayon feature#33
Gheop wants to merge 3 commits into
rust-av:mainfrom
Gheop:enable-rayon-in-bin

Conversation

@Gheop

@Gheop Gheop commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

The binary depends on the lib with default-features = false, which silently disables the lib's rayon feature (on by default, used for the parallel horizontal blur pass) for every install of the CLI, however it is built. cargo install ssimulacra2_rs therefore always produces a fully single-threaded scorer.

This PR turns the feature back on for the binary: default-features = false, features = ["rayon"].

Measured with hyperfine (10 runs, Core Ultra 7 155H, Linux) on a 1448×1080 pair with ssimulacra2_rs image:

mean ± σ
before 1.231 s ± 0.014
after 1.013 s ± 0.044

No score or output change (the parallel rows keep their per-row summation order).

There is more parallelism available in the rest of the pipeline (vertical pass, planes, downscale, maps); happy to follow up with that work if there's interest — this one-liner is the zero-risk part.

@FreezyLemon

Copy link
Copy Markdown
Contributor

Hi, thank you for the PR!

The binary depends on the lib with default-features = false, which silently disables the lib's rayon feature [...]

The rayon feature was disabled on purpose in rust-av/ssimulacra2_bin#35. The reason for this was explained in rust-av/ssimulacra2_bin#21, and I am pretty sure the same reasoning still holds true.

cargo install ssimulacra2_rs therefore always produces a fully single-threaded scorer.

This is not true, we have a different approach to threading/parallelization in the source for the binary. Have you tested the -f/--frame_threads optoin of ssimulacra2_rs?

@Gheop

Gheop commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the pointers — I read #21 and #35, and I agree with the reasoning for video mode: frame-level threading composes better than intra-frame rayon, and stacking both oversubscribes. My "always single-threaded" wording was too broad.

The case I care about is image mode, where --frame_threads doesn't exist: a single pair comparison runs on one core. That's the path quality-target search scripts hit in a loop. On a 1448×1080 pair, enabling the lib's rayon feature takes ssimulacra2_rs image from 1.231 s ± 0.014 to 1.013 s ± 0.044 (hyperfine, 10 runs).

Would you take a version scoped so video behavior is strictly preserved? Concretely: keep the feature enabled for the binary, and in video mode run each frame worker's scoring inside a 1-thread rayon::ThreadPool (install), so per-frame scoring stays sequential and CPU usage stays predictable, while image mode uses the default pool. Happy to update this PR accordingly, or to close it if you'd rather keep the CLI as is.

The binary depends on the ssimulacra2 lib with default-features = false,
which turns the lib's rayon feature (on by default, parallel horizontal
blur pass) off for every installation of the CLI, however it is built.

Enabling it is measurable on still-image scoring: on a 1448x1080 pair,
hyperfine reports 1.231 s -> 1.013 s (Core Ultra 7 155H). No output or
score change.
@Gheop Gheop force-pushed the enable-rayon-in-bin branch from 3542d94 to 0fe1c90 Compare July 3, 2026 20:31
@FreezyLemon

Copy link
Copy Markdown
Contributor

Hmm right, sorry I was focused mainly on the video feature/subcommand. I don't think our current implementation of the underlying ssimulacra2 crate can easily include both the rayon and non-rayon code paths... we should probably find a proper solution for this. Just enabling rayon is going to regress video performance, but parallelizing image mode would be great.

Frame-level threading already saturates the CPU in video mode; the lib's
rayon feature must not multiply it (see ssimulacra2_bin#21). Each frame
worker installs its scoring loop in a local 1-thread rayon pool, so
per-frame scoring stays sequential and CPU usage matches the previous
non-rayon build, while image mode uses the default parallel pool.
@Gheop

Gheop commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

I've pushed the scoped version. rayon is enabled for the binary, and each video frame worker now runs its scoring loop inside a single-threaded local rayon pool (pool.install), so the lib's rayon path stays sequential per frame; image mode uses the default pool.

To check the video-regression concern, I benchmarked a harness mirroring the video worker structure (N threads, each calling compute_frame_ssimulacra2 in a loop; 1448×1080 pair, 8 workers × 4 scores, 3 runs):

config throughput total CPU
non-rayon lib (current behavior) 3.0–3.4 fps 64–68 s
rayon lib + 1-thread worker pools (this PR) 2.9–3.3 fps 64–75 s
rayon lib, shared global pool (naive enable) 3.1 fps 76 s (+12–22 % CPU)

The naive config reproduces the #21 findings; the pooled config is within run-to-run noise of current behavior. Image mode still goes 1.231 s ± 0.014 → 1.013 s ± 0.044.

@FreezyLemon

Copy link
Copy Markdown
Contributor

This looks like a good compromise. We might want to extend the parallelization for image mode in the future, e.g. by supplying a CLI parameter similar to video mode's -f, but that's not required for this PR.

Comment thread ssimulacra2_bin/Cargo.toml Outdated
@FreezyLemon

Copy link
Copy Markdown
Contributor

Hm.. on my laptop CPU, the performance improvements are so small as to be negligible (<1%) for a 1920x1080 image pair. I will bench this again on my desktop system and report back.

By the way:

That's the path quality-target search scripts hit in a loop.

If this is the usecase, have you tried running the entire program multiple times in parallel? This rayon approach is pretty inefficient by comparison. If you can do multiple images at the same time, it should be easily 2x as fast, which is why we do it in video mode.

@Gheop

Gheop commented Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

That would be consistent with my numbers: upstream's rayon feature only parallelizes the horizontal blur pass, so the gain scales roughly with core count (I measured 18% on a 22-thread machine; on a 4-8 core laptop it shrinks toward noise, and PNG decode time dilutes it further). Your desktop bench should tell.

On running multiple instances instead: that works for throughput, but the use case is a quality-target search (bisection over encoder quality), where iteration N+1 depends on the score of iteration N — it's inherently sequential, so what matters is the latency of one comparison, not aggregate fps. Multiple processes don't help there.

No hard feelings either way — if the numbers don't justify it on typical hardware, feel free to close.

@FreezyLemon

Copy link
Copy Markdown
Contributor

where iteration N+1 depends on the score of iteration N — it's inherently sequential

I understand this, although I would've expected a target quality search to produce more than one image for N before stepping to N+1, at least in a video encoding context. Either way, it's understandable to want a faster one-shot image comparison.

On my 32-thread desktop machine, I gain a reproducible advantage of roughly 11%. That's a good increase. I tend towards merging it, but I'd like to hear from @shssoichiro first since it's a change with tradeoffs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants