C# 中带 SIMD 的 2x2 矩阵向量积

Posted

技术标签:

【中文标题】C# 中带 SIMD 的 2x2 矩阵向量积【英文标题】:2x2 Matrix vector product in C# with SIMD 【发布时间】:2020-05-26 18:11:03 【问题描述】:

我正在做一些事情,我想每秒多次将相同的 2x2 short 值矩阵与不同的二维 short 值向量相乘,在这种情况下,性能很重要。现在,我只是以天真的方式做它并写出矩阵乘法。我查阅了 C# 的 SIMD 功能,发现没有办法制作这种类型的 2x2 矩阵。所以我尝试使用来自System.Numerics.VectorsVector<T> 结构来做到这一点。构造函数希望至少有 4 个元素进入向量。我可以解决它并使其与 4 维向量一起工作,但我想知道是否有一种方法可以更轻松地完成我想做的事情:将 2x2 矩阵与 2 维向量相乘成一个新的 2 维向量使用 SIMD。

【问题讨论】:

你能使用来自System.Runtime.Intrinsics.X86的SIMD instrinsics吗? @harold 可以在任何系统上运行吗?从 Microsoft 文档看来,它们只能在 Intel 处理器上运行 它适用于 Intel 和 AMD 处理器,如果这就是您的意思的话。虽然不是 ARM。 【参考方案1】:

使用System.Runtime.Intrinsics.X86Sse2.MultiplyAddAdjacent 可以用来做繁重的工作,用一些洗牌等来排列数据。例如:

struct Vec2

    public short X, Y;


struct Mat2x2

    public short A, B, C, D;


static unsafe Vec2 Mul(Mat2x2 m, Vec2 v)

    // movd: 0 0 0 0 0 0 Y X
    var rawvec = Sse2.LoadScalarVector128((int*)&v);
    // pshufd: Y X Y X Y X Y X
    var vec = Sse2.Shuffle(rawvec, 0).AsInt16();
    // movq: 0 0 0 0 D C B A
    var mat = Sse2.LoadScalarVector128((ulong*)&m).AsInt16();
    // pmaddwd: 0 0 DY+CX BY+AX
    var dword_res = Sse2.MultiplyAddAdjacent(mat, vec);
    // packssdw: 0 0 DY+CX BY+AX 0 0 DY+CX BY+AX
    var rawres = Sse2.PackSignedSaturate(dword_res, dword_res);
    Vec2 res;
    *((int*)&res) = Sse2.ConvertToInt32(rawres.AsInt32());
    return res;

生成的程序集相当合理:

 mov         dword ptr [rsp+10h],ecx  
 mov         qword ptr [rsp+18h],rdx  
 vmovd       xmm0,dword ptr [rsp+18h]  
 vpshufd     xmm0,xmm0,0  
 vmovq       xmm1,mmword ptr [rsp+10h]  
 vpmaddwd    xmm0,xmm1,xmm0  
 vpackssdw   xmm0,xmm0,xmm0  
 vmovd       eax,xmm0  
 mov         dword ptr [rsp],eax
 mov         eax,dword ptr [rsp]

但这并不理想。 mv 函数参数(以及最后的结果)都是“反弹”内存.. 诚然,这正是 C# 代码所说的。这可以通过手动将XY 与算术组合成int 然后使用ConvertScalarToVector128Int32 来解决,但是JIT 显然不够聪明,无法看到算术是多余的。所以似乎没有什么好的解决办法。希望在某个时候,JIT 优化器能够检测到这种毫无意义的“内存反弹”情况并将其删除。

另一点是MultiplyAddAdjacent 被部分浪费了:它做了 8 个乘积,但只有 4 个是有用的计算,向量的上半部分只是零。如果您有 2 个向量要乘以相同的 2x2 矩阵,则可以以很小的额外成本来完成,比简单地调用上述函数两次要少得多。

【讨论】:

同样令人失望的是,启用 AVX 的编译没有使用 vbroadcastss 而不是 vmovd + vpshufd @PeterCordes 是的,这可以通过 vec = Avx.BroadcastScalarToVector128((float*)&v).AsInt16(); 手动完成(仍然会发生内存反弹)

以上是关于C# 中带 SIMD 的 2x2 矩阵向量积的主要内容,如果未能解决你的问题,请参考以下文章

向量与 SIMD 的点积

向量乘法(矩阵乘法)奇数输出的向量

计算混合实复矩阵向量积的最快方法是啥?

Swift 中的向量 SIMD 类型

标量向量矩阵张量之间的区别和联系

R语言使用crossprod函数和tcrossprod函数计算矩阵matrix交叉积(Matrix Cross Product)crossprod函数tcrossprod函数计算矩阵和向量的交叉积