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

Gle*_*den 1 .net bit-manipulation simd hardware-acceleration

我正在查看版本4.5.0-preview1-26216-02Vector<T>System.Numerics.Vectors命名空间.MSDN文档说:

Vector<T>是一个不可变的结构,表示指定数字类型的单个向量.实例的计数Vector<T>是固定的,但其上限取决于CPU寄存器.
https://docs.microsoft.com/en-us/dotnet/api/system.numerics.vector-1(强调添加)

即使忽略误导的措辞" 计算向量的数量 [sic.] ",这句话似乎还不清楚,因为它暗示不同的Vector<T>实例可能有不同的 - 尽管"固定"到某些CPU限制 - "计数"(再次,究竟是什么'所谓的'计数'?)这里没有提到实际的Count属性 - 或者实际上在介绍页面的任何地方都没有提到.

现在通常情况下,我认为"只读""不可变"在描述实例的属性或字段时比传统上使用"固定",但在这种情况下,事实证明该Vector<T>.Count属性虽然是只读的,但也是静态的,因此与任何Vector<T> 实例无关.相反,它的值仅根据泛型类型参数而变化T(然后可能来自机器到机器,如图所示):

bool hw = Vector.IsHardwareAccelerated;    // --> true

var c = (Vector<sbyte>.Count, Vector<short>.Count, Vector<int>.Count, Vector<long>.Count);
Debug.WriteLine(c);    // -->  (16, 8, 4, 2)
Run Code Online (Sandbox Code Playgroud)

哦.

它基本上System.Int128是伪装的吗?是吗?我的问题是:

  • 我错过了什么吗?这是真的,我知之甚少SIMD,但我认为这个库将允许使用的多少不是仅仅128位的更广泛的硬件加速的数据类型.我的HPSG解析引擎通常执行5,000+位的密集位计算向量.
  • 再假设我没有错过这一点,为什么不把它称为System.Int128/ System.UInt128而不是Vector<T>?使用通用基元类型对其进行参数化确实带来了一定的好处,但是我错误地认为它更像是一个有用的扩展数组(即,blittable元素T),而不是只有一个双宽度的CPU寄存器,这对我的你的心灵就像"标量"一样.

    不要误会我的意思,128位寄存器是有趣的,有用的,令人兴奋的东西 - 如果这里只是有点超卖?例如,Vector<byte>无论是什么,无论你是否需要或使用它们,都会有16个元素,所以它的精神Count因集合而异,似乎不适用于此.

  • 即使单个Vector<T>不能像我希望的那样直接处理我的用例,是否值得更新我当前的实现(它使用ulong[N >> 6]每个N位向量的Vector<ulong>[N >> 7]数组)来代替使​​用数组?

    ...是的,那是" 阵列 Vector<ulong> ",这对我来说再次显得很奇怪; 不应该在其名称中具有"Vector"的类型是否足够或有用地可扩展而无需显式创建数组来包装多个实例?

  • 除了每个128位SIMD按位操作处理两倍数据的事实之外,每个操作码的周期内SIMD逐位运算还会更快(或更慢)吗?
  • 是否有其他常用或可用的硬件平台System.Numerics.Vectors实际报告不同的SIMD位宽?

har*_*old 8

矢量大小并不总是16个字节,尽管这很常见.例如,在具有AVX2的平台上,以64位模式运行的程序获得32字节向量.通过这种方式,通过在不同模式下运行程序,Count属性也可以在同一台机器上(对于相同的机器T)而变化.原则上它不一定是这样,即使只支持AVX1,32位程序仍然可以使用256位操作,但这不是System.Numerics.Vectors的工作方式.CPU的每个功能级别的不同大小是API设计的一个相当基本的部分,可能是为了实现某种形式的面向未来,尽管它可能导致缺少shuffle(这很难指定为矢量的非静态已知大小).

我认为这个库允许使用比128位更广泛的硬件加速数据类型

这在硬件中不存在,因此很难提供.顾名思义,AVX-512最高可达512位,但就目前主流CPU的SIMD而言.

为什么不叫它System.Int128/System.UInt128

我希望这些类型映射到实际的整数类型,而不是矢量类型.许多操作,这将使在一个128位的整数感实际上并不存在如CPU指令,并且几乎所有的操作存在于操作2×64(Vector<long>,  long[2]),4×32(Vector<int>,  int[4]),8×16(Vector<short>,  short[8])或16×8(Vector<byte>,  byte[16])位向量(或支持它的平台上的两倍宽度).在a上提供"逐字节添加"操作Int128会很奇怪,并且提供真正的128位加法使它更加奇怪.除了前面提到的,大小不是128位的定义,这很常见.

尽管存在一些例外情况,但许多SIMD操作都非常快.例如,32位乘法通常具有相当极端的延迟.该System.Numerics.VectorsAPI还允许一些不存在的操作(即必须慢慢效仿,比如整数除法或字节乘法)没有暗示有问题.映射到实际存在的指令的操作通常很快.

虽然逐位操作ulong也很快,但从"每单位时间完成的总工作量"的角度来看,它们的矢量版本甚至更好.例如,Skylake可以在每个周期执行(最多)四个标量按位运算(但是额外的操作,如加法和比较/分支以使循环在同一资源上竞争)但是使用SIMD执行三个256位按位运算,这是同时工作量的3倍,它为标量操作或分支留下了一个执行端口.

所以是的,它可能值得使用.你可以保留数组ulong并使用construct-from-array构造函数Vector<T>,这样你就不必在任何地方处理向量.例如,索引到具有变量索引的向量根本不是一个好的操作,导致分支,向量存储和标量重新加载.矢量的可变大小性质显然也使得直接使用它们的数组变得非常复杂,而不是使用基元类型的数组然后从它们进行向量加载.您可以轻松地将数组的长度向上舍入为向量计数的倍数,以消除对小数组循环的需要,以处理不完全适合向量的数组末尾的剩余项目.