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

Den*_*nis 1 c# simd

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

har*_*old 5

使用System.Runtime.Intrinsics.X86,Sse2.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;
}
Run Code Online (Sandbox Code Playgroud)

最终的组装结果相当合理:

 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]
Run Code Online (Sandbox Code Playgroud)

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

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