Nicolás Wolovick 20140401
Single Instruction Multiple Data
Nos vamos a concentrar en una versión particular.
SSSE3
c = a*b
1 #define N (1<<25)
2 float a[N], b[N], c[N];
3 int main(void) {
4 for (unsigned int i=0; i<N; ++i) {
5 a[i] = b[i]*c[i];
6 }
7 }
Compilamos y ejecutamos.
1 $ gcc -std=c99 -O1 multmap.c && perf stat -e cycles,instructions,cache-references,cache-misses -r 3 ./a.out
2
3 Performance counter stats for './a.out' (3 runs):
4
5 454.302.503 cycles # 0,000 GHz ( +- 0,48% ) [49,18%]
6 323.757.709 instructions # 0,71 insns per cycle ( +- 0,20% ) [74,60%]
7 884.318 cache-references ( +- 2,18% ) [75,85%]
8 361.578 cache-misses # 40,888 % of all cache refs ( +- 1,73% ) [75,78%]
9
10 0,173437347 seconds time elapsed ( +- 0,28% )
Tiene un paralelismo trivial de grano fino. ¡Qué lo descubra el compilaror!
1 $ gcc -std=c99 -O1 -ftree-vectorize -ftree-vectorizer-verbose=2 multmap.c && perf stat -e cycles,instructions,cache-references,cache-misses -r 3 ./a.out
2
3 Analyzing loop at multmap.c:6
4
5
6 Vectorizing loop at multmap.c:6
7
8 6: LOOP VECTORIZED.
9 multmap.c:5: note: vectorized 1 loops in function.
10
11 Performance counter stats for './a.out' (3 runs):
12
13 400.026.689 cycles # 0,000 GHz ( +- 1,61% ) [49,17%]
14 169.915.260 instructions # 0,42 insns per cycle ( +- 1,21% ) [75,36%]
15 918.045 cache-references ( +- 0,72% ) [75,66%]
16 377.502 cache-misses # 41,120 % of all cache refs ( +- 0,43% ) [75,71%]
17
18 0,153767320 seconds time elapsed ( +- 0,91% )
-ftree-vectorizer-verbose=2
para que informe si pudo vectorizar.-O3
incluye vectorización, pero también muchas más cosas que dificultan la lectura del assembler.Código absolutamente memory-bound con intensidad aritmética de 1 FLOP/ 8 bytes.
Aun asi, mejora "leer ancho".
Mostafa Hagog, Looking for 4x speedups? SSE™ to the rescue!, Intel, 2006.
gcc -std=c99 -S -O1 multmap.c
1 .L2:
2 movss b(%rax), %xmm0
3 mulss c(%rax), %xmm0
4 movss %xmm0, a(%rax)
5 addq $4, %rax
6 cmpq $134217728, %rax
7 jne .L2
gcc -std=c99 -S -O1 -ftree-vectorize multmap.c
1 .L2:
2 movaps b(%rax), %xmm0
3 mulps c(%rax), %xmm0
4 movaps %xmm0, a(%rax)
5 addq $16, %rax
6 cmpq $134217728, %rax
7 jne .L2
Notar:
%rax
va 4 veces más rápido (repite lazo 4x menos).ps
: packed single.xmm{0..15}
__m128
: 4 flotantes __m128d
: 2 dobles.__m128i
: 16 bytes, 8 shorts, 4 ints, 2 longs.Notar: __m128 v
se puede acceder como si fuera float v[4]
.
1 #include <mmintrin.h> // MMX
2 #include <xmmintrin.h> // SSE
3 #include <emmintrin.h> // SSE2
4 #include <pmmintrin.h> // SSE3
5 #include <tmmintrin.h> // SSSE3
6 #include <smmintrin.h> // SSE4.1
7 #include <nmmintrin.h> // SSE4.2
8 #include <ammintrin.h> // SSE4A
9 #include <wmmintrin.h> // AES
10 #include <immintrin.h> // AVX
ó
1 #include <x86intrin.h> // el que sea necesario
Copiar un valor a las 4 componentes.
1 __m128 _mm_set1_ps (float a)
Establece las 4 componentes.
1 __m128 _mm_setr_ps (float e3, float e2, float e1, float e0)
1 __m128 _mm_loadl_pi (__m128 a, __m64 const* mem_addr) // movlps
2 void _mm_store_ps (float* mem_addr, __m128 a) // movaps
Más
1 __m128 _mm_loadh_pi (__m128 a, __m64 const* mem_addr) // movhps
2 __m128d _mm_loadh_pd (__m128d a, double const* mem_addr) // movhpd
3 void _mm_store_pd (double* mem_addr, __m128d a) // movapd
Este intrinsic se mapea a una instrucción movaps
.
En este caso tenemos dos: movss + shufps
.
1 __m128 _mm_movelh_ps (__m128 a, __m128 b) // movlhps
1 __m128 _mm_unpacklo_ps (__m128 a, __m128 b) // unpcklps
También está _mm_movehl_ps
(movhlps
).
1 __m128 _mm_shuffle_ps (__m128 a, __m128 b, unsigned int imm) // shufps
Notar que NO se puede hacer unpcklps
, unpckhps
con shufps
.
(Franz Franchetti and Markus Püschel, Generating SIMD Vectorized Permutations, 2008.)
Escalares vs. Empacketadas.
1 __m128 _mm_add_ps (__m128 a, __m128 b) // addps
2 __m128 _mm_sub_ps (__m128 a, __m128 b) // subps
3 __m128 _mm_mul_ps (__m128 a, __m128 b) // mulps
4 __m128 _mm_min_ps (__m128 a, __m128 b) // minps
5 __m128 _mm_max_ps (__m128 a, __m128 b) // maxps
Todas tienen un throughput de 1 ciclo.
1 __m128 _mm_div_ps (__m128 a, __m128 b) // divps
2 __m128 _mm_rcp_ps (__m128 a) // rcpps
3 __m128 _mm_sqrt_ps (__m128 a) // sqrtps
4 __m128 _mm_rsqrt_ps (__m128 a) // rsqrtps
divps
tiene throughput de 14 ciclos en Sandy Bridge, mulps
y rcpps
de 1.sqrtps
tiene throughput de 14 ciclos en Sandy Bridge, rsqrtps
de 1.1 __m128 _mm_cmpeq_ps (__m128 a, __m128 b) // cmpps
2 __m128 _mm_cmpneq_ps (__m128 a, __m128 b) // cmpps
3 __m128 _mm_cmplt_ps (__m128 a, __m128 b) // cmpps
4 __m128 _mm_cmpnlt_ps (__m128 a, __m128 b) // cmpps
1 __m128 _mm_and_ps (__m128 a, __m128 b) // andps
2 __m128 _mm_or_ps (__m128 a, __m128 b) // orps
3 __m128 _mm_xor_ps (__m128 a, __m128 b) // xorps
4 __m128 _mm_andnot_ps (__m128 a, __m128 b) // andnps
1 int _mm_cvtss_si32 (__m128 a) // cvtss2si
2 __m128 _mm_cvtsi32_ss (__m128 a, int b) // cvtsi2ss
3 __m64 _mm_cvtps_pi32 (__m128 a) // cvtps2pi
4 __m128 _mm_cvtpi32_ps (__m128 a, __m64 b) // cvtpi2ps
Y otras cosas sencillas como tomar el primer elemento y devolver un float
.
1 float _mm_cvtss_f32 (__m128 a) // movss
1 void _mm_prefetch (char const* p, int i) // prefetchnta, prefetcht0, prefetcht1, prefetcht2
1 void _mm_lfence (void) // lfence
2 void _mm_sfence (void) // sfence
3 void _mm_mfence (void) // mfence
1 __m128 _mm_addsub_ps (__m128 a, __m128 b)
Input: { A0, A1, A2, A3 }, { B0, B1, B2, B3 }
Output: { A0 - B0, A1 + B1, A2 - B2, A3 + B3 }
1 __m128 _mm_hsub_ps (__m128 a, __m128 b)
Input: { A0, A1, A2, A3 }, { B0, B1, B2, B3 }
Output: { A0 - A1, A2 - A3, B0 - B1, B2 - B3 }
1 __m128 _mm_hadd_ps (__m128 a, __m128 b)
Input: { A0, A1, A2, A3 }, { B0, B1, B2, B3 }
Output: { A0 + A1, A2 + A3, B0 + B1, B2 + B3 }
¿Cómo sumo las 4 compontentes de un __m128
?
1 _MM_TRANSPOSE4_PS (__m128 row0, __m128 row1, __m128 row2, __m128 row3)
Necesita 8 instrucciones y 4 registros.
1 _MM_SHUFFLE(z, y, x, w)
2 // expands to the following value (z<<6) | (y<<4) | (x<<2) | w
Broadcast el elemento 1 del vector.
1 _mm_shuffle_ps(v, v, _MM_SHUFFLE(1,1,1,1))
1 __m128 _mm_blend_ps (__m128 a, __m128 b, const int imm) // blendps
1 for (i=0; i<N; ++i) {
2 if (a[i]<b[i])
3 c[i]=a[i]*b[i];
4 else
5 c[i]=a[i];
6 }
blend
1 for (i=0; i<N; i+=4){
2 A = _mm_load_ps(&a[i]);
3 B = _mm_load_ps(&b[i]);
4 C = _mm_mul_ps (A, B);
5 mask = _mm_cmplt_ps (A, B);
6 C = _mm_blend_ps (C, A, mask);
7 _mm_store_ps (&c[i], C);
8 }
1 __m128 _mm_dp_ps (__m128 a, __m128 b, const int imm) // dpps
¿Cómo se hace un producto punto en SSE3?
1 __m128 _mm_insert_ps (__m128 a, __m128 b, const int imm) // insertps
2 int _mm_extract_ps (__m128 a, const int imm) // extractps
Escribe/lee elementos individualmente.
Anterior a SSE4.1, muchas instrucciones de shuffling.
Recién en AVX2 hay gatherps
, storeu2
, reales.
(el Talón de Aquiles de todo esto)
multmap
a manoEl código armado a mano con intrinsics de "C"
.
1 #include <xmmintrin.h>
2 #define N (1<<25)
3 float a[N], b[N], c[N];
4
5 void main(void) {
6 for (unsigned int i=0; i<N; i+=4) {
7 __m128 ta = _mm_load_ps(&a[i]);
8 __m128 tb = _mm_load_ps(&b[i]);
9 __m128 tc = _mm_mul_ps(ta, tb);
10 _mm_store_ps(&c[i], tc);
11 }
12 }
Podríamos haber hecho un one-liner con los operadores prefijos en vez de infijos.
1 for (unsigned int i=0; i<N; i+=4)
2 _mm_store_ps(&c[i], _mm_mul_ps(_mm_load_ps(&a[i]), _mm_load_ps(&b[i])));
Pero quiero ser como René Lavard:
"no se puede hacer más lento"
1 $ gcc -std=c99 -O1 multmap_quadload.c && perf stat -e cycles,instructions,cache-references,cache-misses -r 3 ./a.out
2
3 Performance counter stats for './a.out' (3 runs):
4
5 408.215.203 cycles # 0,000 GHz ( +- 5,94% ) [49,80%]
6 190.947.933 instructions # 0,47 insns per cycle ( +- 3,37% ) [75,25%]
7 905.136 cache-references ( +- 4,67% ) [76,07%]
8 348.820 cache-misses # 38,538 % of all cache refs ( +- 1,84% ) [74,87%]
9
10 0,156784760 seconds time elapsed ( +- 5,28% )
Funciona igual que el vectorizador.
1 .L2:
2 movl %eax, %edx
3 movaps a(,%rdx,4), %xmm0
4 mulps b(,%rdx,4), %xmm0
5 movaps %xmm0, c(,%rdx,4)
6 addl $4, %eax
7 cmpl $33554432, %eax
8 jne .L2
Table of Contents | t |
---|---|
Exposé | ESC |
Full screen slides | e |
Presenter View | p |
Source Files | s |
Slide Numbers | n |
Toggle screen blanking | b |
Show/hide slide context | c |
Notes | 2 |
Help | h |