Avx2 内在函数不使用所有可用的寄存器。 .NET 8

问题描述 投票:0回答:1

我使用 SIMD 优化了某些算法,使其与 L1 缓存相比具有延迟限制。由于只有 C# 编译器知道的原因, said 莫名其妙地发出代码,仅使用 ymm0 并忽略其他 15 个 ymmX 寄存器。事实上,每次操作后,它似乎都会将结果存储回 RAM(L1 缓存),然后直接从同一位置重新加载。

根据我目前的理解,这是实际的 JIT 输出与预期输出。

观察拆解时:

    mov         qword ptr [rbp-0B0h],rcx                                
Vector256<ulong> Accum_vec = Avx2.BroadcastScalarToVector256(&zero);
    vpbroadcastq ymm0,mmword ptr [rbp-0B0h]  
    vmovups     ymmword ptr [rbp-0D0h],ymm0  

Vector256<ulong> A_vec = Avx2.LoadVector256(A_Ptr);
    mov         rcx,qword ptr [rbp-60h]  
    vmovups     ymm0,ymmword ptr [rcx]  
    vmovups     ymmword ptr [rbp-1B0h],ymm0 
    
Vector256<ulong> B_vec = Avx2.LoadVector256(B_Ptr);
    mov         rcx,qword ptr [rbp-70h]  
    vmovups     ymm0,ymmword ptr [rcx]  
    vmovups     ymmword ptr [rbp-0F0h],ymm0  
    
Vector256<ulong> C_vec = Avx2.LoadVector256(C_Ptr);
    mov         rcx,qword ptr [rbp-80h]  
    vmovups     ymm0,ymmword ptr [rcx]  
    vmovups     ymmword ptr [rbp-110h],ymm0  
    
Vector256<ulong> D_vec = Avx2.LoadVector256(D_Ptr);
    mov         rcx,qword ptr [rbp-90h]  
    vmovups     ymm0,ymmword ptr [rcx]  
    vmovups     ymmword ptr [rbp-130h],ymm0  
    
Accum_vec = Avx2.Add(A_vec, B_vec);
    vmovups     ymm0,ymmword ptr [rbp-1B0h]  
    vpaddq      ymm0,ymm0,ymmword ptr [rbp-0F0h]  
    vmovups     ymmword ptr [rbp-0D0h],ymm0  
    
Accum_vec = Avx2.Add(Accum_vec, C_vec);
    vmovups     ymm0,ymmword ptr [rbp-0D0h]  
    vpaddq      ymm0,ymm0,ymmword ptr [rbp-110h]  
    vmovups     ymmword ptr [rbp-0D0h],ymm0  
    
Accum_vec = Avx2.Add(Accum_vec, D_vec);
    vmovups     ymm0,ymmword ptr [rbp-0D0h]  
    vpaddq      ymm0,ymm0,ymmword ptr [rbp-130h]  
    vmovups     ymmword ptr [rbp-0D0h],ymm0  
    
Avx2.Store(store_Ptr, Accum_vec);
    mov         rcx,qword ptr [rbp-0A0h]  
    vmovups     ymm0,ymmword ptr [rbp-0D0h]  
    vmovups     ymmword ptr [rcx],ymm0  

当然应该是:

    mov         qword ptr [rbp-0B0h],rcx  
Vector256<ulong> Accum_vec = Avx2.BroadcastScalarToVector256(&zero);
    vpbroadcastq ymm0,mmword ptr [rbp-0B0h]

Vector256<ulong> A_vec = Avx2.LoadVector256(A_Ptr);
    mov         rcx,qword ptr [rbp-60h]  
    vmovups     ymm1,ymmword ptr [rcx]
    
Vector256<ulong> B_vec = Avx2.LoadVector256(B_Ptr);
    mov         rcx,qword ptr [rbp-70h]  
    vmovups     ymm2,ymmword ptr [rcx]
    
Vector256<ulong> C_vec = Avx2.LoadVector256(C_Ptr);
    mov         rcx,qword ptr [rbp-80h]  
    vmovups     ymm3,ymmword ptr [rcx]
    
Vector256<ulong> D_vec = Avx2.LoadVector256(D_Ptr);
    mov         rcx,qword ptr [rbp-90h]  
    vmovups     ymm4,ymmword ptr [rcx] 
    
Accum_vec = Avx2.Add(A_vec, B_vec);
    vpaddq      ymm0, ymm1, ymm2
    
Accum_vec = Avx2.Add(Accum_vec, C_vec);
    vpaddq      ymm0, ymm0, ymm3
    
Accum_vec = Avx2.Add(Accum_vec, D_vec);
    vpaddq      ymm0, ymm0, ymm4
    
Avx2.Store(store_Ptr, Accum_vec);
    mov         rcx,qword ptr [rbp-0A0h] 
    vmovups     ymmword ptr [rcx],ymm0  

是否有理由 C# 内在函数仅“理解”ymm0(以及给定其他函数及其参数的 1 和 2)?

为什么要全部加载/存储,即使加载和存储相同的变量?

尝试了各种重新排列 SIMD 代码的方式,以尝试使用所有 16 个 AVX2 寄存器让 JITter 输出代码。它不起作用。 JIT 输出的代码只与 ymm0 相关。

c# compiler-optimization simd intrinsics avx
1个回答
0
投票

解决了!使用以下内容修饰 SIMD/内在方法:

[MethodImplAttribute(MethodImplOptions.AggressiveOptimization)]

导致 JITter 利用所有寄存器发出完全优化的 asm,并避免恒定的加载/存储/加载/存储类似调试模式。

在没有上述指令的情况下,尚不清楚 JITter 是否会向程序集缓存发出未优化的代码。如果进一步清晰,我将更新这个答案。

© www.soinside.com 2019 - 2024. All rights reserved.