Войти
ПрограммированиеФорумОбщее

SIMD оптимизации (26 стр)

Страницы: 122 23 24 25 26 27 Следующая »
#375
15:40, 10 фев. 2019

> Оно имеет смысл, если вся единица трансляции (или вообще - вся программа) скомпилирована с -msse2
Причём в этом случае, GCC приходится следить, можно ли функцию вызвать по указателю, и он реально это делает.
https://godbolt.org/z/YkF9zl
Из интереса - можно заменить p=g; на p=f;, или тело g на return x+y; и посмотреть, как меняется код.


#376
22:42, 7 апр. 2019

Загадка: есть лямбда. Как в ней прописать __attribute__((target("sse2")))?
И нет, лямбда не использует target от функции, в которой она определена (по крайней мере в GCC).

void __attribute__((target("sse2"))) render_mesh(int n,const Triangle *triangles)
{
    for(int i=0;i<n;++i)
        render_triangle(triangles[i],
        [](int x,int y,float bary0,float bary1,float bary2)->void // "pixel shader".
        {
            (void)bary0;(void)bary1;(void)bary2;
            colorbuffer[y*WIDTH+x]=0xFFFFFFFFu;
        })
}
Ответ:
+ Показать

Подсказка: GCC'шные __attribute__(()) синтаксически размещаются там же, где и стандартные [[attribute]], положение которых можно посмотреть в документации.

Прошло более 8 месяцев
#377
11:59, 13 дек. 2019

Можно ли как-то переложить эту рекуррентную функцию на SIMD?

void filter(float y[], const float x[], int N, float a, float da, float yprev)
{
    float b = 1-a;
    for(int i = 0; i < N; i++)
    {
        y[i] = a*x[i] + b*yprev;
        a += da;
        b -= da;
        yprev = y[i];
    }
}
Выравнивание можно не проверять и хвосты обрабатывать не нужно. Можно предположить, что N делится на 16 и оба массива выровнены на 16 float.

#378
(Правка: 12:53) 12:40, 13 дек. 2019

a и b тут допустим можно разложить. Подготовить a*x(i), а вот b*yprev придется линейно считать.

+ Показать
#379
14:13, 13 дек. 2019

foxes
> а вот b*yprev придется линейно считать.
Смущает, что у нас до линейного вычисления данные в векторных регистрах лежат, то есть в векторном виде это будет выглядеть как-то так:

float4 Y = Load4<float4>(x+i);
Y *= A;
Y += B*float4{Yprev[3], 0, 0, 0};
Y += B*float4{0, Y[0], 0, 0};
Y += B*float4{0, 0, Y[1], 0};
Y += B*float4{0, 0, 0, Y[2]};
Store(Y, y+i);
Yprev = Y;
A += dA;
B -= dA;
Как в SSE/AVX/NEON реализуется операция, чтобы взять один элемент вектора и поместить его в определённую позицию результата, оставив остальные элементы нулевыми?
И ещё тут же получается нехилая data dependency. Latency при этом не убьёт весь выигрыш от SSE? Или процессор сможет параллельно выполнять несколько итераций цикла?
#380
(Правка: 14:27) 14:20, 13 дек. 2019

gammaker
> Смущает, что у нас до линейного вычисления данные в векторных регистрах лежат,
> то есть в векторном виде это будет выглядеть как-то так:
Есть доступ к первому элементу, через это с помощью циклического сдвига можно достучаться ко всем. Параллельно итерации цикла в любом случае не выполняться из-за зависимости y(i) = ... +b*y(i-1). Максимум чуть прироста за счет остальных операций и чтения, записи.
"B" тут лучше не представлять в виде вектора, это ни чего не даст.

#381
16:41, 13 дек. 2019

foxes
> Есть доступ к первому элементу, через это с помощью циклического сдвига можно
> достучаться ко всем.
То есть тут будет куча шаффлов и процессор их всех будет ждать по цепочке?

>Параллельно итерации цикла в любом случае не выполняться из-за зависимости y(i) = ... +b*y(i-1).
Да, точно, я тупанул.

>Максимум чуть прироста за счет остальных операций и чтения, записи.
Если хоть немного прироста, хорошо, потому что у меня само чтение и запись может происходить сложнее - через интерполяцию, которую уже тут разбирали и векторизировали. Так что суммарно прирост может неплохой получится.
Но вообще есть сомнения, asm код векторной версии (vector2) гораздо длиннее скалярной версии (scalar2), которая делает то же самое. Шаффлы настолько дешевле инструкций чтения из памяти и записи в неё, что смогут повысить производительность?

foxes
> "B" тут лучше не представлять в виде вектора, это ни чего не даст.
Почему? Я же заменил четыре "b -= da" на один "B -= dA".

#382
(Правка: 17:04) 16:54, 13 дек. 2019

gammaker
> Почему? Я же заменил четыре "b -= da" на один "B -= dA".
Это перемешается с другими операциями на конвейере, поэтому смысла мало и неудобства дополнительные. Так ты сможешь с ним как с синглом работать ss. Для y(i) = ... +b*y(i-1) он все равно как сингл требуется. И в vector3 у тебя все таки не память а регистры.

#383
17:11, 13 дек. 2019

foxes
Ладно, короче надо будет сегодня вечером или на выходных сделать бенчмарк и сравнить на цифрах.

#384
(Правка: 18:06) 17:42, 13 дек. 2019

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

Если написать простую функцию

return float4{Y[1],Y[2],Y[3],Y[0]};
То результат ожидаемый
shufps  xmm0, xmm0, 57

Если сделать первую часть

Y[0] += b*Yprev;
return Y;
Тут тоже все в порядке
mulss   xmm1, dword ptr [rdi]
addss   xmm0, xmm1

Но стоит их объединить, то лезет лишнее

mulss   xmm1, dword ptr [rdi]
addss   xmm1, xmm0
shufps  xmm1, xmm0, 112
shufps  xmm0, xmm1, 41
А должно быть
mulss   xmm1, dword ptr [rdi]
addss   xmm0, xmm1
shufps  xmm0, xmm0, 57
По сути это одна из четырех строчек, которая должна просто повторяться и другого быть (лишнего) не должно. Как видишь тут получается циклический сдвиг операндов одной командой shufps и такое как unpcklps более чем излишне.

#385
23:03, 13 дек. 2019

Задача близка задаче нахождения всех частичных сумм ряда, отличия только в коэффициентах.
Соответственно, параллелится она стандартным образом — через логарифмическую редукцию:

z0[i] = (a0 + da * i) * x[i];
b0[i] = (1 - a0 - da * i);
z1[i] = z0[i] + b0[i] * z0[i - 1];
b1[i] = b0[i] * b0[i - 1];
z2[i] = z1[i] + b1[i] * z1[i - 2];
b2[i] = b1[i] * b1[i - 2];
z3[i] = z2[i] + b2[i] * z2[i - 4];
b3[i] = b2[i] * b2[i - 4];
z4[i] = z3[i] + b3[i] * z3[i - 8];
...
Это дерево вычислений надо аккуратно прервать на подходящем уровне и переключиться на последовательную версию и получится финальный алгоритм.

#386
12:41, 14 дек. 2019

foxes
> Я повнимательней посмотрел на генерируемый код, ручками его можно сократить в
> два раза. Очень много лишнего кода на пустом месте получается.
А я надеялся, компиляторы уже стали умными и не делают такой фигни. Асм писать я точно не хочу, я хотел, чтобы всё автоматически под нужную платформу генерировалось. Надеюсь, хоть как-то через интринсики можно будет помочь компилятору, не скатываясь в асм.

}:+()___ [Smile]
> Соответственно, параллелится она стандартным образом — через логарифмическую редукцию:
Какой-то незнакомый для меня термин и не гуглится ничего. По коду не могу понять закономерность и что такое z0 - z4? И что в итоге записывать в y(i)?

}:+()___ [Smile]
> Это дерево вычислений надо аккуратно прервать на подходящем уровне и
> переключиться на последовательную версию и получится финальный алгоритм.
Вот допустим как это будет выглядеть для SSE, то есть для четырёхмерных векторов?

#387
18:38, 14 дек. 2019

foxes
> Но стоит их объединить, то лезет лишнее
Если не пытаться комбинацию этих двух функций вручную инлайнить, то не лезет.

#388
21:13, 14 дек. 2019

gammaker
> Какой-то незнакомый для меня термин и не гуглится ничего.
Это я от балды сочинил, искать надо расчет частичных сумм — он используется, например, в сортировке подсчетом на GPU.

> По коду не могу понять закономерность и что такое z0 - z4? И что в итоге записывать в y(i)?
zn — это приближение к твоему результату, в y[​i] должно быть z∞[​i].

> Вот допустим как это будет выглядеть для SSE, то есть для четырёхмерных векторов?

a[0:3] = aprev + {0, 1, 2, 3} * da;
z0[0:3] = a[0:3] * x[0:3];
b0[0:3] = 1 - a[0:3];
z1[0:3] = z0[0:3] + b0[0:3] * {yprev, z0[0], z0[1], z0[2]};
b1[0:3] = b0[0:3] * {0, b0[0], b0[1], b0[2]}; // 3 ненулевых компоненты
z2[0:3] = z1[0:3] + b1[0:3] * {0, yprev, z1[0], z1[1]};  // вместо 0 можно взять yprev
b2[0:3] = b1[0:3] * {0, 0, b1[0], b1[1]}; // 1 ненулевая компонента
z3[0:3] = z2[0:3] + b2[0:3] * {0, 0, 0, yprev};  // вместо 0 можно взять yprev
y[0:3] = z3[0:3];
Но не уверен, что для таких мелких векторов будет эффективно.
В логарифмических алгоритмах все ускорение идет за счет поздних стадий.
Возможно, даже на SSE имеет смысл работать с 8-векторами.

#389
2:43, 15 дек. 2019

Есть такое:
http://www.reanimator.ltd.uk/articles/simdiir
https://software.intel.com/sites/default/files/m/d/4/1/d/8/Intel_… t_Data_WP.pdf
Но там коэффициенты не меняются (da=0).
Если меняются - матрицу, наверно, можно обновлять, непонятно, насколько это дорого.

Страницы: 122 23 24 25 26 27 Следующая »
ПрограммированиеФорумОбщее