具有SIMD的C#中的2x2矩阵矢量乘积

时间:2020-05-26 18:11:03

标签: c# simd

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

1 个答案:

答案 0 :(得分:2)

可以使用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矩阵,则可以花很少的额外费用,比简单地两次调用上述函数要少得多。