SIMD-код против скалярного кода - PullRequest
0 голосов
/ 09 декабря 2010

Следующий цикл выполняется сотни раз.
elma and elmc are both unsigned long (64-bit) arrays, so is res1 and res2. </p> <pre><code>unsigned long simdstore[2]; __m128i *p, simda, simdb, simdc; p = (__m128i *) simdstore; for (i = 0; i < _polylen; i++) { u1 = (elma[i] >> l) & 15; u2 = (elmc[i] >> l) & 15; for (k = 0; k < 20; k++) { 1. //res1[i + k] ^= _mulpre1[u1][k]; 2. //res2[i + k] ^= _mulpre2[u2][k]; 3. _mm_prefetch ((const void *) &_mulpre2[u2][k], _MM_HINT_T0); 4. _mm_prefetch ((const void *) &_mulpre1[u1][k], _MM_HINT_T0); 5. simda = _mm_set_epi64x (_mulpre2[u2][k], _mulpre1[u1][k]); 6. _mm_prefetch ((const void *) &res2[i + k], _MM_HINT_T0); 7. _mm_prefetch ((const void *) &res1[i + k], _MM_HINT_T0); 8. simdb = _mm_set_epi64x (res2[i + k], res1[i + k]); 9. simdc = _mm_xor_si128 (simda, simdb); 10. _mm_store_si128 (p, simdc); 11. res1[i + k] = simdstore[0]; 12. res2[i + k] = simdstore[1]; } }

В цикле for скалярная версия кода (с комментариями) выполняется в два раза быстрее, чемкод simd.С выходом cachegrind (чтение инструкций) указанных строк упоминается ниже.

Строка 1: 668,460,000 2 2
Строка 2: 668,460,000 1 1
Строка 3: 89,985,000 1 1
Строка 4: 89,985,000 1 1
Строка 5: 617,040,000 2 2
Строка 6: 44,992,500 0 0
Строка 7: 44,992,500 0 0
Строка 8: 539,910,000 1 1
Строка 9: 128,550,000 0 0
Строка 10:.,.
Строка 11: 205,680,000 0 0
Строка 12: 205,680,000 0 0

Из приведенного выше рисунка видно, что для прокомментированного (скалярного кода) требуется значительно меньшее количество инструкций, чем для кода simd.

Как этот код можно сделать быстрее?

Ответы [ 2 ]

3 голосов
/ 15 декабря 2010

Уберите _mm_prefetch встроенные функции - они ничего не достигают в этом контексте и могут даже снизить производительность.Предварительная выборка полезна только в том случае, если (а) у вас есть запасная пропускная способность и (б) вы можете выдать подсказку предварительной выборки за несколько сотен тактов до того, как данные действительно потребуются.Я думаю, что ни (a), ни (b) не соответствуют действительности в вашем случае.

1 голос
/ 04 февраля 2011

Ваша проблема в производительности:

_mm_set_epi64x (_mulpre2 [u2] [k], _mulpre1 [u1] [k]);

Класс встроенных функций mm_set (a, b, c, d) очень медленный. Только встроенные функции одного набора параметров (он же широковещательный) являются быстрыми.

Я посмотрел, что они делают в ассемблерном коде.

Они в основном создают массив в стеке, перемещают два целых числа из многомерных массивов, в которых они сейчас находятся, в массив стека, используя обычные перемещения памяти (mov DWORD). Затем из массива стека с помощью перемещения памяти XMM (mov XMWORD).

Скалярная версия идет напрямую из памяти в регистры. БЫСТРЕЕ!

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

Если есть способ переместить 64-битные значения непосредственно в нормальный регистр или из него в регистр XMM, я все еще его ищу.

Чтобы получить повышение скорости от использования регистров SSE / XMM, ваши данные, вероятно, должны быть уже в порядке в памяти. Загрузка данных из заказа в регистр XMM стоит того, только если вы можете выполнить несколько операций XMM за загрузку вне заказа. Здесь вы выполняете одну операцию XOR.

...