Optimize and expose WebP encoding - #2
Conversation
Merging this PR will improve performance by 11.17%
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ⚡ | webp_single_frame_lossless |
1.8 s | 1.6 s | +11.17% |
| 🆕 | webp_batch_serialize_fast |
N/A | 156.8 ms | N/A |
| 🆕 | webp_serialize_fast |
N/A | 350 ms | N/A |
Tip
Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.
Comparing xirreal:perf/expose-fast-webp-encoder (fbccc8b) with trunk (5d9a6df)
|
@codspeedbot explain why is this faster? |
Why
|
BASE (trunk) |
HEAD |
|
|---|---|---|
| Native dep | webp-animation 0.10 → libwebp-sys2 0.2.0 |
libwebp-sys =0.14.4 |
| Lossless entropy kernel | CombinedShannonEntropy_SSE2 (dsp/lossless_enc_sse2.c) |
CombinedShannonEntropy_AVX2 (dsp/lossless_enc_avx2.c) |
| LZ77 match scan | VectorMismatch_SSE2 |
VectorMismatch_AVX2 |
| Total | 1.8 s | 1.6 s |
1. AVX2 lossless-encoder kernels (the main win)
The bundled libwebp in libwebp-sys 0.14.4 ships src/dsp/lossless_enc_avx2.c, which the older vendored copy behind webp-animation 0.10 didn't have at all — so on the same hardware class the base run could only dispatch to SSE2. Both callers of the entropy kernel got cheaper:
CombinedShannonEntropy: 401.6 ms (SSE2) → 361.1 ms (AVX2)- inside it, the pack step alone:
_mm_packs_epi3285.1 ms →_mm256_packs_epi3262.8 ms (2× wider vectors, half the ops)
Rolled up to the three top-level lossless stages:
| Stage | BASE |
HEAD |
Δ |
|---|---|---|---|
VP8LGetHistoImageSymbols |
682.9 ms | 608.6 ms | −74.3 ms |
VP8LResidualImage |
586.0 ms | 513.3 ms | −72.7 ms |
VP8LColorSpaceTransform |
247.0 ms | 225.7 ms | −21.3 ms |
That's ~168 ms of the ~200 ms total delta.
2. Fewer entropy evaluations in the newer histogram code
GetEntropyUnrefined_C goes from 6 calls / 73.7 ms to 2 calls / 32.2 ms — the extra PopulationCost invocations present in the old version are gone. GetCombinedEntropyUnrefined_C also drops 395 ms → 372.7 ms. This is an upstream algorithmic change in the newer libwebp, on top of the SIMD win.
3. What did not move the needle here
Dropping the webp-animation wrapper removes one full-frame memcpy on the way into the encoder (visible in the base flamegraph as encode_single → __memcpy_avx_unaligned_erms), but the Rust layer is only ~0.2–0.3% of this benchmark — 99.6%+ of the time is inside VP8LEncodeStream either way. The wrapper removal matters for API shape and for the new batch path, not for this number.
Consistent with that: webp_single_frame (lossy) is unchanged, because the lossy VP8 encoder doesn't go through the lossless AVX2 kernels at all.
One caveat on the number
CodSpeed flagged an environment difference on this comparison: base ran on an EPYC 9V74, head on an EPYC 7763. In Simulation mode the cache model is derived from the physical CPU, so a few percent of the 11.17% may be runner-attributable. The direction and mechanism are real though — the SSE2→AVX2 dispatch and the reduced call counts are structural properties of the two libwebp versions, visible in the profiles regardless of runner.
The 11.3× figure in the PR description is a separate story: that's the new batch API (webp_batch_serialize_fast, 156.8 ms vs webp_serialize_fast, 350 ms in these runs), driven by dirty rectangles and parallel frame encoding rather than by the backend swap.
No code changes were needed for this question, so nothing has been pushed.
Summary
- Warming up the performance wizard...
- Investigating the WebP speedup — Root-caused the 11.17% improvement to the libwebp backend swap (AVX2 lossless kernels + cheaper histogram cost path)
- Handling comment — Comment handling finished successfully
Was this response helpful? React with 👍 or 👎 to help us improve CodSpeedBot.
|
@codspeedbot can you create a new PR? Just asking. |
Not from here, no — when I'm triggered from a PR comment I work on that PR's branch ( What I can do on this PR:
If you want a change landed on a different branch, the usual path is to open the PR yourself and mention me there. Summary
Was this response helpful? React with 👍 or 👎 to help us improve CodSpeedBot. |
Summary
libwebp-sysencoder and expose bounded-memory streaming plus high-throughput batch APIs frommaple-render-coremaple-render-core0.3.0 and re-export it from Maple on native targetsPerformance
On the included generated 640x360x16 workload (lossy quality 85, method 0), the optimized build measured:
The batch API intentionally trades retained input frames and independent rectangle compression for throughput. The streaming API remains the bounded-memory default.
Alternative evaluated
zenwebp0.4.4 has promising pure-Rust, SIMD, mux, animation, andno_stdsupport, but it is not suitable here today:Verification
cargo fmt --all -- --checkcargo clippy --locked --all-targets -- -D warningswasm32-unknown-unknownlibrary checkmaple-render-corepackage verification