Item 1 of #484, declined there rather than attempted: #484's other half — the composed effort
dial — is delivered, and this is what it deliberately left behind.
What
PngEncoder::compress_scanlines runs FilterStrategy::BruteForce's seven whole-image
candidates sequentially — seven full filter passes and seven full DEFLATE streams, one after
another — and there is no threading anywhere in gamut-png or gamut-deflate. They are
embarrassingly parallel.
Why it now matters more than it did
The effort ladder makes the cost visible and reachable. Measured over the nine-row efficiency
corpus at 64x64 (cargo test -p gamut-png --test effort, test profile, one machine — read the
ratios, not the absolute times):
| rung |
ms per corpus pass |
relative |
bytes |
vs Balanced |
Fast |
1.372 |
0.36x |
17 530 |
+3.4% |
Balanced |
3.819 |
1x |
16 952 |
— |
Small |
574.058 |
150x |
16 497 |
−2.7% |
Smallest |
3697.040 |
968x |
15 903 |
−6.2% |
Smallest is the rung that runs the brute-force search, and it is 6.4x slower than Small,
which differs from it by the filter search and the effort budget. That factor is the ceiling
this issue is about, and gamut convert --png-preset smallest now hands it to a user directly.
The decision it needs
This is why it was declined rather than done: it is a workspace-level dependency decision, not
a local change.
gamut-deflate advertises zero runtime dependencies, and this repository's rule is no new
external dependency without a recorded decision.
rayon on wasm32 builds, but degrades to serial only for the implicit global pool
(build_global() errors), so it would have to be feature-gated off by default.
- The closest precedent, image-rs's
png 0.18.1, has no rayon at all. oxipng does parallelise
exactly here (filters.par_iter().with_max_len(1), not across deflate attempts) and ships
src/rayon.rs, a serial shim implementing join/spawn/par_iter with identical signatures
for when the feature is off. That shim is the cleanest pattern to copy.
What must survive
Determinism. Output is byte-reproducible today and STATUS.md axis 8 says so. The reduction must
stay min_by_key over an ordered collection so ties still resolve to the earlier (more preferred)
strategy — which brute_force_keeps_the_first_strategy_on_a_tie and
encoded_size_is_deterministic already pin, and which a parallel version must not weaken.
Refs #484, #224
Item 1 of #484, declined there rather than attempted: #484's other half — the composed effort
dial — is delivered, and this is what it deliberately left behind.
What
PngEncoder::compress_scanlinesrunsFilterStrategy::BruteForce's seven whole-imagecandidates sequentially — seven full filter passes and seven full DEFLATE streams, one after
another — and there is no threading anywhere in
gamut-pngorgamut-deflate. They areembarrassingly parallel.
Why it now matters more than it did
The effort ladder makes the cost visible and reachable. Measured over the nine-row efficiency
corpus at 64x64 (
cargo test -p gamut-png --test effort, test profile, one machine — read theratios, not the absolute times):
BalancedFastBalancedSmallSmallestSmallestis the rung that runs the brute-force search, and it is 6.4x slower thanSmall,which differs from it by the filter search and the effort budget. That factor is the ceiling
this issue is about, and
gamut convert --png-preset smallestnow hands it to a user directly.The decision it needs
This is why it was declined rather than done: it is a workspace-level dependency decision, not
a local change.
gamut-deflateadvertises zero runtime dependencies, and this repository's rule is no newexternal dependency without a recorded decision.
rayonon wasm32 builds, but degrades to serial only for the implicit global pool(
build_global()errors), so it would have to be feature-gated off by default.png0.18.1, has no rayon at all. oxipng does paralleliseexactly here (
filters.par_iter().with_max_len(1), not across deflate attempts) and shipssrc/rayon.rs, a serial shim implementingjoin/spawn/par_iterwith identical signaturesfor when the feature is off. That shim is the cleanest pattern to copy.
What must survive
Determinism. Output is byte-reproducible today and
STATUS.mdaxis 8 says so. The reduction muststay
min_by_keyover an ordered collection so ties still resolve to the earlier (more preferred)strategy — which
brute_force_keeps_the_first_strategy_on_a_tieandencoded_size_is_deterministicalready pin, and which a parallel version must not weaken.Refs #484, #224