Как перемешать Vector128 <T>и добавить элементы, а затем правильно извлечь скалярное значение? - PullRequest
2 голосов
/ 13 апреля 2020

Я использую Vector128<byte> в C# для подсчета совпадений из байтового массива с индексом 16.

Это часть реализации байтовой версии Микро Оптимизация гистограммы из 4 блоков большого массива или списка , используя технику из Как подсчитывать вхождения символов, используя SIMD расширения 8-битных счетчиков до 64 внутри внешнего l oop (hsum_epu8_epu64 вспомогательная функция), а затем после всех циклов суммирования этого вектора счетчиков до одного скаляра (hsum_epu64_scalar).

Так что C ++ со встроенными функциями Intel должен быть перенесен на C# , А без AVX2 мы используем 128-битные целочисленные векторы, а не 256.


Массив байтов состоит из чисел 0 и 1, где встречается 5 0.

Теперь задача состоит в том, чтобы сосчитать те 5 0, где мы можем видеть, что 2 из 0 находятся в верхней полосе Vector128<byte>, а 3 из 0 - в нижней полосе Vector128<byte>.

Я успешно прошел весь код до того места, где я Sse2.SumAbsoluteDifferences, и могу извлечь число 0 для sumHigh и sumLow, показывающее 3 и 2 соответственно.

проблема начинается сейчас, когда мне нужно перетасовать так, чтобы верхний и нижний диапазоны поменялись местами, чтобы позже я мог извлечь противоположности в: sumHigh и sumLow для sum64b

Я поместил много комментариев в код, так что я думаю, что можно следовать коду и увидеть там также, как я пытаюсь перемешать и завершить код.

(Код также показывает, что мой процессор AMD K10 поддерживает: Sse, Sse2, Sse3)

    using System.Runtime.Intrinsics;
    using System.Runtime.Intrinsics.X86;

    private void button2_Click(object sender, EventArgs e)
    {
        //This shows what is supported on my processor. However it seems that I could use something from "Avx" anyway
        bool avx = Avx.IsSupported; //false
        bool avx2 = Avx2.IsSupported; //false

        bool sse = Sse.IsSupported; //true
        bool sse2 = Sse2.IsSupported; //true
        bool sse3 = Sse3.IsSupported; //true
        bool ssse3 = Ssse3.IsSupported; //false
        bool sse41 = Sse41.IsSupported; //false
        bool sse42 = Sse42.IsSupported; //false

        //Create a bytearray of 16 indexes. As seen: '0' occur 2 times in the upper band and 3 times in the lower band
        //We want to count those "0" in the below code
        byte[] v1 = new byte[16];
        v1[0] = 0; v1[1] = 0; v1[2] = 1; v1[3] = 1; v1[4] = 1; v1[5] = 1; v1[6] = 1; v1[7] = 1;
        v1[8] = 1; v1[9] = 0; v1[10] = 0; v1[11] = 0; v1[12] = 1; v1[13] = 1; v1[14] = 1; v1[15] = 1;

        Vector128<byte> counts = Vector128<byte>.Zero;
        unsafe
        {
            fixed (byte* fixedInput = v1)
            {
                //Load byte Vector with 16 indexes
                var v = Avx.LoadVector128(&fixedInput[0]);

                //Now match how many "0" we can find in "Vector128: v". 'counts' show the result string where: '1' tells where we found: "0".
                //As seen it happened as expected total times: 5 (2 times in the upper band and 3 times in the lower band of the Vector)
                byte val = 0;
                var match = Avx.CompareEqual(v, Vector128.Create(val));
                counts = Avx.Subtract(counts, match); //counts: <1,1,0,0,0,0,0,0,0,1,1,1,0,0,0,0>



                //Extract high/low bands
                //So we use "SumAbsoluteDifferences" to "Separately sum the 8 low differences and 8 high differences to produce two unsigned word integer results."
                //We can see on index 0: 2 and on index 4: 3
                Vector128<ushort> sum64 = Vector128<ushort>.Zero;
                sum64 = Sse2.Add(sum64, Sse2.SumAbsoluteDifferences(counts, Vector128<byte>.Zero)); //sum64: <2,0,0,0,3,0,0,0>

                //I AM NOT SURE OF THE CODE BELOW HOW TO DO IT PROPERLY!

                //Now I need to shuffle the above: "<2,0,0,0,3,0,0,0>" but are not sure of how the complete process is to do this correctly?
                //Below is a start of an "attempt" but are not sure how to do this all the way correctly?

                Vector128<uint> result = Sse2.Shuffle(sum64.AsUInt32(), 0xB1); 

                //Extract high/low bands from ther shuffle above?
                //Vector128<uint> sum64b = Vector128<uint>.Zero;
                //sum64b = Sse2.Add(sum64b, result);
                //sumHigh = Sse2.Extract(sum64b, 1); //0
                //sumLow = Sse2.Extract(sum64b, 0); //                                                                    
            }
        }
    }

Использование 16-битных извлечений будет возможно, но не пригодно для больших количеств.

    var sumHigh = Sse2.Extract(sum64, 4);    // pextrw
    var sumLow = Sse2.Extract(sum64, 0); //sumHigh == 3 and sumLow == 2
    var sumScalar = SumLow + sumHigh;

Примечание от @PeterCordes: реальный вариант использования будет l oop добавить до 255 векторов в counts, затем во внешнем l oop накапливается в широкие элементы в sum64 с Sse2.SumAbsoluteDifferences и Sse2.Add и сбрасывается counts. Эта часть выглядит правильно в этом порту C#, за исключением того, что sum64 не должен использовать ushort элементов.

Часть, о которой этот вопрос задает, является горизонтальной суммой двух 64-битных векторов элементы вплоть до одного скалярного целого числа. (Реальный сценарий использования имеет три вектора отсчетов, из 3 сегментов гистограммы; транспонирование и сумма могут работать, но просто делать отдельные горизонтальные суммы для каждого вектора хорошо.)

1 Ответ

1 голос
/ 13 апреля 2020

Это должен быть ответ о том, как подсчитать, сколько 0 в верхнем и нижнем элементах байтового массива v1.

Ответ будет:
нижних элементов: 2
высших элементов: 3

  1. Итак, сначала Sse2.SumAbsoluteDifferences используется для:
    Суммирования 8 низких разностей и 8 высоких разностей для получения двух целочисленные слова без знака

  2. Тогда мы можем Sse2.UnpackHigh верхние элементы

  3. Используйте sum64.ToScalar(), чтобы получить нижние элементы, потому что scalar совпадает со значением первого элемента.

    private void button2_Click(object sender, EventArgs e)
    {
        //Create a bytearray of 16 indexes. As seen: '0' occur 2 times in the upper band and 3 times in the lower band
        //We want to count those "0" in the below code
        byte[] v1 = new byte[16];
        v1[0] = 0; v1[1] = 0; v1[2] = 1; v1[3] = 1; v1[4] = 1; v1[5] = 1; v1[6] = 1; v1[7] = 1;
        v1[8] = 1; v1[9] = 0; v1[10] = 0; v1[11] = 0; v1[12] = 1; v1[13] = 1; v1[14] = 1; v1[15] = 1;
    
        Vector128<byte> counts = Vector128<byte>.Zero;
        unsafe
        {
            fixed (byte* fixedInput = v1)
            {
                //Load byte Vector with 16 indexes
                var v = Avx.LoadVector128(&fixedInput[0]);
    
                //Now match how many "0" we can find in "Vector128: v". 'counts' show the result string where: '1' tells where we found: "0".
                //As seen it happened as expected total times: 5 (2 times in the upper band and 3 times in the lower band of the Vector)
                byte val = 0;
                var match = Avx.CompareEqual(v, Vector128.Create(val));
                counts = Avx.Subtract(counts, match); //counts: <1,1,0,0,0,0,0,0,0,1,1,1,0,0,0,0>
    
                //SumAbsoluteDifferences
                Vector128<UInt64> sum64 = Vector128<UInt64>.Zero;
                sum64 = Sse2.Add(sum64, Sse2.SumAbsoluteDifferences(counts, Vector128<byte>.Zero).AsUInt64()); //sum64: <2,0,0,0,3,0,0,0>
    
                //UnpackHigh and add the lower,upper element from the Vector128<UInt64>
                //var lower = sum64; // low element already where we want it
                UInt64 upper = Sse2.UnpackHigh(sum64, sum64).ToScalar(); //3
                Uint64 total_matches_of_0 = Sse2.Add(sum64, upper).ToScalar(); //2 + 3
            }
        }
    }
    
...