Существует ли более быстрый способ умножения элемента результирующего вектора (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.