在通用Windows平台中使用Vector <t>进行SIMD

时间:2015-09-20 21:54:39

标签: .net assembly simd uwp .net-native

我正在尝试使用System.Numerics.Vector(T)来矢量化算法并利用CPU的SIMD操作。但是,我的矢量实现比我原来的实现要慢得多。有没有使用可能没有记录的Vector的技巧?这里的具体用途是尝试加速数据kb的Xors。

不幸的是,我在其上找到的几乎所有文档都基于RyuJIT的预发布版本,我不知道有多少资料可以移植到.NET Native。

当我在Vector xor操作期间检查反汇编时,它显示:

00007FFB040A9C10  xor         eax,eax  
00007FFB040A9C12  mov         qword ptr [rcx],rax  
00007FFB040A9C15  mov         qword ptr [rcx+8],rax  
00007FFB040A9C19  mov         rax,qword ptr [r8]  
00007FFB040A9C1C  xor         rax,qword ptr [rdx]  
00007FFB040A9C1F  mov         qword ptr [rcx],rax  
00007FFB040A9C22  mov         rax,qword ptr [r8+8]  
00007FFB040A9C26  xor         rax,qword ptr [rdx+8]  
00007FFB040A9C2A  mov         qword ptr [rcx+8],rax  
00007FFB040A9C2E  mov         rax,rcx  

为什么不使用xmm寄存器和SIMD指令呢?同样奇怪的是,SIMD指令是针对此代码的一个版本生成的,我没有明确地向量化,但它们从未被执行,而是支持常规寄存器和指令。

我确保运行Release,x64,启用Optimize代码。我看到了x86编译的类似行为。我在机器级别的东西上有点新手,所以它可能只是在这里发生了一些我不能正确理解的东西。

Framework版本为4.6,Vector.IsHardwareAccelerated在运行时为false。

更新:“使用.NET Native工具链编译”是罪魁祸首。启用它会导致Vector.IsHardwareAccelerated == false;禁用它会导致Vector.IsHardwareAccelerated == true。我已经确认,当禁用.NET Native时,编译器会使用ymm寄存器生成AVX指令。这导致了一个问题......为什么在.NET Native中没有启用SIMD?有没有办法改变它?

更新切线:我发现自动SSE矢量化数组代码没有被执行的原因是因为编译器插入了一条指令,看看数组的开头是否是在比数组的最后一个元素更低的地址处,如果是,则只使用普通寄存器。我认为这必定是编译器中的一个错误,因为按照惯例,数组的开头应始终位于比其最后一个元素低的地址。它是测试每个操作数数组的内存地址的一组指令的一部分,我想确保它们不重叠。我已为此提交了Microsoft Connect错误报告:https://connect.microsoft.com/VisualStudio/feedback/details/1831117

1 个答案:

答案 0 :(得分:9)

我联系了Microsoft,他发布了.Net Native问题和疑虑的联系地址:https://msdn.microsoft.com/en-us/vstudio/dotnetnative.aspx

我的问题被提交给微软代码生成和优化技术团队的首席软件工程经理Ian Bearman:

  

目前,.NET Native不会优化System.Numerics库   并依赖于默认的库实现。这可能(读:将   很可能)导致使用System.Numerics编写的代码不能执行   很好的.NET Native,因为它将与其他CLR实现。

     

虽然这很不幸,但.NET Native确实支持自动矢量化   它使用上面提到的C ++优化。该   当前发货.NET Native编译器支持SSE2 ISA   ARM上的x86和x64以及NEON ISA上的自动向量化。

他还提到他们希望从C ++编译器中生成所有向量指令(AVX,SSE等)和基于在运行时检测指令集的分支的能力。

然后他建议如果使用指令非常关键,那么组件可以用C ++构建,它可以访问编译器内在函数(可能是这种分支能力?),然后很容易与剩下的C#应用​​程序接口。

对于跳过的SSE2指令,我需要做的就是将它编译成正确的指令是用“a ^ = b”替换循环的“a = a ^ b”。因为它们应该是等效的表达式,所以它似乎是一个bug,但幸运的是它有一个解决方法。