包装昂贵的System.Numerics.VectorX-为什么?

克鲁梅鲁尔

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):

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;
}

它被分为:

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;
}

现在,我的代码更加复杂了:

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优点。

编辑:因此,在coreclr源代码中进行了一些挖掘之后,我发现System.Numerics类实际上没有什么特别之处。我只需要将System.Numerics.JitIntrinsic属性添加到我的方法中即可。然后,JIT将用自己的实现替换我的实现。JitIntrinsic是私人的吗?没问题,只需复制并粘贴即可。原始问题仍然存在(即使我现在有解决方法)。

彼得

包装Numerics.Vector时性能不佳是编译器问题,并且此修复程序于2017年1月20日提交母版:

https://github.com/dotnet/coreclr/issues/7508

我不知道传播在此项目中的工作原理,但似乎该修补程序将成为2.0.0版本的一部分

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

为什么需要用这个简单的LINQ表达式引用System.Numerics?

System.Numerics.Vector.ConditionalSelect用来做什么?

为什么System.Numerics命名空间中没有Matrix3x3?C#

什么是Org.BouncyCastle.Math.BigInteger.ToByteArrayUnsigned的.NET System.Numerics.BigInteger等效项?

缺少System.Numerics.Vectors.Vector <T>

System.Numerics.Vector.GreaterThan and bool结果

System.Numerics.dll中的BigInteger类型和FSharp.Core.dll中的BigInteger类型之间有什么区别?

Protobuff序列化System.Numerics.BigInteger

大数据集上的System.Numerics.Vector <T>

System.Numerics.Vector <int>仅部分初始化

使用System.Numerics.Vectors旋转2D点

C#哪里可以下载System.Numerics

为什么储物障碍被认为很昂贵?

为什么随机设备创建昂贵?

为什么在Java中同步昂贵?

为什么创建线程据说很昂贵?

为什么类型检查比较昂贵?

为什么高速缓存如此昂贵?

为什么要包装函数?

为什么system()存在?

System.Numerics.Vectors'Vector <T>':基本上只是System.UInt128吗?

在System.Numerics.Vector <T>中使用F#度量单位

尝试使用protobuf-net序列化System.Numerics.Quaternion

使用System.Numerics进行矩阵乘法的完全错误的值

我如何在MonoDevelop中的Mono C#中使用System.Numerics?

C#SIMD使用System.Numerics.Vector进行排序/中位数

如何在C#中获取System.Numerics.Vector的元素?

.NET Framework上System.Numerics.Vector <T>的初始化性能

C#中包含System.Numerics.Vector <double>的结构的指针