2013-11-23 2 views
4

Вот фрагмент кода для вычисления квадратного корня из значений в массиве поплавка, взятой из http://felix.abecassis.me/2011/09/cpp-getting-started-with-sse/Есть ли способ использовать все регистры XMM?

void sse(float* a, int N) 
{ 
    // We assume N % 4 == 0. 
    int nb_iters = N/4; 
    __m128* ptr = (__m128*)a; 


    for (int i = 0; i < nb_iters; ++i, ++ptr, a += 4){ 
     _mm_store_ps(a, _mm_sqrt_ps(*ptr)); 
    } 

} 

Когда я лукавить этот код, я вижу только один XMM (XMM0) используется. Я предположил, что разворачивание цикла даст компилятору сигнал, что можно использовать больше xmms. Я изменил код как

void sse3(float* a, int N) 
{ 
__m128* ptr = (__m128*)a; 

for (int i = 0; i < N; i+=32){ 

    _mm_store_ps(a + i, _mm_sqrt_ps(*ptr)); 
    ptr++; 
    _mm_store_ps(a + i + 4, _mm_sqrt_ps(*ptr)); 
    ptr++; 
    _mm_store_ps(a + i + 8, _mm_sqrt_ps(*ptr)); 
    ptr++; 
    _mm_store_ps(a + i + 12, _mm_sqrt_ps(*ptr)); 
    ptr++; 
    _mm_store_ps(a + i + 16, _mm_sqrt_ps(*ptr)); 
    ptr++; 
    _mm_store_ps(a + i + 20, _mm_sqrt_ps(*ptr)); 
    ptr++; 
    _mm_store_ps(a + i + 24, _mm_sqrt_ps(*ptr)); 
    ptr++; 
    _mm_store_ps(a + i + 28, _mm_sqrt_ps(*ptr)); 
    ptr++; 
} 
} 

N должно быть больше 32 в этом случае. Однако я до сих пор не вижу более одного хмм. Почему компилятор не может назначить более одного xmm?

Мое понимание - это вычисления на xmm0, xmm1, xmm2 ... xmm7 являются независимыми и могут идти параллельно на современных суперскалярных архитектурах. На 4-сторонней суперскалярной машине второй фрагмент кода должен дать мне теоретическое ускорение 4 (если, например, назначено 4 разных xmms).

PS: Второй фрагмент кода выглядит немного быстрее (последовательно).

sse3: 18809 microseconds 
sse: 20543 microseconds 

ОБНОВЛЕНО

Используется -O3 флаг, как предложил

Вот Демонтажные для ответа Бен Фойгта - Обратите внимание, что я изменил имя функции для SSE4.

147:ssetest.cpp **** void sse4(float* a, int N) 
148:ssetest.cpp **** { 
2076     .loc 8 148 0 
2077     .cfi_startproc 
2078    .LVL173: 
2079    .LBB5900: 
2080    .LBB5901: 
149:ssetest.cpp **** __m128 b, c, d, e; 
150:ssetest.cpp **** 
151:ssetest.cpp **** for (int i = 0; i < N; i += 16) { 
2081     .loc 8 151 0 
2082 0320 85F6   testl %esi, %esi # N 
2083 0322 7E4C   jle .L106 #, 
147:ssetest.cpp **** void sse4(float* a, int N) 
2084     .loc 8 147 0 
2085 0324 8D56FF  leal -1(%rsi), %edx #, tmp104 
2086    .LBE5901: 
2087    .LBE5900: 
2088 0327 31C0   xorl %eax, %eax # ivtmp.1046 
2089    .LBB5925: 
2090    .LBB5924: 
2091 0329 C1EA04  shrl $4, %edx #, 
2092 032c 4883C201  addq $1, %rdx #, D.189746 
2093 0330 48C1E206  salq $6, %rdx #, D.189746 
2094    .LVL174: 
2095     .p2align 4,,10 
2096 0334 0F1F4000  .p2align 3 
2097    .L108: 
2098    .LBB5902: 
2099    .LBB5903: 
899:/usr/lib/gcc/x86_64-linux-gnu/4.6/include/xmmintrin.h **** return (__m128) *(__v4sf *)__P; 
2100     .loc 9 899 0 discriminator 2 
2101 0338 0F285407  movaps 16(%rdi,%rax), %xmm2 # MEM[base: a_7(D), index: ivtmp.1046_85, offset: 16B], c 
2101  10 
2102    .LVL175: 
2103    .LBE5903: 
2104    .LBE5902: 
2105    .LBB5904: 
2106    .LBB5905: 
182:/usr/lib/gcc/x86_64-linux-gnu/4.6/include/xmmintrin.h **** return (__m128) __builtin_ia32_sqrtps ((__v4sf)__A); 
2107     .loc 9 182 0 discriminator 2 
2108 033d 0F511C07  sqrtps (%rdi,%rax), %xmm3 # MEM[base: a_7(D), index: ivtmp.1046_85, offset: 0B], tmp107 
2109    .LBE5905: 
2110    .LBE5904: 
2111    .LBB5906: 
2112    .LBB5907: 
899:/usr/lib/gcc/x86_64-linux-gnu/4.6/include/xmmintrin.h **** return (__m128) *(__v4sf *)__P; 
2113     .loc 9 899 0 discriminator 2 
2114 0341 0F284C07  movaps 32(%rdi,%rax), %xmm1 # MEM[base: a_7(D), index: ivtmp.1046_85, offset: 32B], d 
2114  20 
2115    .LVL176: 
2116    .LBE5907: 
2117    .LBE5906: 
2118    .LBB5908: 
2119    .LBB5909: 
182:/usr/lib/gcc/x86_64-linux-gnu/4.6/include/xmmintrin.h **** return (__m128) __builtin_ia32_sqrtps ((__v4sf)__A); 
2120     .loc 9 182 0 discriminator 2 
2121 0346 0F51D2  sqrtps %xmm2, %xmm2 # c, tmp109 
2122    .LBE5909: 
2123    .LBE5908: 
2124    .LBB5910: 
2125    .LBB5911: 
899:/usr/lib/gcc/x86_64-linux-gnu/4.6/include/xmmintrin.h **** return (__m128) *(__v4sf *)__P; 
2126     .loc 9 899 0 discriminator 2 
2127 0349 0F284407  movaps 48(%rdi,%rax), %xmm0 # MEM[base: a_7(D), index: ivtmp.1046_85, offset: 48B], e 
2127  30 
2128    .LVL177: 
2129    .LBE5911: 
2130    .LBE5910: 
2131    .LBB5912: 
2132    .LBB5913: 
182:/usr/lib/gcc/x86_64-linux-gnu/4.6/include/xmmintrin.h **** return (__m128) __builtin_ia32_sqrtps ((__v4sf)__A); 
2133     .loc 9 182 0 discriminator 2 
2134 034e 0F51C9  sqrtps %xmm1, %xmm1 # d, tmp111 
2135    .LBE5913: 
2136    .LBE5912: 
2137    .LBB5914: 
2138    .LBB5915: 
2139     .loc 9 948 0 discriminator 2 
2140 0351 0F291C07  movaps %xmm3, (%rdi,%rax) # tmp107, MEM[base: a_7(D), index: ivtmp.1046_85, offset: 0B] 
2141    .LVL178: 
2142    .LBE5915: 
2143    .LBE5914: 
2144    .LBB5916: 
2145    .LBB5917: 
182:/usr/lib/gcc/x86_64-linux-gnu/4.6/include/xmmintrin.h **** return (__m128) __builtin_ia32_sqrtps ((__v4sf)__A); 
2146     .loc 9 182 0 discriminator 2 
2147 0355 0F51C0  sqrtps %xmm0, %xmm0 # e, tmp113 
2148    .LBE5917: 
2149    .LBE5916: 
2150    .LBB5918: 
2151    .LBB5919: 
2152     .loc 9 948 0 discriminator 2 
2153 0358 0F295407  movaps %xmm2, 16(%rdi,%rax) # tmp109, MEM[base: a_7(D), index: ivtmp.1046_85, offset: 16B] 
2153  10 
2154    .LVL179: 
2155    .LBE5919: 
2156    .LBE5918: 
2157    .LBB5920: 
2158    .LBB5921: 
2159 035d 0F294C07  movaps %xmm1, 32(%rdi,%rax) # tmp111, MEM[base: a_7(D), index: ivtmp.1046_85, offset: 32B] 
2159  20 
2160    .LVL180: 
2161    .LBE5921: 
2162    .LBE5920: 
2163    .LBB5922: 
2164    .LBB5923: 
2165 0362 0F294407  movaps %xmm0, 48(%rdi,%rax) # tmp113, MEM[base: a_7(D), index: ivtmp.1046_85, offset: 48B] 
2165  30 
2166 0367 4883C040  addq $64, %rax #, ivtmp.1046 
2167    .LVL181: 
2168    .LBE5923: 
2169    .LBE5922: 
2170     .loc 8 151 0 discriminator 2 
2171 036b 4839D0  cmpq %rdx, %rax # D.189746, ivtmp.1046 
2172 036e 75C8   jne .L108 #, 
2173    .LVL182: 
2174    .L106: 
2175 0370 F3   rep 
2176 0371 C3   ret 
2177    .LBE5924: 
2178    .LBE5925: 
2179     .cfi_endproc 
2180    .LFE7998: 
2182 0372 66666666  .p2align 4,,15 
2182  662E0F1F 
2182  84000000 
2182  0000 
2183     .globl _Z6normalPfi 
2185    _Z6normalPfi: 
2186    .LFB7999: 
152:ssetest.cpp ****  b = _mm_load_ps(a + i); 
153:ssetest.cpp ****  c = _mm_load_ps(a + i + 4); 
154:ssetest.cpp ****  d = _mm_load_ps(a + i + 8); 
155:ssetest.cpp ****  e = _mm_load_ps(a + i + 12); 
156:ssetest.cpp ****  _mm_store_ps(a + i,  _mm_sqrt_ps(b)); 
157:ssetest.cpp ****  _mm_store_ps(a + i + 4, _mm_sqrt_ps(c)); 
158:ssetest.cpp ****  _mm_store_ps(a + i + 8, _mm_sqrt_ps(d)); 
159:ssetest.cpp ****  _mm_store_ps(a + i + 12, _mm_sqrt_ps(e)); 
160:ssetest.cpp **** } 
161:ssetest.cpp **** } 

Как ни странно, и SSE SSE4 имеют почти такую ​​же производительность и SSE3 выполняет худшее (хотя часть петли разматывается).

+1

Взгляните на [Регистрация переименования] (http://en.wikipedia.org/wiki/Register_renaming). Из-за этого компилятору просто не нужно использовать более одного регистра ... – Mysticial

+3

Согласно известным руководствам Anger Fog (http://www.agner.org/optimize/), для процессоров Wolfdale инструкция SQRTPS имеет задержку 6-13 циклов, 5-12 циклов взаимной пропускной способности и может использовать только один порт выполнения, p0. В переводе для вас может быть только одна инструкция SQRTPS, и ее можно запускать только каждые 5-12 циклов. Таким образом, существует нулевая возможность для одновременного выполнения инструкций SQRTPS, и они могут пройти долгий путь до конца по сравнению с добавлением или умножением. Компилятор не может выиграть, сбивая больше регистров здесь. –

+0

@Mysticial В собранном коде есть зависимость чтения-записи после записи, xmm0 всегда должен ждать загрузки из памяти после предыдущего хранилища в память. – Mathai

ответ

3

Как насчет:

void sse3(float* a, int N) 
{ 
    __m128 b, c, d, e; 

    for (int i = 0; i < N; i += 16) { 
     b = _mm_load_ps(a + i); 
     c = _mm_load_ps(a + i + 4); 
     d = _mm_load_ps(a + i + 8); 
     e = _mm_load_ps(a + i + 12); 
     _mm_store_ps(a + i,  _mm_sqrt_ps(b)); 
     _mm_store_ps(a + i + 4, _mm_sqrt_ps(c)); 
     _mm_store_ps(a + i + 8, _mm_sqrt_ps(d)); 
     _mm_store_ps(a + i + 12, _mm_sqrt_ps(e)); 
    } 
} 

NB: Используя тот же регистр XMM для нескольких вычислений является одна вещь, которая может убить конвейерную, но это не единственное. Операции с различными регистрами могут выполняться независимо, только если доступны другие ресурсы в достаточном количестве. Для каждого регистра нет целого блока вычислений SIMD, так как ваш комментарий подсказывает, что вы верите.

+0

Спасибо за ваш ответ. Я все еще вижу только xmm0 при разборке. В скорости нет разницы. – Mathai

+0

@Mathai: Можете ли вы опубликовать разборку? Я не очень удивлен, что скорость не изменилась, но для компилятора должно быть совсем иначе, чтобы использовать только один регистр XMM для всех четырех именованных переменных, учитывая, что их время жизни перекрывается. Много ли было переупорядочения инструкций? –

+0

. Я не уверен, что такое переупорядочение команд. – Mathai

2

Я думаю, что вы видите здесь компилятор, защищающий от псевдонимов. Вы читаете и пишете через указатели, поэтому компилятор должен предположить, что каждая запись в любой float * может быть изменена следующим образом: float *. Попробуйте использовать __restrict__ на ваших указателях, чтобы сообщить компилятору, что вы этого не делаете. Вы также можете переписать свой цикл для вычисления из одного массива (глобальный является идеальным, потому что тогда никакое наложение не может быть задействовано) в другое.

+0

Он на самом деле не читает 'float *' нигде. Тем не менее, избавление от псевдонимов между 'a' и' ptr' было бы неплохо. –

+0

@Ben Jackson, я не знаком с __restric__. Я могу изменить его на void sse3 (float * __restrict__ a, int N)? Я попробовал это, я вижу только xmm0 при разборке. – Mathai

+0

GCC, похоже, не заботится об псевдониме между 'float' и' __m128', как отметил @BenVoigt. Я получаю несколько регистров с '-funroll-all-loops' (он даже имеет дело со всеми случаями, когда' nb_iters' не кратно разворачивается.) –

Смежные вопросы