Skip to content

perf(encoders): set web-tuned effort default (heif=1, webp=2)#113

Merged
olivervogel merged 1 commit into
Intervention:developfrom
nlemoine:perf/heif-webp-effort-default
May 24, 2026
Merged

perf(encoders): set web-tuned effort default (heif=1, webp=2)#113
olivervogel merged 1 commit into
Intervention:developfrom
nlemoine:perf/heif-webp-effort-default

Conversation

@nlemoine
Copy link
Copy Markdown
Contributor

The Vips Avif, Heic and Webp encoders set lossless, Q and keep, but they never pass libvips' effort. So they inherit libvips' default of effort=4 (heifsave range 0..9, webpsave range 0..6), which is slower than needed for web encodes.

This sets effort=1 for heif (avif/heic) and effort=2 for webp.

I found this on a demo that negotiates AVIF from the browser Accept header. The Vips driver was slower than GD on the same encode, and it turned out to be the effort default.

Repro on libvips 8.18.2, decode + encode, best of 3, Q=75 for AVIF, Q=80 for WebP. Cells are ms / output bytes, percent is bytes vs the default.

AVIF (heifsave):

source default (=4) effort=1 effort=2
960x1280 JPEG 146 ms / 54540 21 ms / 35690 (-35%) 31 ms / 35042 (-36%)
1920x1280 JPEG 412 ms / 233269 60 ms / 241511 (+4%) 153 ms / 240761 (+3%)
4000x2667 JPEG 1300 ms / 394542 147 ms / 404564 (+3%) 445 ms / 405572 (+3%)
2000x2000 RGBA PNG 537 ms / 246386 114 ms / 264800 (+7%) 214 ms / 252485 (+2%)

WebP (webpsave):

source default (=4) effort=1 effort=2
960x1280 JPEG 49 ms / 35442 16 ms / 40044 (+13%) 21 ms / 36500 (+3%)
1920x1280 JPEG 138 ms / 225012 55 ms / 259642 (+15%) 71 ms / 234594 (+4%)
4000x2667 JPEG 462 ms / 399278 165 ms / 474912 (+19%) 215 ms / 420262 (+5%)
2000x2000 RGBA PNG 223 ms / 262288 137 ms / 295696 (+13%) 149 ms / 270972 (+3%)

Reasoning for the values:

  • default and effort=4 are byte-identical in every row, so 4 is the live default.
  • For heifsave, effort=1 is about 2-3x faster than effort=2 with basically the same bytes on photos. effort=2 only helps on the RGBA source (about 5% smaller, still about 2x slower), so 1 is the better default.
  • For webpsave it is the other way, effort=1 costs +13-19% bytes, so 2 is the sweet spot.
  • effort=0 is faster still but not safe as a default, the RGBA source blew up (+63% AVIF, +122% WebP).

No version gate: both CI libvips versions (8.17.2 and 8.18.1) have effort on both ops.

On tests: effort leaves no readable trace in the output (unlike progressive on JpegEncoder), so there is nothing stable to assert on the encoded bytes across libvips builds. The existing encoder tests now exercise the effort path on both CI versions. This matches how optimize_coding=true is already set on JpegEncoder without a dedicated test.

libvips' heifsave and webpsave default to effort=4. The Avif, Heic and
Webp specialized encoders never set effort, so they inherited that
default, which spends a lot of encoder CPU for little size gain on web
sources.

Set effort=1 for heif (avif/heic) and effort=2 for webp. On libvips
8.18.2 a 1920x1280 AVIF encode drops from ~410ms to ~60ms with
near-identical bytes. WebP roughly halves its time within ~3% bytes;
effort=1 was not used for webp because it costs +13-19% bytes.

Both CI libvips versions (8.17.2 and 8.18.1) have effort on both ops, so
no version gate is needed.
@nlemoine
Copy link
Copy Markdown
Contributor Author

Follow up idea, separate from this PR if you prefer. This hardcodes the default in the driver, so users can't override it. The other option is to add effort as a public param on the generic Avif/Heic/Webp encoders in intervention/image (the way progressive lives on JpegEncoder), default null, and have the driver fall back to 1/2 when it is null. That needs a small change in intervention/image too, but it gives users a real knob and GD/Imagick could map it later.

Happy to do that as a follow-up (the values here would just become the fallback defaults), or fold it in now if you would rather have the public option from the start. Let me know which you prefer.

@nlemoine
Copy link
Copy Markdown
Contributor Author

Just so you have all cards in hands, this is how other libvips-based image services default the AVIF/WebP effort knob, for reference.

service backend AVIF default = libvips effort WebP effort quality (avif/webp)
imgproxy Go + libvips speed 8 effort 1 4 63 / 79
imagor Go + libvips (govips) speed 5 effort 4 4 (no knob exposed) via quality filter
weserv C++ + libvips effort 4 effort 4 4 80 / 80
intervention-vips (today) PHP + libvips unset effort 4 4 encoder Q
intervention-vips (this PR) PHP + libvips effort 1 effort 1 2 encoder Q

imgproxy already defaults AVIF to effort 1, the same as this PR. imagor and weserv both leave AVIF at the libvips default of 4. For WebP, all three keep effort 4.

AVIF effort=1 is pretty obvious default. WEBP might be worth deciding the right default balance between speed/compression (x2 speed for a roughly +4% on size at 2).

@deluxetom
Copy link
Copy Markdown
Collaborator

Looks good to me, thank you!

@olivervogel
Copy link
Copy Markdown
Member

Thanks. This is a good optimization.

@olivervogel olivervogel merged commit be0be40 into Intervention:develop May 24, 2026
6 checks passed
@olivervogel
Copy link
Copy Markdown
Member

Follow up idea, separate from this PR if you prefer. This hardcodes the default in the driver, so users can't override it. The other option is to add effort as a public param on the generic Avif/Heic/Webp encoders in intervention/image (the way progressive lives on JpegEncoder), default null, and have the driver fall back to 1/2 when it is null. That needs a small change in intervention/image too, but it gives users a real knob and GD/Imagick could map it later.

To be honest, I don't think much of the idea of integrating one single driver-specific parameters into Intervention Image's public API. The main goal is to provide a unified API and interchangable driver architecture, adding these options wouldn't be aligned with the product direction.

However, I think it’s worth considering expanding the encoder options in general while maintaining consistency across drivers. However, we would first need to discuss to what extent this would even be possible (GD has very limited capabilities in this regard) and how to standardize it in a user-friendly way. This topic is beyond the scope of this discussion. I've opened a new issue for it.

@nlemoine
Copy link
Copy Markdown
Contributor Author

Providing user control over drive options seems like a nice addition 👍 But yes, probably another scope.

Thanks for your feedback and merging this!

@nlemoine nlemoine deleted the perf/heif-webp-effort-default branch May 26, 2026 07:08
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.

3 participants