numactl
, taskset
.mendieta
.GOMP_CPU_AFFINITY
(GNU), MP_BIND
y MP_BLIST
(PGI), PSC_OMP_AFFINITY_MAP
(EKOPath), KMP_AFFINITY
(Intel).Nicolás Wolovick 20140513
1 PROGRAM EASYPARALLEL
2
3 PARAMETER (N = 2**28)
4 REAL A(N)
5 REAL B(N)
6 c DO I = 1,N
7 c B(I) = I*3.14159 + N - I
8 c ENDDO
9
10 DO I = 1,N
11 A(I) = B(I) * 3.14159
12 ENDDO
13 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
3 Performance counter stats for './a.out' (5 runs):
4
5 1092,059892 task-clock # 1,740 CPUs utilized ( +- 0,17% )
6
7 0,627569616 seconds time elapsed ( +- 0,39% )
1 float a[N][N], b[N], c[N];
2
3 int main(void) {
4 unsigned int 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 }
-ftree-parallelize-loops=n
, donde n
es la cantidad de hilos.
Mostrar juego entre:
-floop-parallelize-all
: lo saco y tengo multicore.
-ffast-math
: lo pongo y tengo vectorización.
¿No pude? hacer andar autovectorización y autoparalelización.
Sigo buscando el vellocino de oro.
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
.
Lograr autoparalelizar y autovectorizar sgemv
.
Premio: un paquete de "Sonrisas®".
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.
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 |