Repository navigation
Vector{128,256} operations that use MmShuffle fall back to method call #2121
Description
Activity
@gfoidl This has been on my mind for a long while and I was actually thinking about it the other day.
Ideally I'd like to be able to keep using MMShuffle in some manner as it helps visual the operation and helps porting.
I was thinking maybe we could generate a bunch of constants for that using either T4 or source generators. Maybe even a full collection like the following.Another option is to use binary literals.
0b10_11_00_01is a bit more readable and0xB1but less thanMmShuffle(2, 3, 0, 1)Defining constants like
public const int MmShuffle_2301 = 0b10_11_00_01would also be viable and allow the optimization.-- Getting the JIT to handle this properly is still on my radar, but its not "simple" and probably won't make .NET 7 unfortunately.
Reacted by James Jackson-South and Günther FoidlThe approach with the constant looks good to me. It's self describing and the optimization kicks in.
Reacted by James Jackson-South- added a commit that references this issue
on May 18, 2022 How could things like
be handled in order for the JIT to don't emit the software fallback?ImageSharp/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs
Lines 113 to 119 in c661ab1
internal readonly struct WZYXShuffle4 : IShuffle4 { public byte Control { [MethodImpl(InliningOptions.ShortMethod)] get => SimdUtils.Shuffle.MmShuffle(0, 1, 2, 3); } Typing a literal or the constant approach (as outlined above) won't work here as it's no compile time constant.
Or falls this under the category "trade-off that has to be taken"?be handled in order for the JIT to don't emit the software fallback?
This pattern could be changed to effectively expose
Shuffle(op1),Shuffle(op1, op2)methods instead and those could themselves doIsa.Shuffle(op1, MmShuffle_0123)which would get optimized as expected.The current issue in the JIT is that we are making a decision during "import" on whether or not a given call should be an intrinsic or left as a call. For most APIs this is "always intrinsic", for the handful of APIs that require a constant operand (because the underlying instruction encoding requires it), we currently just say "if the operand is constant, intrinsic; otherwise leave as call".
This misses some cases, particularly where an input becomes constant after inlining. However, enabling handling for that case requires that we either generate some "small fallback" (not feasible in many cases), emit the 256-case jump table "inline" (which would overall be worse for throughput), or update the JIT to support converting the intrinsic back into a call very late (around
rationalizationorlowering). The last option is the "best", but its also incredibly complicated to handle and its not bubbled up to the point where I can do that work yet.Reacted by Günther Foidl and James Jackson-South@tannergooding thanks for the insights -- much appreciated!
Reacted by James Jackson-South
Prerequisites
DEBUGandRELEASEmodeImageSharp version
Current main branch
Other ImageSharp packages and versions
none
Environment (Operating system, version and so on)
all .NET supported
.NET Framework version
all
Description
While working on #1762 I recognized that methods that use
ImageSharp/src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs
Lines 236 to 238 in c661ab1
E.g.
Vp8Encoding.FTransformPass1SSE2looks after inlining the vector constants likeIf instead
SimdUtils.Shuffle.MmShufflethe constant is given as literal, then the code boils down to:This is a de-facto a JIT-limitation, recorded in dotnet/runtime#9989 and dotnet/runtime#38003, also noticed in #1517 (comment)
As this is quite a difference in code-gen, hence in perf it should show up too, I propose to change to typing the shuffle literals explicetely. E.g.
If OK I'd like to tackle this in one shot with #1762 (as I'm touching these pieces anyway).
Edit: I was too eager, the PR is out...
Steps to Reproduce
Look at dissassembly of any method that uses
SimdUtils.Shuffle.MmShuffle.Images
No response