Skip to content

gamut-png: parallelise the BruteForce filter trials #624

Description

@justin13888

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

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions