numactl
, taskset
.mendieta
.GOMP_CPU_AFFINITY
(GNU), MP_BIND
y MP_BLIST
(PGI), PSC_OMP_AFFINITY_MAP
(EKOPath), KMP_AFFINITY
(Intel).Nicolás Wolovick 20160505
1 PROGRAM EASYPARALLEL
2
3 PARAMETER (N = 2**26)
4 REAL A(N)
5 REAL B(N)
6 DO I = 1,N
7 B(I) = I*3.14159 + N - I
8 ENDDO
9
10 DO I = 1,N
11 A(I) = B(I) * 3.14159
12 ENDDO
13
14 END
Probamos
1 $ gfortran -O3 -floop-parallelize-all -ftree-parallelize-loops=2 hpc_p123.f && perf stat -r 5 -e task-clock ./a.out
2 Performance counter stats for './a.out' (5 runs):
3 325.545034 task-clock (msec) # 1.785 CPUs utilized ( +- 0.51% )
4 0.182329860 seconds time elapsed ( +- 1.27% )
-O1
no funciona1 $ gfortran -O1 -floop-parallelize-all -ftree-parallelize-loops=2 hpc_p123.f && perf stat -r 5 -e task-clock ./a.out
2 Performance counter stats for './a.out' (5 runs):
3 322.723723 task-clock (msec) # 0.988 CPUs utilized ( +- 1.63% )
4 0.326674686 seconds time elapsed ( +- 1.46% )
gfortran-4.9
se queja la comadreja por Graphite1 $ gfortran-4.9 -O3 -floop-parallelize-all -ftree-parallelize-loops=2 hpc_p123.f && perf stat -r 5 -e task-clock ./a.out
2 f951: sorry, unimplemented: Graphite loop optimizations cannot be used (-fgraphite, -fgraphite-identity, -floop-block, -floop-interchange, -floop-strip-mine, -floop-parallelize-all, and -ftree-loop-linear)
1 main:
2 .LFB1:
3 .cfi_startproc
4 subq $24, %rsp
5 .cfi_def_cfa_offset 32
6 call _gfortran_set_args
7 movl $options.0.3397, %esi
8 movl $9, %edi
9 call _gfortran_set_options
10 movq %rsp, %rsi
11 xorl %ecx, %ecx
12 movl $2, %edx
13 movl $main._loopfn.0, %edi
14 movq $b.3384, (%rsp)
15 call GOMP_parallel
16 ...
1 main._loopfn.0:
2 pushq %rbp
3 pushq %rbx
4 subq $8, %rsp
5 movq (%rdi), %rbx
6 call omp_get_num_threads
7 movslq %eax, %rbp
8 call omp_get_thread_num
9 xorl %edx, %edx
10 movslq %eax, %rcx
11 movl $67108863, %eax
12 divq %rbp
13 cmpq %rdx, %rcx
14 ...
1 .L9:
2 pxor %xmm1, %xmm1
3 leal 1(%rdx), %eax
4 cvtsi2ss %eax, %xmm1
5 movaps %xmm1, %xmm0
6 mulss %xmm3, %xmm0
7 addss %xmm2, %xmm0
8 subss %xmm1, %xmm0
9 movss %xmm0, (%rbx,%rdx,4)
10 addq $1, %rdx
11 cmpq %rdx, %rcx
12 jne .L9
13 ...
1 float a[N][N], b[N], c[N];
2
3 int main(void) {
4 size_t i = 0, j = 0;
5 double start = 0.0;
6
7 start = omp_get_wtime();
8 for (i=0; i<N; ++i)
9 for (j=0; j<N; ++j)
10 c[i] += a[i][j]*b[j];
11 printf("%f ", ((long)N*N*3*sizeof(float))/((1<<30)*(omp_get_wtime()-start)));
12
13 return 0;
14 }
-ffast-math
: para poder reordenar.-ftree-vectorize
para vectorizar.-ftree-parallelize-loops=n
, donde n
es la cantidad de hilos.¡¡¡Tengo autovectorización y autoparalelización!!!!
(esto en gcc-4.9
no andaba, o tenía uno o tenía el otro)
mendieta
1 $ gcc -O3 -ftree-parallelize-loops=16 sgemv.c && perf stat -r 5 -e task-clock ./a.out
2 61.421345 61.059372 60.920014 60.466478 61.158156
3 Performance counter stats for './a.out' (5 runs):
4
5 767,697361 task-clock # 14,689 CPUs utilized ( +- 0,76% )
6
7 0,052262059 seconds time elapsed ( +- 0,31% )
En OpenMP era un one-liner, que lo pude evitar sin problema.
Notar que llega a 60 GiB/s, casi el tope del ancho de banda de estas máquinas.
(bueno en realidad no, son dos pastillas E5-2680, y cada una tiene 51.2 GiB/s)
Mirar el reporte de autoparalelización con -fdump-tree-parloops-all
.
gcc -fdump-tree-all -fcheck-data-deps -fdump-tree-ckdd-all -O3 filename.c
gcc -fdump-tree-all -fdump-tree-vect-all -msse4 -O3 filename.c
gcc -fdump-tree-all -ftree-parallelize-loops=x -fdump-tree-parloops-all -O3 filename.c
gcc -fdump-tree-all -ftree-parallelize-loops=x -fdump-tree-parloops-all -floop-parallelize-all -O2 filename.c
gcc -fdump-tree-all -floop-interchange -fdump-tree-graphite-all -O3 filename.c
Sacado de PARALLELIZATION AND VECTORIZATION IN GCC.
Se puede deshabilitar el interleave.
http://frankdenneman.nl/2010/12/node-interleaving-enable-or-disable/
Se puede habilitar el interleave.
http://frankdenneman.nl/2010/12/node-interleaving-enable-or-disable/
lstopo -p --no-io --no-bridges --no-caches --of png > mendieta-lstopo.png
1 $ numactl --hardware
2 available: 2 nodes (0-1)
3 node 0 cpus: 0 1 2 3 4 5 6 7
4 node 0 size: 16355 MB
5 node 0 free: 15254 MB
6 node 1 cpus: 8 9 10 11 12 13 14 15
7 node 1 size: 16384 MB
8 node 1 free: 15805 MB
9 node distances:
10 node 0 1
11 0: 10 21
12 1: 21 10
Notar como se informa de la memoria disponible por nodo.
1 $ numactl --hardware
2 available: 8 nodes (0-7)
3 node 0 cpus: 0 1 2 3 4 5 6 7
4 node 0 size: 32765 MB
5 node 0 free: 2833 MB
6 node 1 cpus: 8 9 10 11 12 13 14 15
7 node 1 size: 32768 MB
8 node 1 free: 20462 MB
9 node 2 cpus: 16 17 18 19 20 21 22 23
10 node 2 size: 32768 MB
11 node 2 free: 25181 MB
12 node 3 cpus: 24 25 26 27 28 29 30 31
13 node 3 size: 32768 MB
14 node 3 free: 22888 MB
15 node 4 cpus: 32 33 34 35 36 37 38 39
16 node 4 size: 32768 MB
17 node 4 free: 24988 MB
18 node 5 cpus: 40 41 42 43 44 45 46 47
19 node 5 size: 32768 MB
20 node 5 free: 25006 MB
21 node 6 cpus: 48 49 50 51 52 53 54 55
22 node 6 size: 32768 MB
23 node 6 free: 24859 MB
24 node 7 cpus: 56 57 58 59 60 61 62 63
25 node 7 size: 32752 MB
26 node 7 free: 25099 MB
27 node distances:
28 node 0 1 2 3 4 5 6 7·
29 0: 10 16 16 22 16 22 16 22·
30 1: 16 10 22 16 22 16 22 16·
31 2: 16 22 10 16 16 22 16 22·
32 3: 22 16 16 10 22 16 22 16·
33 4: 16 22 16 22 10 16 16 22·
34 5: 22 16 22 16 16 10 22 16·
35 6: 16 22 16 22 16 22 10 16·
36 7: 22 16 22 16 22 16 16 10·
numactl
Puedo controlar cómo quiero que use la memoria respecto a nodos.
Esto es usando el sgemv.c
autoparalelizado.
1 $numactl --cpunodebind=1 --membind=1 ./a.out
2 37.390317
3 $ numactl --cpunodebind=0 --membind=1 ./a.out
4 19.060755
5 $ numactl --cpunodebind=1 --membind=0 ./a.out
6 21.900698
7 $ numactl --cpunodebind=0,1 --membind=0,1 ./a.out
8 69.289990
9 $ numactl --cpunodebind=0 --membind=0,1 ./a.out
10 33.960225
11 $ numactl --cpunodebind=0,1 --membind=0 ./a.out
12 42.920589
numactl
como taskset
¡Atar procesos a cores!
sgemv.c
para N=1L<<16
.
Ojo, hay que compilar con -mcmodel=large
.
Las cuentas dicen que son ~16 GiB
(((1<<16)*(1<<16) + 2*(1<<16)) * 4.0) / (1<<30) = 16.00048828125
1 $ numactl --physcpubind=0 ./a.out
2 6.915849
3 $ numactl --physcpubind=0-1 ./a.out
4 13.575607
5 $ numactl --physcpubind=0-15 ./a.out
6 68.431601
El último es idem a:
1 $ taskset 0x0000FFFF ./a.out
2 69.925633
sgemv
paralelo 1 #define N (1L<<16)
2 float a[N][N], b[N], c[N];
3
4 int main(void) {
5 int i = 0, j = 0;
6 double start = 0.0;
7
8 start = omp_get_wtime();
9 #pragma omp parallel for shared(a,b,c,start) private(i,j)
10 for (i=0; i<N; ++i)
11 for (j=0; j<N; ++j)
12 c[i] += a[i][j]*b[j];
13 printf("%f ", ((long)N*N*3*sizeof(float))/((1<<30)*(omp_get_wtime()-start)));
14
15 return 0;
16 }
Resultado en GiB/s:
1 $ gcc -O3 -mcmodel=large -fopenmp parallel_sgemv.c && ./a.out
2 73.599593
sgemv
paraleloEn medio de la ejecución (puse un pause 0
antes de salir) tenemos:
1 $ numactl --hardware
2 available: 2 nodes (0-1)
3 node 0 cpus: 0 1 2 3 4 5 6 7
4 node 0 size: 16355 MB
5 node 0 free: 6861 MB
6 node 1 cpus: 8 9 10 11 12 13 14 15
7 node 1 size: 16384 MB
8 node 1 free: 6965 MB
9 node distances:
10 node 0 1
11 0: 10 21
12 1: 21 10
Ambos nodos NUMA ocupados.
Notar: que hay ~16 GiB ocupados distribuidos en los dos nodos.
Para evitar poner un getchar()
, se puede usar /usr/bin/time
.
Entre otras cosas muestra el consumo máximo de memoria residente
1 $ gcc -O3 -mcmodel=large -fopenmp parallel_sgemv_nonnuma.c && /usr/bin/time -f '%MkB' ./a.out
2 75.722791
3 67114112kB
Ojo con /usr/bin/time
versión 1.7, hay que dividir el resultado por 4.
O sea da bien: ~16 GiB.
sgemv
init 0 1 #define N (1L<<16)
2
3 float a[N][N], b[N], c[N];
4
5 int main(void) {
6 int i = 0, j = 0;
7 double start = 0.0;
8
9 memset(a, 0, N*N*sizeof(float));
10 memset(b, 0, N*sizeof(float));
11 memset(c, 0, N*sizeof(float));
12
13 start = omp_get_wtime();
14 #pragma omp parallel for shared(a,b,c,start) private(i,j)
15 for (i=0; i<N; ++i)
16 for (j=0; j<N; ++j)
17 c[i] += a[i][j]*b[j];
18 printf("%f ", ((long)3*N*N*sizeof(float))/((1<<30)*(omp_get_wtime()-start)));
19
20 return 0;
21 }
Resultado en GiB/s:
1 $ gcc -O3 -mcmodel=large -fopenmp parallel_sgemv_nonnuma.c && ./a.out
2 82.269709
sgemv
init 0En medio de la ejecución tenemos:
1 $ numactl --hardware
2 available: 2 nodes (0-1)
3 node 0 cpus: 0 1 2 3 4 5 6 7
4 node 0 size: 16355 MB
5 node 0 free: 2245 MB
6 node 1 cpus: 8 9 10 11 12 13 14 15
7 node 1 size: 16384 MB
8 node 1 free: 12172 MB
9 node distances:
10 node 0 1
11 0: 10 21
12 1: 21 10
Pero tenemos lo que queremos, casi toda la memoria en el nodo 0.
¿Porqué funciona rápido?
¿Porqué?
¿Ah?
1 [nwolovick@mendieta Clase13_20140513]$ numastat && gcc -O3 -mcmodel=large -fopenmp parallel_sgemv.c && ./a.out && numastat
2 node0 node1
3 numa_hit 89139513 91739553
4 numa_miss 4240 24102
5 numa_foreign 24102 4240
6 interleave_hit 78788 78786
7 local_node 89080660 91631851
8 other_node 63093 131804
9 67.330149
10 node0 node1
11 numa_hit 89151580 91747783
12 numa_miss 4240 24102
13 numa_foreign 24102 4240
14 interleave_hit 78788 78786
15 local_node 89092727 91640081
16 other_node 63093 131804
El valor de other_node
es la que nos interesa.
No hubo cambio.
La versión non-numa-aware si registra comunicación.
1 $ numastat && gcc -O3 -mcmodel=large -fopenmp parallel_sgemv_nonnuma.c && ./a.out && numastat
2 node0 node1
3 numa_hit 89096545 91700335
4 numa_miss 3230 24102
5 numa_foreign 24102 3230
6 interleave_hit 78788 78786
7 local_node 89037692 91592633
8 other_node 62083 131804
9 81.223156
10 node0 node1
11 numa_hit 89115401 91717115
12 numa_miss 4240 24102
13 numa_foreign 24102 4240
14 interleave_hit 78788 78786
15 local_node 89056548 91609413
16 other_node 63093 131804
Más indicios de que efectivamente se está sobrecargando el QPI.
Frank Denneman, Node Interleaving: Enable or Disable?, 2012
"De la Playstation a la Computación Científica",
ó
"¿Porqué los gamers son nuestro máximo aliado?"
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 |