TL;DR : System.Numerics.Vectors 型のラップはなぜコストがかかるのですか?それについて何かできることはありますか?
次のコードを検討してください。
[MethodImpl(MethodImplOptions.NoInlining)]
private static long GetIt(long a, long b)
{
var x = AddThem(a, b);
return x;
}
private static long AddThem(long a, long b)
{
return a + b;
}
これは (x64) に JIT します:
00007FFDA3F94500 lea rax,[rcx+rdx]
00007FFDA3F94504 ret
および x86:
00EB2E20 push ebp
00EB2E21 mov ebp,esp
00EB2E23 mov eax,dword ptr [ebp+10h]
00EB2E26 mov edx,dword ptr [ebp+14h]
00EB2E29 add eax,dword ptr [ebp+8]
00EB2E2C adc edx,dword ptr [ebp+0Ch]
00EB2E2F pop ebp
00EB2E30 ret 10h
これを構造体でラップすると、たとえば
public struct SomeWrapper
{
public long X;
public SomeWrapper(long X) { this.X = X; }
public static SomeWrapper operator +(SomeWrapper a, SomeWrapper b)
{
return new SomeWrapper(a.X + b.X);
}
}
と変更GetIt
、例えば
private static long GetIt(long a, long b)
{
var x = AddThem(new SomeWrapper(a), new SomeWrapper(b)).X;
return x;
}
private static SomeWrapper AddThem(SomeWrapper a, SomeWrapper b)
{
return a + b;
}
JITted の結果は、ネイティブ型を直接使用した場合とまったくAddThem
同じです ( 、およびSomeWrapper
オーバーロードされた演算子とコンストラクターはすべてインライン化されています)。予想通り。
ここで、SIMD 対応の型でこれを試してみると、たとえばSystem.Numerics.Vector4
次のようになります。
[MethodImpl(MethodImplOptions.NoInlining)]
private static Vector4 GetIt(Vector4 a, Vector4 b)
{
var x = AddThem(a, b);
return x;
}
次のように JIT されます。
00007FFDA3F94640 vmovupd xmm0,xmmword ptr [rdx]
00007FFDA3F94645 vmovupd xmm1,xmmword ptr [r8]
00007FFDA3F9464A vaddps xmm0,xmm0,xmm1
00007FFDA3F9464F vmovupd xmmword ptr [rcx],xmm0
00007FFDA3F94654 ret
ただし、構造体でラップするVector4
と (最初の例と同様):
public struct SomeWrapper
{
public Vector4 X;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public SomeWrapper(Vector4 X) { this.X = X; }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SomeWrapper operator+(SomeWrapper a, SomeWrapper b)
{
return new SomeWrapper(a.X + b.X);
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static Vector4 GetIt(Vector4 a, Vector4 b)
{
var x = AddThem(new SomeWrapper(a), new SomeWrapper(b)).X;
return x;
}
私のコードは、さらに多くのコードに JIT されています。
00007FFDA3F84A02 sub rsp,0B8h
00007FFDA3F84A09 mov rsi,rcx
00007FFDA3F84A0C lea rdi,[rsp+10h]
00007FFDA3F84A11 mov ecx,1Ch
00007FFDA3F84A16 xor eax,eax
00007FFDA3F84A18 rep stos dword ptr [rdi]
00007FFDA3F84A1A mov rcx,rsi
00007FFDA3F84A1D vmovupd xmm0,xmmword ptr [rdx]
00007FFDA3F84A22 vmovupd xmmword ptr [rsp+60h],xmm0
00007FFDA3F84A29 vmovupd xmm0,xmmword ptr [rsp+60h]
00007FFDA3F84A30 lea rax,[rsp+90h]
00007FFDA3F84A38 vmovupd xmmword ptr [rax],xmm0
00007FFDA3F84A3D vmovupd xmm0,xmmword ptr [r8]
00007FFDA3F84A42 vmovupd xmmword ptr [rsp+50h],xmm0
00007FFDA3F84A49 vmovupd xmm0,xmmword ptr [rsp+50h]
00007FFDA3F84A50 lea rax,[rsp+80h]
00007FFDA3F84A58 vmovupd xmmword ptr [rax],xmm0
00007FFDA3F84A5D vmovdqu xmm0,xmmword ptr [rsp+90h]
00007FFDA3F84A67 vmovdqu xmmword ptr [rsp+40h],xmm0
00007FFDA3F84A6E vmovdqu xmm0,xmmword ptr [rsp+80h]
00007FFDA3F84A78 vmovdqu xmmword ptr [rsp+30h],xmm0
00007FFDA3F84A7F vmovdqu xmm0,xmmword ptr [rsp+40h]
00007FFDA3F84A86 vmovdqu xmmword ptr [rsp+20h],xmm0
00007FFDA3F84A8D vmovdqu xmm0,xmmword ptr [rsp+30h]
00007FFDA3F84A94 vmovdqu xmmword ptr [rsp+10h],xmm0
00007FFDA3F84A9B vmovups xmm0,xmmword ptr [rsp+20h]
00007FFDA3F84AA2 vmovups xmm1,xmmword ptr [rsp+10h]
00007FFDA3F84AA9 vaddps xmm0,xmm0,xmm1
00007FFDA3F84AAE lea rax,[rsp]
00007FFDA3F84AB2 vmovupd xmmword ptr [rax],xmm0
00007FFDA3F84AB7 vmovdqu xmm0,xmmword ptr [rsp]
00007FFDA3F84ABD vmovdqu xmmword ptr [rsp+70h],xmm0
00007FFDA3F84AC4 vmovups xmm0,xmmword ptr [rsp+70h]
00007FFDA3F84ACB vmovupd xmmword ptr [rsp+0A0h],xmm0
00007FFDA3F84AD5 vmovupd xmm0,xmmword ptr [rsp+0A0h]
00007FFDA3F84ADF vmovupd xmmword ptr [rcx],xmm0
00007FFDA3F84AE4 add rsp,0B8h
00007FFDA3F84AEB pop rsi
00007FFDA3F84AEC pop rdi
00007FFDA3F84AED ret
JITは何らかの理由でレジスタを使用できず、代わりに一時変数を使用することを決定したようですが、その理由はわかりません。最初はアラインメントの問題かもしれないと思ったのですが、最初に両方を xmm0 にロードしてからメモリへのラウンドトリップを決定する理由がわかりません。
ここで何が起こっているのですか?さらに重要なことに、それを修正できますか?
このように構造をラップしたい理由は、API を使用する多くのレガシー コードがあり、その実装が SIMD の利点の恩恵を受けるからです。
編集:したがって、coreclr ソースを掘り下げた後、実際には System.Numerics クラスについて特別なことではないことがわかりました。System.Numerics.JitIntrinsic
メソッドに属性を追加するだけです。その後、JIT は私の実装を独自のものに置き換えます。JitIntrinsic
プライベートですか?問題ありません。コピーして貼り付けるだけです。ただし、元の質問はまだ残っています (回避策がある場合でも)。