2x2 матричное векторное произведение в C# с SIMD - PullRequest
1 голос
/ 26 мая 2020

Я делаю что-то, где я хочу много раз в секунду умножить одну и ту же матрицу 2x2 short с разными двумерными short значениями векторов, и в этом случае важна производительность. Прямо сейчас я просто делаю это наивным способом и выписываю матричное умножение. Я просмотрел возможности SIMD C# и обнаружил, что нет возможности сделать матрицу 2x2 такого типа. Поэтому я попытался сделать это с помощью структуры Vector<T> из System.Numerics.Vectors. Однако конструктор ожидал, что в вектор будет добавлено не менее 4 элементов go. Я мог бы обойти это и заставить его работать с 4-мерными векторами, но мне было интересно, есть ли способ сделать то, что я хотел сделать проще: умножить матрицу 2x2 с 2-мерным вектором на новый 2-мерный вектор с SIMD.

1 Ответ

2 голосов
/ 26 мая 2020

Используя System.Runtime.Intrinsics.X86, Sse2.MultiplyAddAdjacent можно использовать для выполнения тяжелой работы, с некоторой перетасовкой et c для выравнивания данных. Например:

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]

Но это не идеально. Аргументы функции m и v (и результат в конце) оба «отскакивают» через память… что, по общему признанию, именно то, что и сказал код C#. Это можно обойти, вручную объединив X и Y в int с arithmeti c, а затем используя ConvertScalarToVector128Int32, но тогда JIT, по-видимому, недостаточно умен, чтобы увидеть, что арифметическое c равно избыточный. Так что, похоже, нет хорошего решения. Надеюсь, что в какой-то момент JIT-оптимизатор сможет обнаруживать такие бессмысленные ситуации «отскока через память» и удалять их.

Другой момент - MultiplyAddAdjacent частично потрачен впустую: он выполняет 8 продуктов, но только 4 - полезное вычисление, верхняя половина вектора - это просто нули. Если бы у вас было 2 вектора для умножения на одну и ту же матрицу 2x2, это можно было бы сделать с небольшими дополнительными затратами, намного меньшими, чем простой вызов вышеуказанной функции дважды.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...