Skip to content

perf: add ARM64/WASM scalar fast path for Premultiply/UnPremultiply#1

Closed
kodroi wants to merge 1 commit into
mainfrom
perf/fuse-premultiply-simd
Closed

perf: add ARM64/WASM scalar fast path for Premultiply/UnPremultiply#1
kodroi wants to merge 1 commit into
mainfrom
perf/fuse-premultiply-simd

Conversation

@kodroi

@kodroi kodroi commented Apr 1, 2026

Copy link
Copy Markdown
Owner

Prerequisites

  • I have written a descriptive pull-request title
  • I have verified that there are no overlapping pull-requests open
  • I have verified that I am following the existing coding patterns and practice as demonstrated in the repository. These follow strict Stylecop rules 👮
  • I have provided test coverage for my change (where applicable)

Description

On x86, Premultiply and UnPremultiply use PermuteW() + WithW() helpers which generate optimal vpermilps + vinsertps with a single memory load/store (14-23 cycles, as recommended by @tannergooding in PR SixLabors#2369).

On ARM64/WASM where SSE is unavailable, these helpers fall through to scalar fallbacks but still incur method call overhead across a 3-level call chain (Premultiply → PermuteW/WithW → scalar). This PR adds a direct scalar path behind if (Sse.IsSupported) that avoids the helper call chain on non-x86 platforms while keeping the existing x86 behavior unchanged.

Profiled with metreja.io on a 2048x2048 RGBA32 image (50x Resize + 20x GaussianBlur), .NET 10.0.2 ARM64:

Method Before After Change
Premultiply (inclusive) 11,614s 611s -95%
PermuteW 1,029s 0s -100% (not called on ARM)
WithW 1,077s 0s -100% (not called on ARM)
UnPremultiply (inclusive) 947s 39s -96%
VerticalConvolution.Convolve4 (inclusive) 6,559s 417s -94%

Times are cumulative profiler-instrumented across the entire workload. No change on x86 (same code path as before).

All 38,330 existing tests pass. DLL size unchanged.

@kodroi
kodroi force-pushed the perf/fuse-premultiply-simd branch 2 times, most recently from 2454336 to a0f51b7 Compare April 1, 2026 07:45
On x86, keep the existing PermuteW+WithW path which generates optimal
vpermilps+vinsertps with a single memory load/store (14-23 cycles, as
recommended by @tannergooding in PR SixLabors#2369).

On ARM64/WASM where SSE is unavailable, PermuteW and WithW fall through
to scalar code but still incur method call overhead. Add a direct scalar
path that avoids the helper call chain while producing identical results.

Profiled with metreja.io on a 2048x2048 RGBA32 image (50x Resize +
20x GaussianBlur), .NET 10.0.2 ARM64:

  Premultiply (inclusive):    11,614s ->  611s  (-95%)
  PermuteW:                   1,029s ->    0s  (-100%, not called)
  WithW:                      1,077s ->    0s  (-100%, not called)
  UnPremultiply (inclusive):    947s ->   39s  (-96%)
  Convolve4 (inclusive):      6,559s ->  417s  (-94%)

No change on x86 (same code path as before).

All 38,330 tests pass (net8.0, macOS ARM64).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@kodroi
kodroi force-pushed the perf/fuse-premultiply-simd branch from a0f51b7 to a0b612d Compare April 1, 2026 08:46
@kodroi kodroi changed the title perf: fuse PermuteW+WithW into Premultiply/UnPremultiply perf: add ARM64/WASM scalar fast path for Premultiply/UnPremultiply Apr 1, 2026
@kodroi

kodroi commented Apr 1, 2026

Copy link
Copy Markdown
Owner Author

Closing — with JIT inlining enabled (realistic profiling), PermuteW and WithW are fully inlined and the ARM64 scalar fast path shows ~0% measurable difference. The original profiling numbers were inflated by metreja's COR_PRF_DISABLE_INLINING flag, which has since been fixed in metreja v1.0.20.

@kodroi kodroi closed this Apr 1, 2026
@kodroi
kodroi deleted the perf/fuse-premultiply-simd branch April 1, 2026 11:34
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.

1 participant