c# умножение элементов массива с использованием system.numerics - PullRequest
0 голосов
/ 09 февраля 2020

Я экспериментирую с System.Numerics для нескольких элементов массива. Есть ли более быстрый способ умножения элемента результирующего вектора (accVector) вместе? В настоящее время accVector необходимо преобразовать в массив, в котором элементы умножаются вместе с помощью LINQ.

        private double VectorMultiplication(double[] array)
        {
            int vectorSize = Vector<double>.Count;
            var accVector = Vector<double>.One;
            int i;

            for (i = 0; i <= array.Length - vectorSize; i += vectorSize)
            {
                var v = new Vector<double>(array, i);
                accVector = Vector.Multiply(accVector, v);
            }

            var tempArray = new double[Vector<double>.Count];
            accVector.CopyTo(tempArray);
            var result = tempArray.Aggregate(1d, (p, d) => p * d);

            for (; i < array.Length; i++)
            {
                result *= array[i];
            }
            return result;
        }

1 Ответ

2 голосов
/ 10 февраля 2020

Существует ли более быстрый способ умножения элемента результирующего вектора (accVector) вместе?

В пределах Sytem.Numerics, нет. Как упоминал Питер в комментариях, обычно вы начинаете с того, что делите 256-битный вектор на две 128-битные половины и умножаете их, а затем используете перемешивание для обработки 128-битной части. Но System.Numerics не предлагает перемешивание и не позволяет вам выбирать размер используемого вами вектора.

Обычный подход может использоваться с системой .Runtime.Intrinsics.X86 API , для которого требуется. NET Core 3.0 или выше.

Например:

static double product(Vector256<double> vec)
{
    var t = Sse2.Multiply(vec.GetLower(), vec.GetUpper());
    return t.GetElement(0) * t.GetElement(1);
}

Может показаться, что это плохо, оставляя загадочно GetElement до JIT-движка, чтобы понять, но на самом деле кодоген действительно разумен:

 vmovupd     ymm0,ymmword ptr [rcx] 
 vextractf128 xmm0,ymm0,1  
 vmovupd     ymm1,ymmword ptr [rcx]  
 vmulpd      xmm0,xmm1,xmm0  
 vmovaps     xmm1,xmm0  
 vpshufd     xmm0,xmm0,0EEh  
 vmulsd      xmm0,xmm0,xmm1

Так что, похоже, GetElement(0) неявно и GetElement(1) приводит к vpshufd, хорошо. Копирование xmm0 в xmm1 вместо неразрушающего vpshufd немного загадочно, но не так уж плохо, в целом лучше, чем я обычно ожидаю. NET .. Я тестировал эту функцию без встроенных символов, обычно это должны быть встроены, а нагрузки должны быть go.


. Основное значение l oop может быть улучшено, поскольку пропускная способность умножения намного лучше, чем его задержка. Прямо сейчас умножения выполняются по одному (то есть, по одному умножению vector за раз) с задержкой между (5 циклов в Haswell, 4 в Broadwell и новее) для ожидания предыдущего умножения до финала sh, но, например, Intel Haswell может запускать два умножения за цикл, что в 10 раз больше. Реально улучшение не будет таким большим, но помогает создание некоторой возможности для параллелизма на уровне команд.

Например (не проверено):

var acc0 = Vector<double>.One;
var acc1 = Vector<double>.One;
var acc2 = Vector<double>.One;
var acc3 = Vector<double>.One;
var acc4 = Vector<double>.One;
var acc5 = Vector<double>.One;
var acc6 = Vector<double>.One;
var acc7 = Vector<double>.One;
int i;

for (i = 0; i <= array.Length - vectorSize * 8; i += vectorSize * 8)
{
    acc0 = Vector.Multiply(acc0, new Vector<double>(array, i));
    acc1 = Vector.Multiply(acc1, new Vector<double>(array, i + vectorSize));
    acc2 = Vector.Multiply(acc2, new Vector<double>(array, i + vectorSize * 2));
    acc3 = Vector.Multiply(acc3, new Vector<double>(array, i + vectorSize * 3));
    acc4 = Vector.Multiply(acc4, new Vector<double>(array, i + vectorSize * 4));
    acc5 = Vector.Multiply(acc5, new Vector<double>(array, i + vectorSize * 5));
    acc6 = Vector.Multiply(acc6, new Vector<double>(array, i + vectorSize * 6));
    acc7 = Vector.Multiply(acc7, new Vector<double>(array, i + vectorSize * 7));
}
acc0 = Vector.Multiply(acc0, acc1);
acc2 = Vector.Multiply(acc2, acc3);
acc4 = Vector.Multiply(acc4, acc5);
acc6 = Vector.Multiply(acc6, acc7);
acc0 = Vector.Multiply(acc0, acc2);
acc4 = Vector.Multiply(acc4, acc6);
acc0 = Vector.Multiply(acc0, acc4);
// from here on it's the same
var tempArray = new double[Vector<double>.Count];
acc0.CopyTo(tempArray);
var result = tempArray.Aggregate(1d, (p, d) => p * d);
for (; i < array.Length; i++)
    result *= array[i];

Это делает это последним l oop запустить потенциально в 8 раз больше, чем раньше, чего можно было бы избежать, если бы имелась дополнительная единичная векторная на одну итерацию l oop.

...