C# 包装System.Numerics.VECTROX很贵-为什么?

C# 包装System.Numerics.VECTROX很贵-为什么?,c#,simd,ryujit,C#,Simd,Ryujit,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;

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;
}
这将JIT到(x64):

和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;
}
它被分为:

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
包装在结构中(类似于第一个示例):

我的代码现在被JITted转换成了更多的代码:

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的遗留代码,这些API的实现将受益于SIMD的优点


编辑:因此,在对系统进行了深入研究之后,我发现System.Numerics类实际上没有什么特别之处。我只需要在我的方法中添加
System.Numerics.jit
属性。然后JIT将用自己的实现替换我的实现<代码>是私有的吗?没问题,只需复制并粘贴即可。尽管如此,最初的问题仍然存在(即使我现在有了一个解决方法)。

问题仅仅来自一个事实,即Vector4包含4个long,DirectX Vector4包含4个float。在每种情况下,仅通过传递向量来添加Xs会使代码更加复杂,因为W、Y和Z即使保持不变也必须复制。在每次“new SomeWrapper(v)”期间以及最后一次在函数外部复制向量,以影响变量的结果

优化结构代码非常棘手。使用struct可以节省堆分配时间,但由于有多个副本,代码变得更长

有两件事可以帮助你:

1) 不要使用包装器,但扩展方法会避免复制到包装器中

2) 不要为返回值分配新的向量,但在可能的情况下使用其中一个(优化代码,但与其他算术类型一样,不帮助使类型保持不变,因此使用时要格外小心)

样本:

struct Vector
{
    public long X;
    public long Y;
}

static class VectorExtension
{ 
    public static void AddToMe(this Vector v, long x, long y)
    {
        v.X += x;
        v.Y += y;
    }

    public static void AddToMe(this Vector v, Vector v2)
    {
        v.X += v2.X;
        v.Y += v2.Y;
    }
}

包装数字时性能不佳。Vector是编译器问题,修复程序于2017年1月20日提交给master:


我不知道传播在这个项目上到底是如何工作的,但似乎修复程序将成为项目的一部分。

所有字段都是浮点数。除SIMD情况外,结构包装是内联的。通常不需要示例中的代码。我的问题是,为什么在SIMD案例中会出现故障?(正如我在更新中所写,我能够找到一个可接受的解决方法)
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  
struct Vector
{
    public long X;
    public long Y;
}

static class VectorExtension
{ 
    public static void AddToMe(this Vector v, long x, long y)
    {
        v.X += x;
        v.Y += y;
    }

    public static void AddToMe(this Vector v, Vector v2)
    {
        v.X += v2.X;
        v.Y += v2.Y;
    }
}