.NET 中的 SIMD

  • Jonathan Allen
  • 臧秀涛

2014 年 4 月 11 日

话题:.NET语言 & 开发

在 Mono 六年之后,微软的 CLR 实现最终通过 RyuJIT 提供了对 SIMD 的支持。RyuJIT 目前仍处在社区预览阶段,它是下一代的.NET JIT 编译器。

SIMD 代表的是单指令流多数据流(Single Instruction,Multiple Data)。它是一组特殊的 CPU 指令集,其中的指令可以对包含在大小为 2、4、8 或 16 的数组中的每个元素同时执行同一操作。在 2008 年Mono 宣布对 SIMD 的支持时,我们曾报道过。

性能收益非常显著。执行 Spring-Gravity 算法,原生 C++ 程序用了 9.5 秒。比较而言,将其逐行转换到 Mono 中,执行用了 17.7 秒。而再将其从标准操作符转换为 SIMD 函数,运行 Mono 的时间下降到 1.7 秒。

很明显这种特性可以应用于游戏物理引擎和科学研究中,还可以应用于在一系列股票和基金上执行同样计算的金融应用中。

在.NET 中开启 SIMD,不仅需要安装 RyuJIT 并使用正确的向量类型。微软的Kevin Frei列出了一些步骤:

  1. 下载并安装RyuJIT CTP3(同以前一样,需要 x64 Windows 8.1 或 Window Server 2012R2)
  2. 设置“use RyuJIT”环境变量:set COMPLUS_AltJit=*
  3. 现在请注意,下面和以前有点区别了:
  4. 设置一个新的(而且是临时的)“enable SIMD stuff”环境变量:set COMPLUS_FeatureSIMD=1
  5. 把 Microsoft.Bcl.Simd NuGet 包添加到你的项目中(必须选择“include Prerelease”或使用–Pre 选项)
  6. 在 RyuJIT 最终配置好之前还有一些必须处理的麻烦事:对于你编写的会使用新的 Vector 类型的方法,请将一个对 Microsoft.Numerics.Vectors.Vector<T> 的引用添加到会在该方法调用之前调用的某个类的构造器中。我建议就把它放在你的程序的入口类的类构造器中。它必须出现在类的构造器,而不是实例构造器中。
  7. 确保应用确实运行在 x64 上。如果没有看到你的进程中加载了 protojit.dll(通过 tasklist /M protojit.dll 查看),那应该是遗漏了上面的某些东西。

应该注意的是,可用的 SIMD 不止一类。Kevin 写道,

对那些想直接尝试这一特性的人而言,我们可以快速地看一个细节:在 AVX 代码应该会比 SSE2 更好的硬件上,我们打算(而且我们已经开发了原型)让 Vector<T> 自动使用 AVX SIMD 类型。然而要想正确处理,还需要对.NET 运行时做些修改。因为这一限制(时间也不允许),CTP 版本只能支持 SSE2。

RyuJIT 本身主要是为改进应用在 64 位机器上的启动时间而设计的。以前微软假定消费者使用的是 x86,而 x64 仅被用于运行服务器的 IT 部门手中,所以启动时间并不是很严重的问题。这使他们可以选择面向 x86 的快速 JIT 编译器,面向 x64 的则是慢一些、但更高效的 JIT 编译器。快进了大约十年,情况已经发生了变化。

2013 年 9 月的.NET 框架博客上曾经介绍:

.NET 代码生成团队一直在为一个新的下一代 x64 编译器而努力工作,该编译器代号为 RyuJIT。这一新的 JIT 编译器是原来的两倍快,使用它编译的应用,启动时间最多可以减少 30%(花在 JIT 编译器上的时间只是启动时间的一部分,所以应用启动时间并不会随着 JIT 性能加倍而减半)。此外,新的 JIT 编译器仍然会生成极好的代码,可以在服务器进程长期运行的过程中高效执行。

查看英文原文:SIMD Support in .NET

.NET语言 & 开发