Skip to content

Speed up 8-bit horizontal resampling loops - #10293

Merged
radarhere merged 2 commits into
python-pillow:mainfrom
simonhf:resample-8bpc-pointer
Oct 23, 2026
Merged

radarhere merged 2 commits into
python-pillow:mainfrom
simonhf:resample-8bpc-pointer

Conversation

@simonhf

@simonhf simonhf commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

Changes proposed in this pull request:

  • In _ImagingResampleHorizontal_8bpc(), the 2, 3 and 4 band loops read lineIn[(x + xmin) * 4 + c]. They now step a pointer through the input pixels instead, which compilers vectorise better when the filter is long (strong downscaling, or BICUBIC and LANCZOS).
  • For 3 bands with short filters (fewer than 12 taps), the existing indexed loop is kept: with gcc on aarch64 the pointer loop was up to 7% slower than main for those, while on the x86 machines below it was at most about 5% faster than the indexed loop there.
  • No change in output. I compared hashes of 1,100 resizes (11 modes, all 5 filters, several sizes and targets, with and without box) before and after, on all four machines below; they are byte-identical.

Timings use the image from Tests/benchmarks.py (make_pillow_image(), 1237x811), modes RGB, RGBA, LA and L, the 5 filters and scales 0.01, 0.125, 0.5, 0.8 and 2.14 (the test_scale scales plus 0.5), so 25 cases per mode. Each cell is the range of the change in wall time against main over those 25 cases, negative is faster, with how many of the 25 got faster:

RGB RGBA LA L
Apple M3 Max, Apple clang 21 -68% to -2% (25/25) -3% to +4% (13/25) -80% to +1% (24/25) -8% to +1% (15/25)
AWS Graviton4 (c8g), gcc 13.3 -61% to +3% (24/25) -56% to -1% (25/25) -55% to +1% (22/25) -7% to +3% (12/25)
AMD Ryzen 9 3950X, gcc 11.4 -26% to +1% (24/25) -19% to -1% (25/25) -20% to -1% (25/25) -4% to +0.4% (14/25)
Intel Xeon E3-1505M v6, gcc 13.3 -16% to +2% (24/25) -10% to +2% (23/25) -12% to +2% (24/25) 0% to +5% (0/25)

The largest gains are for strong downscaling, for example RGB LANCZOS 1237x811 to 12x8 is 59% faster on Graviton4 and 63% faster on the M3.

A note on method, because the effect sizes are small in places: on the M3, the Graviton4 and the Xeon, where a function lands in memory moved some of these cases by 5-14% on its own, so a single build per side was not reliable. On Linux, each side was built at 5 or 6 code placements (padding inserted before the code), and the table shows the mean over placements of the best of 2 runs; on the M3 it is one build per side, best of 3 runs. L is a useful check: its code is not changed by this PR, and its spread above is that layout effect, not the patch. On the Xeon (a Skylake-family core) it comes from the JCC erratum (jumps crossing 32-byte boundaries); building both sides with -Wa,-mbranches-within-32B-boundaries brings L to within 1% there.

CodSpeed measures this in simulation mode on x86 only, so it will show the change in instruction count, not these wall times.

The full test suite passes (5,659 passed; test_imagegrab.py::TestImageGrab::test_grab fails on my machine with or without this change, as screencapture is not permitted there).

I used an AI coding assistant to find and write this change; I reviewed and tested it, and the measurements are from my own runs.

Step a pointer through the input pixels in the 2, 3 and 4 band loops
of _ImagingResampleHorizontal_8bpc(), which compilers vectorise better
for long filters. For 3 bands with short filters, keep the indexed
loop, which is faster with gcc on aarch64.

The output is unchanged.

Assisted-by: LLM
Signed-off-by: Simon Hardy-Francis <simon@linux.com>
@codspeed

codspeed Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Merging this PR will improve performance by 47.58%

1 warning

⚠️ Base and head ran on different runner types

Some benchmarks with significant performance changes were compared across different runner types, so the changes might not come from the code. Run base and head on the same runner type, or use CodSpeed Macro Runners for more stable results.

Learn more about Macro Runners

Open the report in CodSpeed to investigate

⚡ 37 improved benchmarks
✅ 603 untouched benchmarks
⏩ 338 skipped benchmarks1

Performance Changes

Benchmark BASE HEAD Efficiency
⚡ test_scale[1237x811-RGB-0.01-MKS2013] 49.7 ms 23.4 ms ×2.1
⚡ test_scale[1237x811-RGB-0.01-MKS2021] 82.9 ms 39.9 ms ×2.1
⚡ test_scale[1237x811-RGB-0.01-BICUBIC] 41 ms 19.9 ms ×2.1
⚡ test_scale[1237x811-RGB-0.01-LANCZOS] 60.6 ms 30.1 ms ×2
⚡ test_scale[1237x811-RGB-0.01-BILINEAR] 23.3 ms 12.7 ms +82.58%
⚡ test_scale[1237x811-RGB-0.01-HAMMING] 23.7 ms 13.2 ms +79.56%
⚡ test_scale[1237x811-RGBA-0.01-MKS2021] 128.6 ms 76.6 ms +67.93%
⚡ test_scale[1237x811-LA-0.01-MKS2021] 84.5 ms 50.6 ms +66.94%
⚡ test_scale[1237x811-RGB-0.125-MKS2021] 115.7 ms 70.6 ms +63.89%
⚡ test_scale[1237x811-RGB-0.01-BOX] 14 ms 8.6 ms +62.81%
⚡ test_scale[1237x811-RGBA-0.01-LANCZOS] 101.1 ms 64.3 ms +57.28%
⚡ test_scale[1237x811-LA-0.01-LANCZOS] 67.2 ms 43.2 ms +55.73%
⚡ test_scale[1237x811-RGBA-0.01-MKS2013] 88.4 ms 56.8 ms +55.59%
⚡ test_scale[1237x811-LA-0.01-MKS2013] 58.2 ms 37.6 ms +54.57%
⚡ test_scale[1237x811-RGB-0.125-MKS2013] 65.6 ms 43.7 ms +49.94%
⚡ test_scale[1237x811-LA-0.125-MKS2021] 110.9 ms 74.6 ms +48.68%
⚡ test_scale[1237x811-RGBA-0.01-BICUBIC] 77.6 ms 52.2 ms +48.67%
⚡ test_scale[1237x811-RGBA-0.125-MKS2021] 169.9 ms 115.2 ms +47.54%
⚡ test_scale[1237x811-LA-0.01-BICUBIC] 51.6 ms 35 ms +47.48%
⚡ test_scale[1237x811-LA-0.125-LANCZOS] 84.3 ms 58.5 ms +44.2%
... ... ... ... ...

ℹ️ Only the first 20 benchmarks are displayed. Go to the app to view all benchmarks.

Tip

Curious why performance improved? Comment @codspeedbot explain why performance improved on this PR, or directly use the CodSpeed MCP with your agent.


Comparing simonhf:resample-8bpc-pointer (058bdba) with main (81f3f5f)

Open in CodSpeed

Footnotes

  1. 338 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

Comment thread src/libImaging/Resample.c Outdated
@radarhere
radarhere force-pushed the resample-8bpc-pointer branch from dec923d to 058bdba Compare October 23, 2026 08:44
@radarhere
radarhere merged commit f6122d3 into python-pillow:main Oct 23, 2026
59 of 60 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants