Estudio de utilización efectiva de procesadores vectoriales

Anuncio
Universidad de Las Palmas de Gran Canaria
Estudio de utilización efectiva de
procesadores vectoriales
Proyecto de Fin de Carrera
Ingenierı́a en Informática
Laura Autón Garcı́a
Tutores:
Francisca Quintana Domı́nguez
Roger Espasa Sans
Las Palmas de Gran Canaria, 9 de julio de 2014
Agradecimientos
Quiero agradecer a Francisca Quintana y a Roger Espasa, mis tutores de proyecto, el haberme
brindado la oportunidad de adentrarme en una experiencia que bien podrı́a ser el sueño de cualquier futuro ingeniero informático cuando avista cada vez más cerca la meta de su esfuerzo. Este
viaje no solo ha dado como resultado el presente trabajo, sino también la satisfacción profesional de haber trabajado en Intel, empresa puntera en el ámbito de la computación, y personal de
haber trabajado con extraordinarios ingenieros a la vez que fantásticas personas durante todo el
proceso. Entre ellos, quiero agradecer especialmente a Manel Fernández por la enorme paciencia
y dedicación con las que consiguió guiarme cuando me desviaba del camino, y a Jesús Sánchez
porque su buen humor y positivismo amenizaba todas las tormentas de ideas, por muy oscuras
que pudieran divisarse a lo lejos.
Del mismo modo, quiero agradecer muy especialmente a Susana y Delfı́n, por haber sido
mi familia durante mi estancia en Canarias. A mis padres, Marı́a y Cándido por haber sabido
apoyarme desde la distancia con sus palabras al otro lado del teléfono. Y a Raúl, mi gran compañero
en este viaje, porque ha sido la única persona de este mundo que realmente ha conocido mis más
profundas inquietudes, y que ha sabido iluminarme el camino y cederme las mangas sobre las que
derramar mis lágrimas.
i
Índice de figuras
2.1. SISD . . . . . . . . . . . .
2.2. SIMD . . . . . . . . . . .
2.3. MISD . . . . . . . . . . .
2.4. MIMD . . . . . . . . . . .
R Xeon PhiTM . . .
2.5. Intel
2.6. Esquema general . . . . .
2.7. Microarquitectura . . . .
2.8. Vector Processing Unit . .
2.9. Interconexion . . . . . . .
2.10. Directorio de etiquetas . .
2.11. Controladores de memoria
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
5
5
5
5
9
9
10
10
11
11
12
4.1. Arquitectura software de Pin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
22
5.1.
5.2.
5.3.
5.4.
5.5.
5.6.
Diagrama de funcionamiento CMP$im . . . .
Simulación en modo buffer . . . . . . . . . .
Simulación en modo instrucción a instrucción
Ejemplo de bloque básico . . . . . . . . . . .
Proceso de descubrimiento de bloques . . . .
Punteros a objetos cache . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
31
32
33
34
34
36
6.1. Índice de vectorización de las aplicaciones de Polyhedron .
6.2. Razones para no vectorizar bucles en Polyhedron . . . . .
6.3. Índice de vectorización de las aplicaciones de Mantevo 1.0
6.4. Razones para no vectorizar bucles en Mantevo 1.0 . . . .
6.5. Índice de vectorización de las aplicaciones de Sequoia . . .
6.6. Razones para no vectorizar bucles en Sequoia . . . . . . .
6.7. Índice de vectorización de las aplicaciones de NPB . . . .
6.8. Razones para no vectorizar bucles en NPB . . . . . . . . .
6.9. Índice de vectorización de las aplicaciones de SPEC fp . .
6.10. Razones para no vectorizar bucles en SPEC fp . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
40
41
42
43
44
44
45
46
47
48
7.1.
7.2.
7.3.
7.4.
7.5.
7.6.
7.7.
Pipeline dentro de CMP$im . . . . . . . . . . . . .
Pipeline del bloque de s171 . . . . . . . . . . . . .
Localización del simulador de pipeline en CMP$im
Idea para la implementación de KNC . . . . . . . .
Instrucción que toca dos lı́neas . . . . . . . . . . .
Bloques con ningún y un corte . . . . . . . . . . .
Bloques con 2 cortes . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
50
52
56
56
58
63
64
8.1.
8.2.
8.3.
8.4.
8.5.
Versión vectorizada vs no vectorizada de Polyhedron
Ciclos desglosados de las aplicaciones de Polyhedron
Versión vectorizada vs no vectorizada de Mantevo .
Ciclos desglosados de las aplicaciones de Mantevo . .
Versión vectorizada vs no vectorizada de Sequoia . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
66
67
69
69
70
iii
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
ÍNDICE DE FIGURAS
iv
8.6. Ciclos desglosados de las aplicaciones de Sequoia . . . .
8.7. Versión vectorizada vs no vectorizada de NPB . . . . . .
8.8. Ciclos desglosados de las aplicaciones de NPB . . . . . .
8.9. Versión vectorizada vs no vectorizada de SPEC fp . . .
8.10. Ciclos desglosados de las aplicaciones de SPEC fp 2006 .
8.11. Comparación entre las versiones :nodes y do de gas dyn
8.12. Resultado de doblar la UL2 de 1024Kb a 2048Kb . . . .
8.13. Mejora de SPEC fp/433.milc al doblar la L2 . . . . . . .
8.14. Consecuencia posible por aumento de aciertos en L2 . .
8.15. Resultado de doblar las lı́neas de DTLB2 de 256 a 512 .
8.16. Mejora de IS de NPB al doblar la TLB . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. 70
. 71
. 72
. 73
. 73
. 84
. 97
. 98
. 99
. 100
. 100
Índice de tablas
R ICC . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.1. Knobs soportados por Intel
28
6.1.
6.2.
6.3.
6.4.
6.5.
.
.
.
.
.
40
42
43
45
47
7.1. Latencias de memoria y de instrucción (load-op) . . . . . . . . . . . . . . . . . . .
51
8.1. Bloques 1 y 2 de la lista de bloques básicos más ejecutados en Fatigue, Polyhedron
8.2. Bloque 3 de la lista de bloques básicos más ejecutados en Fatigue, Polyhedron . . .
8.3. Bloques 1, 2, 4 y 5 de la lista de bloques básicos más ejecutados en Induct, Polyhedron
8.4. Bloques 1 y 2 más ejecutados de Aermod, Polyhedron . . . . . . . . . . . . . . . .
8.5. Bloques 3, 5 y 10 más ejecutados de Aermod, Polyhedron . . . . . . . . . . . . . .
8.6. Bloques 7 y 9 más ejecutados de Aermod, Polyhedron . . . . . . . . . . . . . . . .
8.7. Bloque 8 más ejecutado de Aermod, Polyhedron . . . . . . . . . . . . . . . . . . .
8.8. Desglose de instrucciones de las versiones escalar y vectorial de Gas dyn, Polyhedron
8.9. Bloques 1 y 3 más ejecutados de Gas dyn, Polyhedron . . . . . . . . . . . . . . . .
8.10. Bloques 1, 2, 3 y 4 más ejecutados de SPhotmk, Sequoia . . . . . . . . . . . . . . .
8.11. Bloque 1 de los más ejecutados de BT, NPB . . . . . . . . . . . . . . . . . . . . . .
8.12. Bloques 1 y 2 de los más ejecutados de LU, NPB . . . . . . . . . . . . . . . . . . .
8.13. Bloques 1 y 2 de los más ejecutados de Povray, SPEC FP . . . . . . . . . . . . . .
8.14. Aplicaciones con una mejora inferior al 1 % . . . . . . . . . . . . . . . . . . . . . .
75
76
77
79
80
80
82
82
83
88
90
91
94
97
Desglose
Desglose
Desglose
Desglose
Desglose
de
de
de
de
de
instrucciones
instrucciones
instrucciones
instrucciones
instrucciones
de
de
de
de
de
las
las
las
las
las
aplicaciones
aplicaciones
aplicaciones
aplicaciones
aplicaciones
de
de
de
de
de
Polyhedron
Mantevo . .
Sequoia . .
NPB . . . .
SPEC FP .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
R ICC Specific Pragmas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
A.1. Intel
R ICC Supported Pragmas . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
B.1. Intel
R Fotran Directives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
C.1. Intel
D.1. Mensajes del compilador . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
v
Índice general
Agradecimientos
I
Lista de figuras
VIII
Lista de tablas
VIII
1. Introducción
1.1. Objetivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2. Estado del arte
2.1. Taxonomı́a de Flynn . . . . . . . . . . . . . . .
2.2. Vectorización . . . . . . . . . . . . . . . . . . .
2.2.1. SIMD . . . . . . . . . . . . . . . . . . .
R Xeon PhiTM Coprocessor . . . . . . . .
2.3. Intel
2.3.1. Microarquitectura . . . . . . . . . . . .
R Advanced Vector Extensions . . . . . .
2.4. Intel
R Advanced Vector Extensions 1 .
2.4.1. Intel
R Advanced Vector Extensions 2 .
2.4.2. Intel
R Advanced Vector Extensions 512
2.4.3. Intel
1
2
.
.
.
.
.
.
.
.
.
3
4
5
5
8
9
13
13
13
13
3. Metodologı́a
3.1. Plan de trabajo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
15
15
4. Herramientas
4.1. Pin . . . . . . . . . . . . . . . . . . . . .
4.1.1. Pintools . . . . . . . . . . . . . .
4.1.2. Arquitectura software . . . . . .
4.2. CMP$im . . . . . . . . . . . . . . . . . .
4.3. Benchmarks . . . . . . . . . . . . . . . .
4.3.1. Polyhedron Fortran Benchmarks
4.3.2. Mantevo 1.0 . . . . . . . . . . . .
4.3.3. ASC Sequoia Benchmark Codes .
4.3.4. NAS Parallel Benchmarks . . . .
4.3.5. SPEC CPU 2006 . . . . . . . . .
4.4. Compiladores . . . . . . . . . . . . . . .
4.4.1. ICC . . . . . . . . . . . . . . . .
4.4.2. IFORT . . . . . . . . . . . . . .
4.5. Pragmas . . . . . . . . . . . . . . . . . .
4.6. Herramientas internas . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
19
19
19
21
22
23
24
25
25
25
26
27
27
28
29
29
5. Arquitectura del Simulador
5.1. Flujo de ejecución . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.2. Estructuras y clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.3. Parámetros de ejecución . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
31
31
33
37
vii
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
ÍNDICE GENERAL
viii
6. Caracterización de benchmarks
6.1. Polyhedron . . . . . . . . . . .
6.2. Mantevo 1.0 . . . . . . . . . . .
6.3. Sequoia . . . . . . . . . . . . .
6.4. NPB . . . . . . . . . . . . . . .
6.5. SPEC FP . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
39
39
41
42
43
46
7. Adaptación del Simulador
7.1. Pipeline . . . . . . . . . . . . . . . . . . . . . .
7.2. Detección de instrucciones y registros . . . . .
7.2.1. Instrucciones . . . . . . . . . . . . . . .
7.2.2. Registros . . . . . . . . . . . . . . . . .
7.2.3. Latencias . . . . . . . . . . . . . . . . .
7.3. Nuevas estructuras y clases . . . . . . . . . . .
7.4. Estadı́sticas . . . . . . . . . . . . . . . . . . . .
7.5. Invocación activando la funcionalidad vectorial
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
49
49
53
53
54
54
55
62
64
8. Estudio experimental
8.1. Resultados . . . . . . . . . .
8.1.1. Polyhedron . . . . .
8.1.2. Mantevo . . . . . . .
8.1.3. Sequoia . . . . . . .
8.1.4. NPB . . . . . . . . .
8.1.5. SPEC fp . . . . . . .
8.2. Diagnóstico Software . . . .
8.2.1. Polyhedron . . . . .
8.2.2. Mantevo . . . . . . .
8.2.3. Sequoia . . . . . . .
8.2.4. NPB . . . . . . . . .
8.2.5. SPEC fp . . . . . . .
8.3. Diagnóstico Hardware . . .
8.3.1. Incremento de UL2 .
8.3.2. Incremento de TLB
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
65
65
65
68
70
71
72
74
75
84
86
89
92
96
97
99
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
9. Conclusiones
101
9.1. Trabajo Futuro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
R ICC Specific Pragmas
A. Intel
105
R ICC Supported Pragmas
B. Intel
109
R Fortran Directives
C. Intel
113
D. Mensajes del compilador
117
Capı́tulo 1
Introducción
Echando una profunda mirada al pasado para recorrer toda la historia de la informática, desde
donde venimos, en qué punto nos encontramos y a dónde vamos, nos damos cuenta del gran esfuerzo
que ha hecho y sigue haciendo el ser humano para no solo automatizar tareas, sino también para
que éstas se hagan lo más rápido posible. Y es que, ya en su momento, para realizar cálculos
balı́sticos de gran utilidad en posibles contiendas, se incrementaba el número de personas que
realizaban una tarea. Al segmentar el trabajo se conseguı́a realizar la tarea en menos tiempo de lo
que lo conseguirı́a una sola. A estas personas se las denominaba antiguamente computers[Bar08],
nombre que más tarde se adoptó para las máquinas que sustituyeron su trabajo.
Desde entonces, la capacidad de cómputo de las máquinas ha ido evolucionado enormemente
gracias a, por ejemplo, la cantidad de transistores que fuimos capaces de insertar dentro de una
onza de silicio y que bien supo pronosticar Gordon Moore, cuya afirmación se bautizo como Ley
de Moore. Cuando las limitaciones fı́sicas se convirtieron en un problema, empezamos a introducir
más núcleos en un mismo procesador: primero dos, luego cuatro... Está claro que, sean los motivos
que sean los que impulsen al ser humano a seguir escudriñando mejoras en cualquier tipo de
artefacto, mecanismo o sistema que tenga entre manos, y sobre el que haya trabajado desde
tiempos inmemoriales, el mundo, inexorablemente, se sigue moviendo. Y es en ese mundo en
constante cambio y movimiento, donde acaban por surgir ideas como aquella sobre la que se ha
construido el trabajo que se presenta: la vectorización.
Hoy en dı́a, una importante muestra de procesadores disponibles en el mercado disponen de
unidades de cómputo, denominadas vectoriales, que permiten la explotación de este concepto. Y
es que la vectorización explota un caso particular de paralelismo cuyo objetivo consiste en realizar
la misma operación, en vez de sobre un único dato como venı́a siendo hasta ahora, sobre la mayor
cantidad de datos contenidos en un vector que le sea posible. Por ello, se denomina DLP (Data
Level Parallelism) o Paralelismo de datos. Algunos de estos procesadores, por ejemplo, son los de
R basados en la arquitectura Sandy Bridge que, con el objetivo de permitir la explotación del
Intel
paralelismo de datos, incluyen extensiones AVX (Advanced Vector Extensions) sobre el repertorio
de instrucciones x86. Sin embargo, esta obra de ingenierı́a no es suficiente por sı́ sola. Es necesario
un engranaje más, y que no es otro que un compilador especialmente construido para máquinas
como estas, que sea capaz de extraer el mayor paralelismo de datos posible de una aplicación.
Pese a que todos los elementos mencionados conforman la receta perfecta para sacar el mayor
rendimiento posible a aplicaciones que requieren de una importante capacidad de cómputo, no
siempre se obtienen los resultados esperados. Las razones pueden residir tanto en el software como
en el hardware. Puede que la aplicación no experimente las mejoras esperadas después de ser
vectorizada. Es posible que el compilador no sea capaz por sı́ mismo de encontrar potenciales
secciones de código vectorizables debido a ambigüedades en el acceso a los datos. O bien, podrı́a
1
2
CAPÍTULO 1. INTRODUCCIÓN
ser que la memoria esté suponiendo un cuello de botella a la hora de recuperar los datos sobre los
que operar.
Basándonos pues en la realidad descrita, se propuso la realización del trabajo que se detalla
en este documento, con el objetivo fundamental de determinar el grado de utilización efectiva
de la unidad vectorial de un procesador. Se realizarı́a entonces, para aquellos casos donde el uso
fuera menor del esperado, un diagnóstico del problema que permitiera lograr una mejora en el
rendimiento de la aplicación.
1.1.
Objetivos
El objetivo principal de este Proyecto Final de Carrera, consiste en determinar el grado de
utilización efectiva de la unidad vectorial de un procesador. Para lograr la consecución del mismo,
se proponen los siguientes objetivos parciales:
Analizar y clasificar un conjunto de aplicaciones numéricas en función del grado de vectorización sobre un compilador determinado.
Determinar las causas del bajo grado de vectorización, a partir de la simulación de las
aplicaciones según el funcionamiento de un producto existente que hace uso de la unidad
vectorial. Las posibles causas serán las siguientes:
• Problemática en el algoritmo base de la aplicación debido a dependencias en el código.
• Problemática en los criterios seguidos a la hora de escribir el código fuente.
• Incapacidad del compilador de detectar que el código es vectorizable.
• Problemas en la microarquitectura.
Proponer cambios hardware/software que faciliten el uso efectivo de la unidad vectorial.
Capı́tulo 2
Estado del arte
La computación paralela es una forma de cómputo consistente en paralelizar la mayor cantidad
de tareas posible con el objetivo de reducir el coste de cómputo de un programa. Tradicionalmente
se utilizaba otro paradigma: la computación serie. Con ella las instrucciones se ejecutaban una
tras otra en la Unidad Central de Procesamiento (CPU). La utilización de este paradigma produce
que, a medida que se incrementa la frecuencia de funcionamiento de la máquina, se disminiuya el
tiempo que tardan en ejecutarse los programas[HP02]. El aumento de la frecuencia, que tuvo su
apogeo durante las dos últimas décadas del siglo XX y principios del XXI, no podı́a ser infinito,
ya que es directamente proporcional al aumento de la energı́a consumida por el procesador y, por
ende, a la generación de calor. Por este motivo, pese a que la computación paralela se empezó a
usar principalmente en el área de la computación de altas prestaciones, este lı́mite en el aumento
de la frecuencia propició que desde la ultima década, el paradigma principal en arquitectura de
computadores sea la computación paralela.[Bar07]
Existen diferentes fuentes de paralelismo disponibles para sacar partido a la computación
paralela. Estas son: Paralelismo de Instrucciones (ILP), Paralelismo de Datos (DLP) y Paralelismo
de Tareas (TLP)[Dı́06]:
ILP: consiste en ejecutar el mayor número de instrucciones posibles en paralelo sin que ello
afecte al correcto flujo del programa. Como ejemplos tenemos las arquitecturas superescalares
y VLIW, del inglés Very Long Instruction Word:
• Superescalares: capaces de introducir en el pipeline de ejecución una o más instrucciones por ciclo, de manera que se pueden estar ejecutando paralelamente varias en un
mismo ciclo.
• VLIW: la arquitectura permite empaquetar varias instrucciones independientes que se
ejecutarán simultáneamente.
Con el objetivo de explotar al máximo esta fuente de paralelismo, existen diferentes técnicas
que se podrı́an clasificar en técnicas de planificación estática y dinámica[Dı́06].
• Técnicas de planificación estática: trabajan sobre el código de la aplicación para
conseguir eliminar todos los obstáculos que impiden que las instrucciones se ejecuten
lo antes posible: desenrollamiento de bucles, reordenamiento de instrucciones...
• Técnicas de planificación dinámica: se aplican sobre el diseño del hardware para
que tengan lugar en tiempo de ejecución: Ejecución Fuera de Orden (OOO).
DLP: consiste en la realización de la misma operación simultáneamente sobre un conjunto
de datos. Para explotar esta técnica de paralelismo, es necesario que el programa tenga
3
4
CAPÍTULO 2. ESTADO DEL ARTE
secciones de código que se puedan adaptar a la aplicación de este concepto. Además, la
arquitectura tiene que proveer de instrucciones especiales, denominadas SIMD, del ingles
Simple Instruction Multiple Data, y de recursos suficientes, como por ejemplo los registros
vectoriales, los cuales puedan contener más de un dato. La estructura de ejecución por
antonomasia sobre la que se pone en práctica este mecanismo, es el bucle. Las estructuras
de datos análogas son los vectores y matrices.
TLP: el concepto radica en la descomposición de la ejecución del programa en diferentes
trazas con instrucciones independientes para ejecutarlas de forma concurrente. Un ejemplo es
la Tecnologı́a Multihilos, SMT, del inglés Simultaneous MultiThreading. Consiste en ejecutar
instrucciones de diferentes hilos independientes en el mismo ciclo de reloj.
En el mercado existen una gran variedad de arquitecturas que implementan recursos para
explotar cualquiera de las fuentes de paralelismo arriba mencionadas. Por ejemplo, la arquitectura
ARM con su extensión SIMD Avanzada, también conocida como NEON o MPE, del inglés Media
Processing Engine, para explotar el paralelismo de instrucciones; Nvidia y su plataforma CUDA,
del inglés Computed Unified Device Architecture, junto con el set de instrucciones PTX, del
inglés Parallel Thread Execution[nvi], que define una máquina virtual y un ISA con el objetivo de
R
explotar la GPU como máquina de ejecución de hilos paralelos de propósito general; Intely
su
R MIC, del inglés Many Integrated Core, sobre la que han desarrollado varios
arquitectura Intel
R Xeon PhiTM Coprocessor, lanzado al mercado en
productos, siendo el último de ellos el Intel
Noviembre de 2012 y descrito en la Sección 2.3 (pág. 8), que explota tanto el paralelismo de
instrucciones como de tareas.
2.1.
Taxonomı́a de Flynn
La taxonomı́a de Flynn consiste en una clasificación de arquitecturas paralelas desarrollada por
Michael J. Flynn en 1966 y expandida en 1972[Fly72]. Desde el punto de vista del programador en
lenguaje ensamblador, las arquitecturas paralelas estarı́an clasificadas según la concurrencia del
procesamiento de secuencias, datos e instrucciones. Esto da como resultado una metodologı́a de
clasificación de las distintas operaciones paralelas disponibles en el procesador. Propuesta como
una aproximación que clarificara los tipos de paralelismo soportados tanto a nivel hardware como
software, en ella se definen las siguientes cuatro arquitecturas[fly]:
Single Instruction Single Data (SISD): arquitectura secuencial que no explota el paralelismo ni a nivel de instrucciones ni a nivel de datos. Las máquinas tradicionales de un
único procesador secuencial o antiguos mainframes entrarı́an en esta categorización. Ver
Figura 2.1 (pág. 5).
Single Instruction Multiple Data (SIMD): arquitectura que explota el paralelismo durante la ejecución de una única instrucción para realizar operaciones de naturaleza paralela.
Claros ejemplos son los procesadores vectoriales o las GPU. Ver Figura 2.2 (pág. 5).
Multiple instruction Single Data (MISD): múltiples instrucciones operan sobre un
único stream de datos. Es una arquitectura poco común generalmente usada para tolerancia
de fallos, esto es, varios sistemas operan en el mismo stream de datos y obtienen un resultado
que debe ser concorde para todos ellos. Ver Figura 2.3 (pág. 5).
Multiple instruction Multiple Data (MIMD): múltiples procesadores executan simultáneamente diferentes instrucciones sobre diferentes datos. Las arquitecturas VLIW son
un claro ejemplo aparte de sistemas distribuidos o procesadores multicore. Ver Figura 2.4
(pág. 5).
2.2. VECTORIZACIÓN
Figura 2.1: SISD
2.2.
5
Figura 2.2: SIMD
Figura 2.3: MISD
Figura 2.4: MIMD
Vectorización
La vectorización es un proceso de explotación de paralelismo de datos consistente en convertir
un algoritmo de implementación escalar a vectorial. La implementación escalar es aquella en
la que se realiza una única operación simultánea sobre un par de operandos que contienen un
único dato cada uno. La implementación vectorial realizarı́a la misma operación, pero el par de
operandos pasan de contener un único dato a contener una serie de valores. Literalmente, las
escalares operan sobre un escalar y las vectoriales sobre un vector. El bucle siguiente es un claro
ejemplo de candidato a ser vectorizado.
1
2
for ( i =0; i < n ; i ++)
c [ i ] = a [ i ] + b [ i ];
Listado 2.1: Bucle vectorizable
Con el objetivo de poner en práctica esta técnica, es necesario que la arquitectura sobre la que
se va a ejecutar el programa disponga de un repertorio de instrucciones especı́fico, de una unidad
vectorial y de registros vectoriales que puedan contener una serie de datos[LPG13] [SMP11] [Pip12]
[SLA05]. Este repertorio de instrucciones suele ser una extensión sobre las que ya conformen la
ISA con la que se trabaje, denominadas instrucciones SIMD. SIMD también sirve para indicar el
tipo de la máquina. La unidad vectorial contendrá las unidades funcionales necesarias para operar
sobre los registros vectoriales. Finalmente, para generar el código vectorizado, hace falta utilizar
un compilador que sea capaz de reconocer secciones de código potencialmente vectoriales para
generar las instrucciones SIMD que correspondan.
2.2.1.
SIMD
SIMD, como se describió previamente, es un término definido dentro de la taxonomı́a de Flynn
para caracterizar aquellas arquitecturas que explotan el Paralelismo de Datos (DLP). Son arquitecturas que contienen múltiples elementos de procesamiento que permiten realizar la misma
operación sobre varios datos simultáneamente. Esta forma de paralelismo se diferencia de la concurrencia en que es en el mismo momento, y no en momentos diferentes, cuando se realiza de forma
simultánea la misma operación sobre el conjunto de datos que corresponda. Estas instrucciones se
denominan comúnmente como instrucciones SIMD.
Un procesador vectorial, por tanto, es un procesador que puede operar en todos los elementos de
un vector dado con una única instrucción. Esto supone una reducción importante en el número de
instrucciones leı́das, decodificadas y ejecutadas para un mismo programa vectorizado, frente a otro
que no haya sido compilado o escrito usando este recurso. El vector sobre el que se opere, puede a
su vez ser leı́do usando diferentes técnicas que diferencian a unos procesadores vectoriales de otros.
En el caso de ser una arquitectura vectorial memoria-memoria, los operandos son inyectados a la
6
CAPÍTULO 2. ESTADO DEL ARTE
unidad vectorial directamente de memoria, siendo el resultado devuelto a la misma a posteriori. En
el caso de las arquitecturas vectoriales vector-registro, los operandos son depositados en registros
vectoriales que alimentaran a la unidad vectorial, siendo asimismo el resultado depositado en otro
registro vectorial[MS03]. Independientemente de sendos modus operandi descritos, la mejora en
la reducción del número de instrucciones leı́das y decodificadas resulta no ser tal si al final la
instrucción va a tener que esperar a que se lea el vector o porción del vector de memoria. Por
esto, existen diversos esquemas de optimización del rendimiento centrados en reducir el tiempo de
acceso a la memoria:
Hardware y software prefetching: prefetching en términos generales significa traer datos
o instrucciones de memoria antes de que se necesiten. Cuando la aplicación necesita datos que
se han traı́do con el prefetching, puede tomarlos directamente en vez de tener que esperar por
la memoria. Esta técnica puede ser iniciada tanto desde el hardware como desde el software.
Gather-scatter: estas instrucciones permiten un tipo de direccionamiento de memoria propio del tratamiento de vectores. El gather se encargarı́a de indexar la lectura del vector mientras que el scatter se encargarı́a de la escritura. En su funcionamiento intervienen máscaras
que indicarı́an los elementos del vector sobre los que se realizarı́a la operación. Estas podrı́an
ser útiles en caso de haber condiciones en el interior del bucle para acceder a datos de forma
dispersa.
Stripmining: técnica que afronta un problema en la vectorización consistente en que los
registros vectoriales no tienen por qué ser capaces de contener un vector completo definido
en la aplicación. Consistirı́a en romper el bucle de la aplicación que opera sobre el vector en
diferentes bucles: prólogo, principal y epı́logo según conviniera, para tratar un número de
datos <= M V L, donde MVL es la Longitud Máxima de Vector del procesador.
Bancos de memoria: permiten realizar varios load y stores simultáneamente. Los datos
tratados cada vez no tendrı́an por qué ser necesariamente secuenciales.
Repertorio de instrucciones SIMD
El primer uso de instrucciones SIMD fue en los supercomputadores vectoriales a principios de
los 70. Sin embargo, el modo de funcionamiento de entonces era ligeramente distinto al concepto
actual. Anteriormente la instrucción que operaba sobre el vector lo hacı́a sobre un pipeline de
procesadores, cada uno de los cuales operaba únicamente sobre una palabra del vector. En el
concepto moderno, es un procesador el que realiza la operación simultáneamente sobre el vector
completo o una porción del mismo.
A lo largo del tiempo, esta tecnologı́a salto a las máquinas sobremesa, mercado en el que
tuvo gran repercusión y posterior demanda debido a que podı́a soportar aplicaciones tales como
procesamiento de vı́deo y juegos en tiempo real. Es por ello que a lo largo de la historia ha
habido gran variedad de repertorios SIMD fruto de la competencia entre diferentes compañı́as del
momento. Hoy en dı́a no hay computadora de uso general que no haga uso de una arquitectura que
explote el paralelismo de datos a través de la vectorización. A continuación se nombran algunos
de los diferentes repertorios de instrucciones SIMD de la historia:
VIS: Visual Instruction Set. Repertorio desarrollado y lanzado al mercado en 1994 por Sun
Microsystems, adquirida por Oracle Corporation en 2010. La primera version fue implementada en el UltraSPARC en 1995 y por Fujitsu en el SPARC64 GP. Registros de 8, 16 y 32
bits. Hubieron varias versiones posteriores esta: VIS2, VIS2+ y VIS3.
R y lanzado en 1997 en su gama Pentium MMX.
MMX: repertorio desarrollado por Intel
Registros de 64 bits.
2.2. VECTORIZACIÓN
7
3DNow!: extensión al repertorio de la arquitectura x86 desarrollado por AMD. Registros
32 bits para operaciones de punto flotante de simple precisión. El objetivo era mejorar el
ya existente repertorio MMX de Intel de cara a elevar el rendimiento de las aplicaciones
gráficas. En 2010 AMD anunció el fin del mantenimiento y soporte del mismo.
SSE: Streaming SIMD Extensions. Extensión de la arquitectura x86 diseñado por Intel y
lanzado al mercado en 1999 en su serie Pentium III, como respuesta al lanzamiento por parte
de AMD de su extensión 3DNow. Registros de 128 bits.
AltiVec: repertorio diseñado por y propiedad de la siguientes empreas: Apple, aquı́ recibe
el nombre de Velocity Engine; IBM, donde se denomina VMX; Freescale Semiconductor,
propietaria de la marca registrada AltiVec. Registros de 128 bits.
AVX: Advanced Vector Extensions. Extensión al repertorio de la arquitectura x86 destinada
a procesadores Intel y AMD. Registros de 256 bits. Su desarrollo fue propuesto por Intel
en 2008 pero no fue hasta tres años después cuando salió al mercado como caracterı́stica
de la generación de procesadores Sandy Bridge de Intel y Bulldozer de AMD. Posterior a
ella tenemos otra extension denominada AVX2 soportada por Haswell, Broadwell, Skylake
y Cannonlake de Intel y por Excavator de AMD. En Julio de 2013 Intel anunció AVX-512,
última extensión con registros de 512 bits.
Ventajas
Los procesadores vectoriales y por extensión el uso de la vectorización, proporcionan las siguientes ventajas:
Los programas son de menor tamaño al reducir el número de instrucciones que pudiera
requerir un bucle. Ademas, el número de instrucciones ejecutadas también se ve reducido al
poder concentrar un bucle en una única instrucción.
El rendimiento de la aplicación mejora. Se tienen N operaciones independientes que utilizan
la misma unidad funcional, explotando al máximo la localidad espacial de la memoria cache.
Las arquitecturas son escalables: se obtiene un mayor rendimiento cuantos más recursos
hardware haya disponibles.
El consumo de energı́a se reduce. Mientras la instrucción vectorial se está ejecutando, no es
necesario alimentar otras unidades tales como el ROB o el decodificador de instrucciones.
Tomando como ejemplo una aplicación multimedia que cambia el brillo a una imagen, la
vectorización proporciona dos mejoras claras: en primer lugar el conjunto de datos se entiende
como un vector y no como valores individuales. Esto permitirá cargar en un registro todos aquellos
datos que éste pueda albergar, en vez de convertir el programa en una retahı́la carga-opera-guarda
sobre una gran cantidad de pı́xeles individuales. En segundo lugar, la paralelización del trabajo en
una única instrucción es evidente. Cuanto más datos pueda albergar, mayor será el rendimiento.
Inconvenientes
Los procesadores vectoriales comparados con multiprocesadores o procesadores superescalares
podrı́an resultar menos interesantes si lo miramos desde el punto de vista del coste:
Necesitan memoria on-chip rápida y por consiguiente cara.
8
CAPÍTULO 2. ESTADO DEL ARTE
Hay que diseñarlos de forma especı́fica. La unidad vectorial de los procesadores vectoriales
no se compone de elementos prefabricados más allá de las unidades básicas, por tanto pocas
ventas del producto final se traducirı́a en pérdidas debido a su coste de diseño y validación.
Mientras que se conseguı́a una ventaja en el consumo de energı́a al reducir la alimentación
de otras unidades, el consumo por parte de los registros vectoriales podrı́a no mejorar el
balance final de consumo.
Los compiladores, al igual que pueden facilitar la tarea, pueden dificultarla en caso de que
la vectorización del código no se adapte a los requerimientos esperados por el compilador.
Existe entonces una posibilidad real de que haya una importante implicación del ingeniero
tanto a nivel alto como bajo para conseguir vectorizar una aplicación.
No hay un estándar establecido en el proceso. La utilización de un repertorio de instrucciones
SIMD u otro puede dificultar la tarea en caso de compilar la misma aplicación sobre distintas
arquitecturas. Aparte, es posible que el ingeniero tenga que proveer de una implementación
no vectorial de la misma.
No todas las aplicaciones se pueden vectorizar. Por ejemplo, las aplicaciones de análisis de
código, caracterizadas por su fuerte control del flujo de ejecución, son claras candidatas para
no beneficiarse de las ventajas de la vectorización.
2.3.
R Xeon PhiTM Coprocessor
Intel
R Xeon PhiTM es el primer producto basado en la arquitectura Intel
R
El coprocesador Intel
R
MIC que se ha comercializado. Se lanzó al mercado en Noviembre de 2012. La arquitectura Intel
R dentro de un único chip. Esta desMIC se basa en la combinación de muchos núcleos de Intel
tinada a su uso en la Computación de Alto Rendimiento o HPC, del inglés High Performance
Computing, para la ejecución de programas paralelos que se venı́an ejecutando en grandes clústeres1 . Pese a que su objetivo no es sustituir los sistemas ya existentes, es una interesante alternativa
para conseguir buenos resultados de rendimiento de throughput 2 , en entornos donde no haya demasiado espacio para la instalación de múltiples clústeres y donde se impongan limitaciones de
consumo de energı́a. Ademas, un punto clave de la microarquitectura es que esta construida especialmente para proporcionar un entorno de programación similar al entorno de programación del
R XeonTM [intb].
procesador Intel
R Xeon PhiTM puede pasar por un sistema en sı́ mismo, puesto que corre
El coprocesador Intel
una distribución completa del sistema operativo Linux, soporta el modelo x86 de ordenamiento
de memoria y el estándar IEEE 754 de aritmética en punto flotante. Ademas es capaz de ejecutar
aplicaciones escritas en lenguajes de programación propios de la industria del HPC como es el caso
de Fortran, C y C++. Esto permite proporcionar con el producto un rico entorno de desarrollo
que incluye compiladores, numerosas librerı́as de apoyo (siendo de especial importancia aquellas
con soporte multi-thread y operaciones matemáticas para HPC) y herramientas de caracterización
y depurado.
R Xeon, denominado ”host”, a través de un bus PIC
Está conectado a un procesador Intel
Express. Véase Figura 2.6 (pág. 9). Dado que el coprocesador ejecuta de forma autónoma el
sistema operativo Linux, es posible virtualizar una comunicación tcp/ip entre éste y el procesador,
permitiendo al usuario acceder como si fuera un nodo más en la red. Por tanto, cualquier usuario
puede conectarse al mismo a través de una sesión ssh (secure shell) y ejecutar sus aplicaciones.
Además, soporta aplicaciones heterogéneas en las que una parte de la misma se ejecutarı́a en
el host y otra en la propia tarjeta. Ni qué decir tiene que se pueden conectar más de un Xeon
1 Se aplica a los conjuntos de computadoras construidos mediante la utilización de elementos hardware comunes
y que se comportan como si fuesen una única computadora
2 Cantidad de trabajo que un ordenador puede hacer en un periodo de tiempo determinado
R XEON PHITM COPROCESSOR
2.3. INTEL
9
R Xeon PhiTM
Figura 2.5: Intel
Phi en un mismo sistema, pudiéndose establecer entre ellos la comunicación ya sea a través de la
interconexión p2p (peer to peer) o a través de la tarjeta de red del sistema, sin intervención en
ambos casos del host.
Figura 2.6: Esquema general
2.3.1.
Microarquitectura
R Xeon Phi esta formado por más de 50 núcleos de procesamiento,
El coprocesador Intel
memorias cache, controladores de memoria, lógica de cliente PCIe y un anillo de interconexión
bidireccional que proporciona un elevado ancho de banda al sistema. Véase Figura 2.7 (pág. 10).
La ejecución es en orden mientras que la terminación es en desorden. Cada core consta de una L2
privada que mantiene completamente la coherencia con el resto gracias a un directorio de etiquetas
distribuido denominado TD, del inglés Tag Directory. Los controladores de memoria y la lógica
de cliente PCIe proporcionan una interfaz directa con la memoria GDDR5 del coprocesador y el
bus PCIe respectivamente. Ademas, cada core fue diseñado para minimizar el uso de energı́a a la
vez que maximiza el throughput en programas altamente paralelos. Usan un pipeline en orden y
soportan hasta 4 hilos hardware.
10
CAPÍTULO 2. ESTADO DEL ARTE
Figura 2.7: Microarquitectura
VPU
R Xeon PhiTM es la VPU.
Un importante componente de cada núcleo del coprocesador Intel
Véase Figura 2.8 (pág. 10). La VPU cuenta con un repertorio de instrucciones SIMD de 512 bits,
R Initial Many Core Instructions (Intel
R IMCI). Por ello puede
oficialmente conocido como Intel
ejecutar 16 operaciones de simple precisión (SP) u 8 de doble precisión (DP) por ciclo. También
soporta instrucciones Fused Multiply-Add (FMA), que ordenan multiplicar y sumar en la misma
instrucción, y gracias a las cuales se pueden ejecutar 32 instrucciones de simple precisión o 16 de
punto flotante por ciclo. Ni qué decir tiene que proporciona soporte para operaciones con enteros.
Figura 2.8: Vector Processing Unit
Las unidades vectoriales proporcionan una evidente mejora energética en la ejecución de aplicaciones HPC, ya que una única operación codifica una gran cantidad de trabajo, a la vez que no
incurre en el coste adicional de energı́a que supondrı́an las etapas de fetch, decode y retire para
la ejecución de múltiples instrucciones. Sin embargo, hicieron falta varias mejoras para lograr soportar instrucciones SIMD de estas caracterı́sticas. Por ejemplo, se añadió el uso de máscaras a la
VPU para permitir predecir sobre qué datos operar dentro de un registro vectorial. Esto ayudó en
la vectorización de bucles con flujos de ejecución condicionales, mejorando ası́ la eficiencia software
del pipeline. La VPU tambien soporta instrucciones de tipo gather y scatter directamente a través
del hardware. De este modo, para aquellos códigos con patrones de acceso a memoria esporádicos
e irregulares, el uso de este tipo de instrucciones ayuda a mantener el código vectorizado.
Finalmente, la VPU también cuenta con una EMU, del inglés Extended Math Unit, que puede
ejecutar instrucciones trascendentes como son las recı́procas, raı́ces cuadradas y logarı́tmicas. La
EMU funciona calculando aproximaciones polinómicas de estas funciones.
R XEON PHITM COPROCESSOR
2.3. INTEL
11
Interconexión
La interconexión se implementa como un anillo bidireccional. Véase Figura 2.9 (pág. 11). Cada
dirección está compuesta de tres anillos independientes. El primero, que se corresponde con el
más ancho y caro de los tres, es el anillo de datos. Este es de 64 bytes para soportar el requisito
de gran ancho de banda debido a la gran cantidad de cores presentes. El anillo de direcciones es
más estrecho y se utiliza para enviar comandos de lectura/escritura y direcciones de memoria.
Por último, el anillo más estrecho y barato es el anillo de reconocimiento, que envı́a mensajes de
control de flujo y coherencia.
Figura 2.9: Interconexion
Figura 2.10: Directorio de etiquetas
Cuando un core accede a su cache L2 y falla, una solicitud de dirección se envı́a sobre el
anillo de direcciones a los directorios de etiquetas. Véase Figura 2.10 (pág. 11). Las direcciones de
memoria se distribuyen de manera uniforme entre los distintos directorios que hay en el anillo para
añadir la fluidez de tráfico como una caracterı́stica más del mismo. Si el bloque de datos solicitado
se encuentra en la cache L2 de otro core, se dirige una petición a la L2 de ese core sobre el anillo
de direcciones. Finalmente, el bloque de solicitud es posteriormente reenviado sobre el anillo de
datos. Si los datos solicitados no se encuentran en ninguna de las caches, se envı́a la dirección de
memoria desde el directorio de etiqueta hasta el controlador de memoria.
La Figura 2.11 (pág. 12) muestra la distribución de los controladores de memoria en el anillo.
Como se aprecia, se intercalan de forma simétrica alrededor del él. La asignación de los directorios
de etiquetas a los controladores de memoria se realiza de forma todos-a-todos. Las direcciones
se distribuyen uniformemente a través de todos los controladores, eliminando de este modo los
hotspots y proporcionando un patrón de acceso uniforme esencial para un uso efectivo del ancho
de banda.
Volviendo al modo de funcionamiento, durante un acceso de memoria, cada vez que se produce
un error en el nivel L2 de cache en un core, éste genera una petición de dirección en el anillo
12
CAPÍTULO 2. ESTADO DEL ARTE
Figura 2.11: Controladores de memoria
de direcciones y consulta a los directorios de etiquetas. Si los datos no se encuentran en estos
directorios, el core genera otra solicitud de dirección y solicita los datos a la memoria. Una vez
que el controlador recibe el bloque de datos desde la memoria, se entrega al core a través del
anillo de datos. En todo el proceso los elementos trasmitidos a los anillos son: un bloque de datos,
dos solicitudes de dirección junto con dos mensajes de confirmación. Debido a que los anillos de
datos son los más caros y están diseñados para soportar el ancho de banda requerido, es necesario
incrementar el número de anillos de dirección y reconocimiento, más baratos en comparación, en
un factor de dos para soportar las necesidades de ancho de banda causadas por el elevado número
de peticiones sobre los anillos.
Caches
R MIC invierte en mayor medida tanto en caches L1 como L2 en compaLa arquitectura Intel
R Xeon PhiTM implementa un subsistema
ración con las arquitecturas GPU. El coprocesador Intel
de memoria en el que cada core está equipado con una cache de instrucciones L1 de 32KB, una
cache de datos L1 de 32KB y una cache L2 unificada de 512KB. Son totalmente coherentes e
implementan el modelo de orden de memoria x86. Las caches L1 y L2 proporcionan un ancho de
banda agregado que es entre 15 y 7 veces, respectivamente, más rápido que el ancho de banda de
la memoria principal. Por lo tanto, el uso efectivo de esta jerarquı́a es clave para lograr el máximo
rendimiento en el coprocesador. Además de mejorar el ancho de banda, son también más eficientes
que la memoria principal en cuanto al uso de energı́a para el suministro de datos al core. En la era
de la computación exascale 3 , las caches jugarán un papel crucial a la hora de conseguir maximizar
el rendimiento bajo estrictas restricciones de potencia.
R
Imágenes cortesı́a de Intel.
3 La
computacion exascale se refiere a los sistemas de computacion capaces de alcanzar un exaFLOPS.
R ADVANCED VECTOR EXTENSIONS
2.4. INTEL
2.4.
13
R Advanced Vector Extensions
Intel
AVX, del inglés Advanced Vector Extensions engloba, como veı́amos en la Sección 2.2.1 (pág. 6),
el conjunto de extensiones sobre la arquitectura del repertorio de instrucciones x86, propuestas
por primera vez por Intel en Marzo de 2008 tanto para procesadores de Intel como de AMD. El
primer producto en soportarlo fue el procesador Sandy Bridge de Intel en el primer cuarto de
2011, seguido por el procesador Bulldozer de AMD en el tercer cuarto del mismo año.
2.4.1.
R Advanced Vector Extensions 1
Intel
R Advanced Vector Extensions 1 (AVX) mejoraban las extensiones SSE
Las extensiones Intel
mediante el incremento del ancho del banco de registros SIMD de 128 bits a 256 bits. El nombre
de los registros, XMM0-XMM7, se cambió en consecuencia de YMM0-YMM7 (en el caso de x8664, YMM0-YMM15). Sin embargo, en los procesadores con soporte AVX, las instrucciones de la
extensión SSE podı́an ser usadas para operar en los 128 bits menos significativos de los registros
YMM. Entonces podı́a seguir usándose la nomenclatura XMM0-XMM7.
AVX introdujo además un formato de instrucción SIMD de tres operandos donde el registro
de destino podı́a ser distinto a los dos registros fuente. Por ejemplo, una instrucción SSE usando
la forma convencional a = a + b, podı́a ahora utilizar el método de tres operandos c = a + b,
impidiendo que se destruyera la información almacenada en alguno de ellos como ocurrı́a hasta
el momento. Este formato estaba limitado a las instrucciones que utilizan los registros YMM, no
incluyendo por tanto instrucciones con registros de propósito general (por ejemplo EAX).
2.4.2.
R Advanced Vector Extensions 2
Intel
R Advanced Vector Extensions 2 (AVX2) mejoraban el set de extensiones
Las extensiones Intel
R Haswell. La compañı́a
AVX, y fueron introducidas por primera vez en la microarquitectura Intel
amplió por tanto el juego AVX con nuevas instrucciones que funcionaban también sobre números
naturales, ampliando casi la totalidad del conjunto SSE de 128 bits a 256 bits. El formato no
destructivo de tres operandos estuvo ahora también disponible para instrucciones a nivel de bits
y multiplicación de propósito general y para instrucciones FMA (Fused Multiply-Accumulate).
Finalmente, esta nueva ampliación permitió realizar instrucciones gather, lo que significarı́a la
posibilidad de acceder a la vez a varias posiciones no contiguas en memoria, aumentando considerablemente las capacidades de procesado vectorial de la arquitectura x86-64.
2.4.3.
R Advanced Vector Extensions 512
Intel
R Advanced Vector Extensions 512, AVX-512, son las extensiones a 512 bits de las insIntel
trucciones SIMD recogidas en las Advanced Vector Extensions de 256 bits. Fueron propuestas
R Xeon PhiTM denominado
por Intel en Julio de 2013 para ser incluidas en el coprocesador Intel
Knights Landing que se espera lanzar al mercado en el año 2015[inta]. No todas las extensiones están destinadas a ser soportadas por todos los procesadores que las implementen. Sólo la
extensión del núcleo AVX-512F (AVX-512 Foundation) se requiere para todas las implementaciones. Atendiendo al repertorio de instrucciones y a las principales caracterı́sticas de AVX-512, las
extensiones se clasifican del siguiente modo:
AVX-512 Foundation: expande la mayorı́a de instrucciones AVX de 32 y 64 bits con el
esquema de codificación EVEX para soportar los registros de 512 bits, las operaciones con
14
CAPÍTULO 2. ESTADO DEL ARTE
máscaras, la difusión de parámetros y las excepciones de control y redondeo empotradas.
AVX-512 Conflict Detection Instructions (CDI): añade detección de conflictos eficiente para permitir que más bucles puedan ser vectorizados.
AVX-512 Exponential and Reciprocal Instructions (ERI): operaciones exponenciales
y recı́procas diseñadas para ayudar en la implementación de operaciones trascendentes, como
por ejemplo la función de logaritmo.
AVX-512 Prefetch Instructions (PFI): soporte para prefetches.
En cuanto a las caracterı́sticas técnicas, se resumen en los siguientes puntos:
32 registros vectoriales de 512 bits de ancho bajo la nomenclatura ZMM0-ZMM31.
8 registros dedicados a las máscaras, lo cual es de especial trascendencia para las instrucciones
gather y scatter.
Operaciones de 512 bits sobre datos empaquetados enteros y de punto flotante.
Los programas podrán entonces empaquetar en los nuevos registros de 512 bits cualquiera de
las siguientes combinaciones de datos: 8 datos en punto flotante de precisión doble, o 16 datos en
punto flotante de precisión simple, u 8 enteros de 64 bits o 16 enteros de 32 bits. Esto permitirá el
procesamiento del doble de elementos que el AVX/AVX2 con una sola instrucción y cuatro veces
el de SSE.
R AVX-512 ofrece un nivel de compatibilidad con AVX
Es interesante resaltar que Intel
muchı́simo mayor que las transiciones anteriores sobre el ancho de las operaciones. A diferencia de
lo que ocurre con SSE y AVX, que no se pueden mezclar sin penalizaciones en el rendimiento, la
mezcla de instrucciones AVX y AVX-512 es posible sin penalización alguna. Los registros YMM0YMM15 de AVX se mapean en los registros ZMM0–ZMM15 de AVX-512 del mismo modo que se
mapeaban los registros SSE sobre AVX. Por lo tanto, en procesadores que soporten AVX-512, las
instrucciones AVX y AVX2 operarán en los 128 o 256 bits inferiores de los primeros 16 registros
ZMM.
Capı́tulo 3
Metodologı́a
Las ideas que surgieron a la hora de definir el anteproyecto del presente Proyecto Final de
Carrera lo describı́an claramente como un trabajo con una fuerte carga de análisis. Comprendı́a
desde el análisis de todas y cada una de las herramientas a utilizar, hasta el análisis de cada resultado, cada gráfica elaborada, cada bloque básico implicado y cada lı́nea de código que supusiera
un objeto de interés sobre el que adentrarse. Por este motivo no se aplicó una estrategia especı́ficamente etiquetada que seria propia de un trabajo vinculado a la rama de ingenierı́a del software.
Ciertamente, parte de este trabajo consistı́a en desarrollar un simulador sobre el que ejecutar las
aplicaciones, con el objetivo de obtener más estadı́sticas aparte de aquellas conseguidas gracias a
R Sin embargo, incluso el tomar la
las herramientas ya disponibles para los ingenieros de Intel.
decisión de incorporar el desarrollo de este simulador como una extensión de otro ya existente,
supuso un fuerte trabajo de análisis para tratar de reciclar la mayor cantidad de información ya
disponible. Esta información se encontraba almacenada, en su mayor parte, sobre una gran variedad de estructuras y clases que evitaron, ya no solo no reinventar la rueda, sino también impedir
sobrecargar al simulador con operaciones y tareas que eran necesarias y que por supuesto ya se
estaban realizando.
En este capı́tulo, se describirá la forma de trabajo, es decir aquellas actividades que supusieron
una importante parte para la consecución del trabajo, cómo se distribuyeron todas las tareas a
realizar y el tipo de metodologı́a usada cuando se procedió al desarrollo de la nueva extensión del
simulador.
3.1.
Plan de trabajo
Al plan de trabajo diseñado inicialmente y presentado en el anteproyecto se le aplicaron modificaciones sin que ello repercutiese en el computo total de horas. A continuación se presenta la
planificación final y las justificaciones sobre los cambios en el caso de haberlos.
Fase 1: Selección y caracterización de benchmarks
1. Selección del conjunto de benchmarks sobre los que realizar el estudio a partir de los disponibles, como NPB, Polyhedron, PARSEC, etc.
2. Compilar las aplicaciones de los benchmarks para la arquitectura x86 con la extensión AVX512 para la obtención de una caracterización inicial. Utilizando los compiladores disponibles,
15
16
CAPÍTULO 3. METODOLOGÍA
como ICC e IFORT, se realiza la compilación del conjunto de aplicaciones seleccionadas utilizando aquellas opciones que permitan realizar optimizaciones y vectorización del código.
Implica un pequeño análisis mediante el parsing del informe generado, para tener una aproximación inicial al comportamiento de cada aplicación.
Esta fase se redujo a la selección y posterior caracterización de las aplicaciones. El motivo por
el que no se realizó una criba inicial radica en que al principio, sin más datos que los disponibles
estáticamente con el informe del compilador, la mera descripción de la aplicación no parecı́a
suficiente para descartar unas u otras. Era más interesante quedarnos con todas las disponibles
y, a partir de diferentes estadı́sticas, tomar decisiones sobre cuáles analizar según las soluciones
software o hardware a aplicar.
Fase 2: Recopilación y análisis de información sobre la ejecución vectorial de los
programas de prueba
1. Determinación del grado de vectorización de los programas: Usando el emulador Pin/SDE,
se obtiene el número de instrucciones ejecutado por cada programa y se determina el grado
de vectorización de los mismos.
2. Recopilación de información sobre la jerarquı́a de memoria: Usando el emulador CMP$im,
se obtiene la tasa de fallos de los diferentes niveles de la jerarquı́a de memoria para cada
uno de los programas de prueba.
3. Determinación del grado de utilización de la unidad vectorial: En este apartado se desaR Xeon PhiTM
rrollará un núcleo sencillo basado en la arquitectura del coprocesador Intel
sobre el simulador CMP$im, que permita obtener datos estadı́sticos para ası́ determinar el
grado de utilización de la unidad vectorial.
La única modificación planteada en esta fase consistió en realizar el desarrollo del simulador de
un core con arquitectura vectorial embebido dentro del simulador de cache CMP$im. El motivo
radica en que el simulador de cache proporciona mucha información de interés que puede utilizarse
para la simulación de la arquitectura. Además, contiene multitud de estructuras y clases que se
pueden utilizar a la vez que se introduce el código sobre el esqueleto correspondiente a la simulación
de cache.
En primer lugar se llevarı́a a cabo una exhaustiva fase de análisis sobre CMP$im, para conocer
en profundidad tanto la configuración de ficheros del simulador, como las estructuras y clases
usados, aparte del funcionamiento especı́fico de la simulación de la cache. Los objetivos principales
eran reutilizar estructuras, esquemas y clases ya presentes, saber de qué modo introducir el código
correspondiente al simulador de la arquitectura para no entorpecer la simulación ya hecha y poder
hacer uso de las estadı́sticas recopiladas.
Una vez conocidas las caracterı́sticas principales mencionadas, habı́a que proceder a la fase
de desarrollo de la extensión correspondiente a la simulación de la arquitectura vectorial. La
metodologı́a más apropiada para desarrollarlo serı́a incremental. Era preciso en todo momento
que el simulador fuese funcional. Por ello, cada vez que se incorporase una nueva funcionalidad,
esta debı́a protegerse con las macros correspondientes. Ademas se tenı́a que comprobar que todo
seguı́a funcionando correctamente antes de proceder a la incorporación del siguiente incremento.
Cada una de las funcionalidades se iba a discutir y diseñar en reuniones semanales, de manera que
al final de cada semana se tendrı́an tanto los progresos obtenidos como las dificultades encontradas.
Si todo estaba correcto, se tomaban las decisiones oportunas sobre las siguientes funcionalidades
a desarrollar.
3.1. PLAN DE TRABAJO
17
Fase 3: Determinación de cuellos de botella en la ejecución vectorial y propuesta de
soluciones
1. Selección de un subconjunto de aplicaciones numéricas de entre todos los benchmarks seleccionados, teniendo como referencia las estadı́sticas recolectadas. Para la selección se tomaron
como criterio tanto la relación entre las versiones escalar y vectorial, ası́ como el desglose de
ciclos de la aplicación según las dependencias ocasionadas durante la ejecución.
2. Con la información recolectada en los puntos anteriores se determinará cuáles son las regiones del código que tienen un bajo uso de la unidad vectorial. Se estudiará a posteriori
cuál es el motivo: si se trata de una falta de vectorización, si se produce por lı́mites en la
microarquitecura u otros.
3. Adicionalmente, se propondrán mejoras hardware y/o software encaminadas a aumentar el
rendimiento de estas regiones con bajo uso de la unidad vectorial.
En esta fase, se incluyó la selección de las aplicaciones, ya que a estas alturas están completamente caracterizadas, de manera que la información disponible permite tomar decisiones
basándonos exclusivamente en el comportamiento de las mismas.
Capı́tulo 4
Herramientas
En este capı́tulo se introducen las caracterı́sticas principales de las herramientas más significativas usadas a lo largo del proyecto.
4.1.
Pin
R destinada a
Pin1 es una herramienta de código abierto desarrollada por la empresa Intel,
la instrumentación de aplicaciones [pin]. Se denomina herramienta de Instrumentación Binaria
Dinámica porque la instrumentación se realiza en tiempo de ejecución (JIT, Just in Time):
No requiere que la aplicación a instrumentar se tenga que recompilar.
Permite instrumentar programas que generan código dinámicamente.
Se puede adherir a procesos que ya se estuvieran ejecutando.
Proporciona una extensa API para escribir aplicaciones tanto en C, C++ como ensamblador,
denominadas pintools, que permitirán instrumentar aplicaciones, ya sean estas single-threaded
o multi-threaded, compiladas para las arquitecturas IA-32 (x86 32-bit), IA-32E (x86 64-bit) y
R Dicha API permite al programador abstraerse de todas las peculiaridades
procesadores Itanium.
de la arquitectura para la que se haya compilado el binario. Por tanto, podrá utilizar información de
contexto, tales como el contenido de los registros o las direcciones de acceso a memoria, pasándola
como diferentes parámetros dentro del código que el proceso de instrumentación inserta en el
binario. Además, Pin salva y recupera el contenido de los registros que se utilizaran en dicho
código, de manera que no influyan en la ejecución normal del programa instrumentado.
Fue utilizada a la hora de estudiar y modificar el simulador que se va a utilizar para simular
las aplicaciones numéricas.
4.1.1.
Pintools
Una pintool, sea cual sea su funcionalidad, constará de dos secciones fundamentales en su
código:
1 No
es un acrónimo.
19
20
CAPÍTULO 4. HERRAMIENTAS
Instrumentación: contendrá las instrucciones necesarias que indiquen a Pin qué información
se quiere recoger del código que se está ejecutando, como códigos de registro, hilos en ejecución o contador de programa, entre otros. También indica en dónde se quieren insertar las
llamadas a las funciones y procedimientos que harán uso de toda la información recogida.
Análisis: contendrá la definición de todas las funciones y procedimientos que tratarán la
información recogida en la sección de instrumentación.
Además de estas dos secciones, también contendrá, como cualquier aplicación de C o C++, la
función main y, como novedad, un procedimiento denominado Fini que es invocado por Pin cuando
la aplicación termina. El motivo por el que se tiene que desarrollar este último procedimiento, reside
en que una vez hemos dado el control a Pin, no retorna a la función main para terminar.
Pin proporciona una amplia baterı́a de pintools con diferentes funcionalidades. En el Listado 4.1
(pág. 20) se muestra el código de una pintool denominada inscount0 que cuenta el número de
instrucciones ejecutadas de una aplicación. Se encuentra dentro de la extensa baterı́a de pintools
de ejemplo que acompañan a la herramienta. Es una muestra perfecta de la estructura más común
de una pintool, en donde se aprecian tanto las secciones principales descritas, como la definición
de knobs, opciones de la pintool y otras funciones de interés.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# include < iostream >
# include < fstream >
# include " pin . H "
ofstream OutFile ;
static UINT64 icount = 0;
// Seccion de analisis
VOID docount () { icount ++; }
// Seccion de in st r um en ta c io n
VOID Instruction ( INS ins , VOID * v ) {
INS_ InsertCa ll ( ins ,
IPOINT_BEFORE ,
( AFUNPTR ) docount ,
IARG_END ) ;
}
KNOB < string > K nobOutp utFile ( KNOB_MODE_WRITEONCE ,
" pintool " ," o " ,
" inscount . out " ,
" specify output file name " ) ;
VOID Fini ( INT32 code , VOID * v ) {
OutFile . setf ( ios :: showbase ) ;
OutFile << " Count " << icount << endl ;
OutFile . close () ;
}
INT32 Usage () {
cerr << " This tool counts the number of dynamic instructions executed " << ←endl ;
cerr << endl << KNOB_BASE :: S t r i n g K n o b S u m m a r y () << endl ;
return -1;
}
int main ( int argc , char * argv []) {
if ( PIN_Init ( argc , argv ) ) return Usage () ;
OutFile . open ( Knob OutputFi le . Value () . c_str () ) ;
I N S _ A d d I n s t r u m e n t F u n c t i o n ( Instruction , 0) ;
P I N _ A d d F i n i F u n c t i o n ( Fini , 0) ;
P I N _ S t a r t P r o g r am () ;
return 0;
}
Listado 4.1: Código de ejemplo de la pintool inscount0
4.1. PIN
21
Analizando más en detalle el Listado 4.1 se observan llamadas a funciones que forman parte
de la gran API proporcionada por pin:
PIN Init: inicializa Pin con los argumentos de entrada. Devuelve false si hay errores.
INS AddInstrumentFunction: registra qué función se encargará de la instrumentación a
nivel de instrucción.
PIN AddFiniFunction: registra qué función se invocará antes de que la aplicación instrumentada termine.
PIN StartProgram: arranca la ejecución de la aplicación. No se retorna.
INS InsertCall: registra qué función se ha de llamar cuando se encuentren instrucciones
candidatas a instrumentar. Entre los parámetros que se observan en Listado 4.2 tenemos:
• ins: instrucción a instrumentar.
• IPOINT BEFORE: se invocara el análisis antes de que se ejecute.
• docount: función de análisis.
• IARG END: fin de parámetros, tanto si los hubiera como si no. Estos parámetros son los
que pasarı́an a la función docount del ejemplo. Véase Listado 4.3.
1
2
3
4
INS_ InsertCa ll ( ins ,
IPOINT_BEFORE ,
( AFUNPTR ) docount ,
IARG_END ) ;
Listado 4.2: Función de instrumentación
1
2
3
4
5
6
7
8
9
10
11
VOID docount ( UINT32 threadId , ADDRINT pc ) { ... }
VOID Instruction ( INS ins , VOID * v )
{
INS_ InsertCa ll ( ins ,
IPOINT_BEFORE ,
( AFUNPTR ) docount ,
IARG_THREAD_ID ,
IARG_ADDRINT , INS_Address ( ins ) ,
IARG_END ) ;
}
Listado 4.3: Argumentos para la función de análisis
4.1.2.
Arquitectura software
En la Figura 4.1 se observa la arquitectura software de Pin [LCM+ 05]. El elemento principal es
la máquina virtual, que contiene un compilador just in time que se encarga de recompilar aquellas
porciones de la aplicación que se hayan indicado en la sección de instrumentación, sobre las que
se inyectará el código de la sección de análisis que le corresponda.
El dispatcher es el encargado de lanzar el código recién compilado que almacena en el área
denominada code cache. La emulation unit interpreta aquellas instrucciones que no puedan ser
ejecutadas directamente, como es el caso de las llamadas al sistema que tienen un tratamiento
particular dentro de la máquina virtual.
De entre las entradas que alimentan a Pin, se encuentran lógicamente tanto la pintool como la
aplicación. En el Listado 4.4 se observa el esqueleto general de la invocación.
22
CAPÍTULO 4. HERRAMIENTAS
Figura 4.1: Arquitectura software de Pin
1
pin [ pin_opts ] -t pintool . so [ pintool_opts ] -- app [ input ]
Listado 4.4: Ejemplo de invocación de Pin
4.2.
CMP$im
CMP$im (Chip Multi-Processor Cache Simulator) un simulador de cache desarrollado por
R
Intely
orientado a chips multiprocesador, cuyo objetivo es analizar el rendimiento de memoria
de aplicaciones tanto single-threaded, multi-threaded como multi-program. Fue desarrollada sobre
Pin, por lo que fundamentalmente es una pintool, aprovechando el perfil de Pin como herramienta de instrumentación binaria dinámica, que suponı́a una alternativa frente a otros métodos de
simulación como trace-driven basado en trazas [JCLJ06][JCLJ08].
Es interesante destacar que CMP$im es una herramienta muy rápida y fácil de utilizar. Además
proporciona una gran cantidad opciones, tanto estáticas de forma #define MACRO, como dinámicas
(knobs) de forma -knob valor, que la hacen flexible. Por ello permite configurar al detalle el
sistema de memorias cache que van a participar en la simulación. Entre estas opciones se destacan
las siguientes:
Número de niveles de cache.
Número de caches por nivel.
Número máximo de threads que se pueden lanzar con la aplicación.
Polı́ticas de escritura y reemplazo.
Caracterı́sticas particulares para cada cache, como tamaño, asociatividad, latencia, tamaño
de lı́nea, etc.
Latencia de la memoria principal, aunque no se simule.
4.3. BENCHMARKS
23
TLBs.
Inclusividad o exclusividad.
Una vez compilada la pintool con las macros deseadas, se lanza la simulación del mismo modo
especificado en el Listado 4.4 (pág. 22). La salida se compone de un informe con estadı́sticas
muy detalladas, que proporcionan tanto información general de la aplicación, como particular
desglosada por hilos de ejecución y por nivel de cache. Entre ellos, se muestran datos relativos a:
Número total de instrucciones ejecutadas y desglosadas por hilos.
Estimación2 del número de ciclos, donde la latencia de cada instrucción es, por defecto, de
un ciclo, añadiendo al total la latencia total generada por los accesos a memoria.
Número de accesos, aciertos, fallos y tasas de fallo desglosadas por nivel de cache y tipo de
acceso (load, store, write back, etc.).
R
Esta herramienta fue utilizada como base para desarrollar el núcleo del coprocesador IntelXeon
R
PhiTM . Imágenes cortesı́a de Intel.
4.3.
Benchmarks
El benchmarking es una técnica consistente en medir el rendimiento de un sistema o un componente del mismo, con el objetivo de realizar una comparativa con otro sistema similar de modo
que se tenga una referencia base sobre la que trabajar. De este modo, se podrı́a saber si la máquina
obtiene buenos resultados o no, de cara a utilizarlos para el fin que convenga.
Aplicado en el campo de la informática consistirı́a en la ejecución de aplicaciones especı́ficamente diseñadas para medir el rendimiento de una máquina o de uno de sus componentes. Por
ello, de cara a este trabajo el uso de benchmarks software constituye una piedra angular para
poder determinar el grado de utilización efectiva de la unidad vectorial de un procesador.
Los benchmarks se pueden clasificar en diferentes categorı́as. Veamos una posible clasificación:
Benchmarks basados en el nivel del rendimiento que miden:
• Benchmarks de bajo nivel o nivel componente: se encargan de medir directamente un
componente especı́fico del sistema como, por ejemplo, la memoria RAM, la tarjeta
gráfica o el procesador.
• Benchmarks de alto nivel o nivel sistema: evalúan el rendimiento global de una máquina.
Este tipo es interesante para comparar sistemas que se basan en arquitecturas distintas.
Benchmarks basados en el código que los componen:
• Benchmarks sintéticos: creados especı́ficamente combinando diferentes funciones del
sistema a probar, en las proporciones que los desarrolladores estiman oportunas. El
objetivo es conseguir medir determinados aspectos del sistema. Esta descripción se
asocia rápidamente a los benchmarks de tipo bajo nivel ya que, por ejemplo, para
medir el rendimiento del funcionamiento del disco, se pueden incluir funcionalidades de
lectura, escritura o búsqueda de datos en disco en el benchmark.
2 No
implementa ningún mecanismo que haga uso del paralelismo de instrucciones (ILP).
24
CAPÍTULO 4. HERRAMIENTAS
• Benchmarks de aplicación: hacen uso de aplicaciones reales. En este caso los desarrolladores del benchmark pueden tener interés en utilizar determinadas aplicaciones
que realizan funciones enfocadas a una industria concreta o a un determinado tipo
de producto. En este caso, se asocian con los benchmarks de alto nivel ya que este
tipo de aplicaciones miden el rendimiento global del sistema, pudiendo analizar cómo
contribuye cada componente al dicho rendimiento.
Independiente de esta clasificación, se pueden tipificar siguiendo otros patrones más especı́ficos.
Por ejemplo, existen benchmarks que sirven para medir el rendimiento de máquinas con múltiples
núcleos en su procesador o con múltiples procesadores. Existen otros benchmarks para medir la
respuesta de las consultas sobre una base de datos.
Existen una gran cantidad de benchmarks disponibles hoy en dı́a, entre los cuales podemos
mencionar los siguientes a modo de ejemplo:
Whetstone: considerado el padre de los benchmarks sintéticos, fue creado en el Laboratorio
nacional de Fı́sica de Inglaterra. Su objetivo inicial era servir como test para el compilador
ALGOL 60 aunque hoy en dı́a forma parte de otros benchmarks.
3DMark: benchmark sintético creado por la compañı́a Futuremark Corporation, con el objetivo de medir la capacidad de rendering sobre gráficos 3D que tiene la GPU de una máquina,
ası́ como la capacidad de procesamiento de la CPU.
Ciusbet: creado por Ciusbet. Es un benchmark que se compone de un gran número de
pruebas para probar diferentes componentes de una máquina, como la memoria cache, la
CPU, el disco duro, etc.
FurmKar: benchmark sintético que mide el rendimiento de una tarjeta gráfica al través de
la ejecución de un algoritmo de renderizado de pelaje. Su peculiaridad es que, al tratarse de
un algoritmo que somete a la GPU a un nivel de estrés muy fuerte, permite medir muy bien
la capacidad de aguante y estabilidad de la tarjeta.
El conjunto de benchmarks que se presenta a continuación es una muestra continente de variedad de aplicaciones numéricas usadas comúnmente en el ámbito de la computación de alto
rendimiento. Todos los programas se caracterizan por ser o bien Free software, o bien Open source.
4.3.1.
Polyhedron Fortran Benchmarks
Polyhedron[pol] es un paquete de 17 programas escritos en Fortran 90, diseñados para comparar el rendimiento de los diferentes ejecutables generados por distintos compiladores. Todos los
programas se pueden descargar y hacer uso de ellos según convenga. El paquete que actualmente
está disponible para descarga, se denomina pb11. Sin embargo, para el presente trabajo hicimos
uso de 15 aplicaciones del antiguo repertorio, denominado pb05, debido a que el conjunto de datos de entrada permitı́a una mayor rapidez de cara a la simulación completa de los programas.
El nuevo benchmark tiene conjuntos de datos que ralentizaban demasiado su simulación. Las 15
aplicaciones son las siguientes:
ac
channel
induct
protein
armod
doduc
linpk
test fpu
air
fatigue
mdbx
tfft
capacita
gas dyn
nf
4.3. BENCHMARKS
4.3.2.
25
Mantevo 1.0
Mantevo[man] es un benchmark que proporciona una interesante variedad de aplicaciones
clasificadas en:
Miniapplications: partiendo de la idea de que la medición del rendimiento de las aplicaciones
viene determinada por una combinación de diferentes opciones, proporcionan una aproximación excelente para explorarlas. Las opciones mencionadas englobarı́an las siguientes: el
compilador usado, el hardware de la máquina a medir, el algoritmo, el entorno de ejecución,
el uso de miniaplicaciones, definidas como pequeños proxies autocontenidos para aplicaciones
reales, etc.
Minidrivers, pequeñas aplicaciones que sirven para simular el funcionamiento de diferentes
controladores.
Application proxies, aplicaciones parametrizables cuyo objetivo es simular el comportamiento
de aplicaciones a gran escala.
Todas ellas realizan mayoritariamente operaciones en coma flotante para, por ejemplo, resolver
ecuaciones diferenciales en derivadas parciales tanto implı́citas como explı́citas, simular modelos
de dinámica molecular que implican operaciones sobre vectores, etc. Las aplicaciones de que se
compone son las siguientes:
CloverLeaf
miniFE
CoMD
miniMD
HPCCG-200
miniXyce
miniGhost
De los tres conjuntos de ficheros de entrada disponibles para realizar las simulaciones, nos
hemos quedado con los mediums, con el objetivo de tener los resultados en tiempos razonables.
4.3.3.
ASC Sequoia Benchmark Codes
Los investigadores del Laboratorio Nacional Lawrence Livermore (LLNL), con motivo del programa ASC (Advanced Simulation and Computing) de la Administracion Nacional de Seguridad
Nacional (NNSA) de Estados Unidos, llevan a cabo multitud de simulaciones sobre el supercomputador de IBM denominado Sequoia. Dentro de los recursos que están disponibles en la plataforma
online dedicados a este programa y a los trabajos realizados sobre el supercomputador, se encuentra todo un interesante repertorio de aplicaciones[seq]. De entre todas ellas, y dado que solo se iba
R Xeon PhiTM , se seleccionaron solamente las
a simular uno de los núcleos del coprocesador Intel
aplicaciones correspondientes a la sección Tier 3, que se caracterizan por ser single-threaded :
UMTmk
4.3.4.
IRSmk
SPhotmk
Crystalmk
NAS Parallel Benchmarks
Los NAS Parallel Benchmarks[nas] (NPB) son un conjunto de aplicaciones destinadas a la medición del rendimiento de supercomputadores paralelos. Fueron desarrolladas por la división NAS
(NASA Advanced Supercomputing). Inicialmente, en la especificación NPB 1, estaba conformado
por 5 kernels y 3 pseudo aplicaciones. Más adelante fue extendida para incluir nuevos benchmarks
sobre mallas adaptativas, aplicaciones E/S paralelas y redes computacionales. Se utilizó la versión
3.3.1 que contiene las siguientes aplicaciones:
26
CAPÍTULO 4. HERRAMIENTAS
BT
IS
CG
LU
DC
MG
EP
SP
FT
UA
Los diferentes inputs se categorizan en las siguientes clases:
Class S : para pequeñas pruebas.
Class W : destinada a estaciones de trabajo.
Classes A, B, C : test de mayor tamaño, cada uno de los cuales es aproximadamente 4x
superior al anterior.
Classes D, E, F : en este caso son entradas muy grandes, del orden de los 16x de incremento
entre un test y el siguiente.
Para el presente caso, era suficiente utilizar la clase W, puesto que el número de instrucciones
ejecutadas se encontraba en el orden de magnitud de los otros benchmarks seleccionados.
4.3.5.
SPEC CPU 2006
La Standard Performance Evaluation Corporation (SPEC), es una organización sin ánimo de
lucro cuyo objetivo es producir, establecer, mantener y promocionar un paquete estándar de benchmarks para medir el rendimiento de diferentes máquinas. En este sentido, se dispone del conjunto
de benchmarks denominado SPEC CPU2006 diseñado para proporcionar una medida comparativa, con el objetivo de analizar el rendimiento conseguido después de realizar cálculos intensivos
sobre una máquina. El conjunto de aplicaciones que lo conforman fueron desarrolladas basadas en
aplicaciones de usuario reales. Los resultados que se obtengan serán fuertemente dependientes del
procesador, la memoria y el compilador utilizado.
Dado que el presente trabajo está centrado en el estudio efectivo de un procesador vectorial, nos
centramos fundamentalmente en uno de los dos suites en que se divide: CFP2006, que sirve para
medir el rendimiento de las operaciones en punto flotante. El otro suite, CINT2006 está enfocado
a operaciones enteras. Las aplicaciones son las siguientes:
410.bwaves
435.gromacs
447.dealII
459.GemsFDTD
482.sphinx3
416.gamess
436.cactusADM
450.soplex
465.tonto
433.milc
437.leslie3d
453.povray
470.lbm
434.zeusmp
444.namdP
454.calculix
481.wrf
Como datos de entrada, están disponibles los siguientes:
all : común para todos los benchmarks, se usa en caso de ser necesario.
ref : es el conjunto de datos real y completo.
test: entrada para tests más sencillos.
train: tests más grandes.
Para nuestras necesidades, basta con usar test.
4.4. COMPILADORES
4.4.
27
Compiladores
Los compiladores son una parte fundamental de este trabajo, puesto que son los responsables de
generar el código necesario con el que se trabajara después. Si bien todos los elementos expuestos
en este capı́tulo son indispensables para conseguir las sinergias que permitirán completar todos
los objetivos definidos en la Sección 1.1 (pág. 2), los compiladores se alzan con la responsabilidad
suprema. En el caso particular de los optimizadores de que constan, son responsables de generar un
código adecuado con el que se pueda partir, trabajar y mejorar en caso necesario. Si no fuera ası́,
ningún resultado tendrı́a la fiabilidad suficiente. Por estos motivos trabajamos con los compiladores
R C++ Compiler e Intel
R Fortran Compiler.
Intel
Una de las ventajas principales de trabajar con estos compiladores y de realizar este trabajo
R es que se disponı́a en todo momento de la última versión de los comen la propia empresa Intel,
piladores. Por tanto, este trabajo servı́a también como depurador de los cambios introducidos en
cada versión. La versión utilizada en las simulaciones se corresponde con la de Mayo de 2013. Para
las simulaciones realizadas durante los meses de Junio y Julio utilizamos la misma para mantener
la coherencia en los resultados. Utilizar otro implicarı́a obtener mejoras que no recaerı́an sobre los
cambios en configuraciones utilizadas al simular, sino en las propias mejoras del compilador.
El código generado al compilar contiene las extensiones AVX-512 descritas en la Sección 2.4
(pág. 13). Asimismo, mientras los compiladores están disponibles en multitud de plataformas, en
este trabajo se usó la versión para Linux.
Los compiladores fueron usados a la hora de construir la caracterización de todas y cada una de
las aplicaciones, y para tener los ejecutables disponibles en el momento de proceder a la simulación
con la versión de CMP$immodificada.
4.4.1.
ICC
R ICC permite generar código sobre arquitecturas IA-32, Intel64
R
R
El compilador Intel
e Intel
MIC (Multiple Integrated Core) [intc], disponibles para los sistemas operativos Mac OS X, Linux y MicrosoftTM Windows. Contiene soporte para la vectorización de aplicaciones, pues puede
generar instrucciones de los repertorios SSE (Streaming SIMD Extensions), SS2, SSE3, SSSE3
(Suplemental Streaming SIMD Extensions), SSE4, AVX(Advanced Vector Extensions) y AVX2.
Las versiones internas del compilador, además, permitı́an generar código AVX-512. El vectorizador
automático es un componente importante del compilador, ya que utiliza automáticamente instrucciones SIMD (Simple Instruction Multiple Data) de los repertorios de instrucciones mencionados
anteriormente. Se encarga de detectar aquellas operaciones en el programa que se pueden vectorizar para explotar el procesamiento automático de las instrucciones de tipo SIMD. El usuario puede
ayudar a este módulo mediante el uso de pragmas. Consúltese el conjunto de pragmas disponibles
en la Sección 4.5 (pág. 29)
ICC tiene multitud de knobs de compilación[intc]. Para este trabajo, el formato de las mismas
se corresponde con aquel para Linux. A continuación se mostrarán las opciones más significativas
usadas en este trabajo. La lı́nea de compilación tiene el formato mostrado en el Listado 4.5. La
opción -no-vec que se observa en la lı́nea de compilación, se usaba para indicar al compilador
que no vectorizase. Como se verá más adelante, la versión no vectorizada de las aplicaciones es de
interés porque sirven de referencia para compararlas con la versión vectorizada.
1
2
3
icc -g - debug inline - debug - info -vec - report6
- ansi - alias - O3 -no - prec - div - ipo - static
- xKNL [ - no - vec ] -o < ouput > < inputfiles >
Listado 4.5: Lı́nea de compilación
28
CAPÍTULO 4. HERRAMIENTAS
-g
Produce información para depuración simbólica en el
fichero objeto.
-debug inline-debug-info
Genera información de depuración mejorada tanto en
el caso de código del que se haga inline como en el
rastro generado por sucesivas llamadas a funciones.
-vec-report6
Genera un log con toda la información concerniente a los bucles y bloques que han sido vectorizados,
ası́ como de las razones porque otros no lo han sido.
-ansi-alias
Indica al compilador que compile bajo las reglas de
aliasability estándares de la ISO de C.
-O3
Aparte de las optimizaciones de la opción -O2, incluye prefetching, transformación de bucles y sustitución de escalares.
-no-prec-div
Permite optimizaciones que, a cambio de divisiones
algo menos precisas que las operaciones de división
en sı́ mismas, sustituye estas por multiplicaciones.
Por ejemplo, en vez de A/B, se calcuları́a A*(1/B)
-ipo
Interprocedural Optimization. Indica al compilador
que haga inline de las llamadas a funciones que se
encuentran en otros ficheros. Un ejemplo serı́a en
aquellas llamadas dentro de bucles. Por defecto el
valor es 0, esto es que se dejara al compilador decidir
si crear uno o más ficheros objetivo dependiendo de
una estimación del tamaño de la aplicación.
-static
Impide enlazar con librerı́as compartidas. En su lugar, enlaza todas las librerı́as estáticamente.
-xKNL
La arquitectura para la que se está generando código
es KNL, que incluye AVX-512.
-no-vec
Impide al compilador hacer uso del módulo de vectorización. El log resultante de aplicar esta opción no
contendrı́a más información que la lı́nea de compilación usada y los ficheros compilados.
R ICC
Tabla 4.1: Knobs soportados por Intel
4.4.2.
IFORT
R al igual que ICC, permite compilar aplicaciones sobre las
El compilador Fortran de Intel
R 64 y IntelMIC
R
arquitecturas IA-32, Intel
(Multiple Integrated Core) [intc], disponibles para
los sistemas operativos Mac OS X, Linux y MicrosoftTM Windows. Igualmente, tiene caracterı́sticas
análogas al compilador ICC, como es el soporte para la vectorización de aplicaciones, y comparte
la mayorı́a de knobs disponibles.
La lı́nea de compilación solo difiere en la opción -fpp, la cual sirve para indicar al compilador
4.5. PRAGMAS
29
que corra el preprocesador de Fortran sobre los ficheros fuentes antes de realizar la compilación.
El resto permanecen invariantes.
1
2
3
ifort - fpp -g - debug inline - debug - info -vec - report6
- ansi - alias - O3 -no - prec - div - ipo - static
- xKNL [ - no - vec ] -o < ouput > < inputfiles >
Listado 4.6: Lı́nea de compilación
4.5.
Pragmas
Los pragmas son directivas que sirven para especificar qué tiene que hacer el compilador en
determinadas situaciones. Estas instrucciones pueden tener efectos a nivel global o a nivel local.
Por ejemplo, para el caso del presente trabajo, puede ser necesario el uso del pragma vector para
indicar al compilador que un bucle concreto tiene que ser vectorizado, en cuyo caso es un pragma
a nivel local.
Los pragmas no forman parte de un lenguaje de programación, ya que no figuran en su gramática, pero algunos lenguajes, como C++ y Fortran, tienen disponible palabras clave para su uso, las
cuales son tratadas por el preprocesador. En C++ es #pragma y en Fotran es !DIR$. Además, hay
que tener en cuenta que los pragmas son dependientes tanto de la máquina como del sistema
operativo, aparte de que cada compilador tiene su propio conjunto. Es también posible que la
funcionalidad proporcionada por un pragma se consiga con alguna opción particular de compilación. En caso de coincidir en funcionalidad las opciones del compilador con los pragmas, tienen
prioridad los segundos.
Los pragmas, que se pueden consultar en el Apéndice A (pág. 105), el Apéndice B (pág. 109)
R como de otras
y el Apéndice C (pág. 113), están disponibles tanto para procesadores Intel
R lleven a cabo optimizaciones adicionales.
empresas, pero es posible que en procesadores Intel
Hay que tener en cuenta que en el caso de Fortran, al hablar de los pragmas, en realidad se habla
de directivas y su listado no coincide más que en algunos casos con los de ICC. En el Listado 4.7
y el Listado 4.8, se visualizan las diferencias de sintaxis. Particularmente, los pragmas de ICC
tienen la siguiente clasificación:
R ICC Specific Pragmas: desarrollados especı́ficamente para trabajar con el comIntel
R C++.
pilador Intel
R ICC Supported Pragmas: desarrollados por fuentes externas que, por razones
Intel
de compatibilidad, son soportados por este compilador.
1
# pragma nombre [ parametros ]
Listado 4.7: Sintaxis de los pragmas de ICC
1
( c | C |!|*) ( DEC | DIR ) $ directiva [ parametros ]
Listado 4.8: Sintaxis de las directivas de Fortran
4.6.
Herramientas internas
Toda empresa dispone de un conjunto de herramientas que han sido desarrolladas por los propios empleados. Estas suelen tener fines exclusivamente internos e incluso de uso restringido en
30
CAPÍTULO 4. HERRAMIENTAS
la propia organización, al poner su disponibilidad bajo la previa aprobación del jefe del grupo.
R es una empresa muy grande, de importante capital y con gran cantidad de recursos dispoIntel
nibles para sus empleados, incluyendo las herramientas desarrolladas por los diferentes grupos de
trabajo. Por este motivo, previa autorización, tuve acceso a un conjunto de ellas de cara a facilitarme la tarea durante el desarrollo de este trabajo. Estas herramientas facilitaban el análisis de
las aplicaciones, al presentar todos los datos procedentes de los informes generados por diferentes
simuladores, como es el caso de CMP$im y otras pintools, y del informe del compilador, de un
modo más legible y fácil de estudiar, que lo que un fichero de texto con multitud de números y
lı́neas permite.
La herramienta que se usó principalmente permitı́a, para cada unas de las aplicaciones simuladas, entre otras funcionalidades, las siguientes:
Analizar los bloques básicos individualmente.
Consultar el código fuente asociado a cada bloque básico.
Visualizar el flujo de ejecución del programa.
Consultar la distribución de funciones e instrucciones.
Sintetizar la información procedente de cada nivel de la cache.
Todas ellas se usaron principalmente durante la caracterización de las aplicaciones y a la hora
de realizar el diagnóstico software para averiguar las causas del bajo grado de vectorización.
Capı́tulo 5
Arquitectura del Simulador
En el presente capı́tulo se hará un análisis más en detalle del funcionamiento del simulador
CMP$im presentado en la Sección 4.2 (pág. 22). Debido a que es una pintool y que esta ı́ntimamente relacionado con la herramienta Pin, habrá detalles de Pin que será necesario exponer para
comprender mejor el esquema general del simulador.
5.1.
Flujo de ejecución
Las dos secciones principales en las que se organiza una pintool, como se describieron en la
Sección 4.1.1 (pág. 19), son las secciones de instrumentación y de análisis. CMP$im no es una
excepción. En la Figura 5.1 (pág. 31) se presenta una visión general de cómo funciona.
En primer lugar tenemos la función main, donde se recogen las opciones de configuración de
la pintool introducidas por parámetro y tanto la aplicación objeto del análisis como sus datos de
entrada. Aquı́ también se llevan a cabo las inicializaciones pertinentes según la configuración de
cache que se haya elegido. Una vez se lanza la simulación no se retorna. Por ello, la finalización de
la aplicación está ligada a una función de terminación que sera invocada por Pin en el momento
oportuno.
Figura 5.1: Diagrama de funcionamiento CMP$im
31
32
CAPÍTULO 5. ARQUITECTURA DEL SIMULADOR
En segundo lugar tenemos el bloque principal compuesto por las fases de instrumentación y
análisis. Pese a que la instrumentación es la fase con la que se inicia, ambas se van intercalando
a medida que se avanza en la simulación. Durante esta fase se van insertando las llamadas a las
funciones según el tipo de estructura con el que se esté tratando. Estas funciones se encargarán de
realizar la simulación de la cache. Asimismo, irán recogiendo todas las estadı́sticas que se hubieran
programado en ellas, como por ejemplo aquellas mencionadas en la Sección 4.2 (pág. 22). En el caso
de la simulación de cache se recogen, por ejemplo, el número de aciertos y de fallos, desglosados
por hilo, nivel de cache y PC. En el caso de las funciones asociadas a los bloques, se almacenarı́an
el número total de instrucciones ejecutadas por cada bloque desglosadas por cada hilo lanzado por
la aplicación. El objetivo es almacenar en las estructuras definidas a tal efecto, toda la información
que sea de utilidad para caracterizar la aplicación y estudiar su comportamiento sobre la cache
definida.
Finalmente, una vez ha terminado la aplicación, se ejecuta la función de finalización. En esta
se lleva a cabo todo el volcado de la información almacenada en las diferentes estructuras para su
posterior tratamiento.
En la Figura 5.2 (pág. 32) se muestra de forma más detallada el flujo de ejecución para simular
la cache en un modo denominado modo buffer. Este modo se usa cuando se van almacenando
todas las instrucciones de memoria de un bloque antes de simularlas. Las llamadas insertadas en
la instrumentación están indicadas con la palabra clave call. La llamada a foo1 se insertarı́a al
detectar una instrucción de carga de memoria; foo2 cuando es de almacenamiento; finalmente, foo3
se insertará al detectar un bloque básico. Para el modo buffer, la función foo3 serı́a la encargada
de iniciar la simulación de instrucciones previamente instrumentadas. Dado que foo3 se ejecutarı́a
antes que foo1 y foo2, la primera vez no habrı́a instrucciones que simular. En las veces sucesivas
ya se habrı́an instrumentado instrucciones de memoria. Por ello, se da la circunstancia de que
cada bloque básico lanzarı́a la simulación de las instrucciones registradas en el bloque anterior.
Este bloque podrı́a ser tanto él mismo, en caso de bucle, como otro. Para evitar problemas con la
simulación de las instrucciones del último bloque, la función de finalización es una buen candidata
donde comprobar si faltan instrucciones de memoria por simular.
Figura 5.2: Simulación en modo buffer
En la Figura 5.3 (pág. 33) se muestra el flujo de ejecución en modo instrucción a instrucción.
En este otro caso las instrucciones se van simulando a medida que se van encontrando, lo cual lo
hace más lento que el caso anterior al tener que proceder a realizar todo el trasiego de paso de
parámetros para cada instrucción de memoria que se encuentre en el programa.
5.2. ESTRUCTURAS Y CLASES
33
Figura 5.3: Simulación en modo instrucción a instrucción
5.2.
Estructuras y clases
Las estructuras de datos y clases más importantes usadas en el simulador, y que podrı́an
aprovecharse más adelante, son las siguientes:
Bloque Básico
Rutina
Cache
Estadı́sticas
BLOQUE BASICO
Dentro de cada bloque básico CMP$im almacena los siguientes campos:
Direcciones de comienzo y fin: son los PCs de la primera y última instrucciones del
bloque. Es muy importante saber dónde empieza un bloque y dónde termina porque, como
vimos en el modo buffer descrito en la Sección 5.1 (pág. 31), las instrucciones que se tratan
son las del bloque anterior. Si conocemos sus lı́mites, se pueden utilizarlos en adelante a
modo de ı́ndice.
Contador: número de veces que se ejecuta un bloque. Se utiliza como estadı́stica para poder
elaborar una lista de bloques básicos ordenados por frecuencia de ejecución.
La estructura está instanciada a modo de lista STL. Véase el Listado 5.1 (pág. 33). Hay que
tener en cuenta que la instrumentación, pese a estar programada en modo buffer para el caso de
los bloques, dentro de pin realmente se realizaba a nivel de traza. Como una traza puede contener
varios bloques básicos, en el momento en que se reconoce uno y se inserta en la llamada a la
función de análisis correspondiente, no se tiene en cuenta si está solapado con otros bloques. En
la Figura 5.5 (pág. 34) se presenta la problemática.
1
list < const BBInfo * > BBInfoList ;
Listado 5.1: Lista STL de Bloques Básicos
34
CAPÍTULO 5. ARQUITECTURA DEL SIMULADOR
Figura 5.4: Ejemplo de bloque básico
Figura 5.5: Proceso de descubrimiento de bloques
Imaginemos que tenemos un caso de bloques básicos como el de la Figura 5.4 (pág. 34). Antes
de reconocer los dos bloques por separado, Pin identifica un único bloque de instrucciones hasta el
siguiente salto. En la Figura 5.5 (pág. 34) se ha denominado BBL 0. Durante la instrumentación se
insertarı́a entonces una entrada en la lista con la información del BBL 0 y, en caso de simulación
en modo buffer, su correspondiente call a la función de análisis tal y como se ejemplificaba en la
Figura 5.2 (pág. 32).
A continuación, encontrarı́a una instrucción de salto a una posición dentro BBL 0. Es entonces
cuando indicarı́a que hay dos bloques, BBL 1 y BBL 2, que se insertarı́an en la lista y sobre los
cuales se agregarı́an los correspondientes call. Ya sea con el nombre BBL 0 o con el nombre BBL 1,
ambos comenzarı́an en la misma instrucción. Además, en el código instrumentado aparecerı́an dos
llamadas a la función de análisis que cuenta el número de veces que se ejecuta el bloque. Dentro
de los parámetros de las llamadas a la funciones de análisis se indicarı́a el elemento de la lista de
bloques sobre el que incrementar el contador. Dado que al final de la ejecución ambas entradas
tendrán la misma cantidad en el campo Contador, serı́a posible añadir un tratamiento posterior
que mejorara las estadı́sticas finales.
Situaciones como esta son inevitables, ya que Pin no puede predecir inicialmente que va a
haber un salto a una instrucción intermedia hasta que ésta tenga lugar. Por tanto, almacenar
todos los bloques que encuentre inicialmente es necesario para, al final de la simulación, realizar
las intersecciones y uniones pertinentes para imprimirlos en el informe final.
RUTINA
El almacenamiento de todas las rutinas ejecutadas permite averiguar, para un bloque básico,
en qué rutina se encuentra. Esta información, en posteriores análisis, participa en la identificación
del código fuente correspondiente a un bloque en concreto. Los campos de más relevancia son los
siguientes.
Nombre: de cara al código fuente, es un buen modo de identificar todo el código ensamblador
perteneciente a una rutina. Hay que tener en cuenta que habrá llamadas sobre las que se
haya hecho inline.
Direcciones de comienzo y fin: son los PCs de las instrucciones inicial y final de la rutina.
5.2. ESTRUCTURAS Y CLASES
35
Ayudarán a indexar los bloques básicos que encierran.
La estructura estaba instanciada como un mapa STL indexado por el contador de programa
de la primera instrucción de la rutina. Véase el Listado 5.3 (pág. 36). La documentación de Pin
no aconseja averiguar la última instrucción de la rutina. En el caso de que hubiera varios puntos
de salida (return) no se asegura que la ultima instrucción sea realmente la esperada.
1
map < UINT64 , RTNInfo * > RTNInfoMap ;
Listado 5.2: Mapa STL de Rutinas
CACHE
La clase CACHE contiene toda la descripción de las caracterı́sticas y comportamiento aproximados de lo que serı́a una cache real. En ella se centran las rutinas más importantes que simulan
el comportamiento en memoria de una aplicación. Los atributos de que consta son los propios de
una memoria cache tales como tamaño, asociatividad, tamaño de la lı́nea, latencia, inclusividad
o exclusividad, polı́tica de reemplazo, etc. Hay que destacar que, tal y como estaba organizado el
simulador, pese a que es una clase que podrı́a coexistir en sı́ misma, estaba diseñada como clase
derivada de una clase padre denominada ESTADISTICAS.
La función más importante se denominaba CacheAccess. Los parámetros principales de esta
función son:
PC: instrucción responsable del acceso.
Address: dirección de la lı́nea a la que se quiere acceder.
Tipo de acceso: es un enumerado que acepta cualquiera de los siguientes tipos de acceso:
• LOAD: acceso de lectura.
• STORE: acceso de escritura.
• SOFTWARE PREFETCH: software prefetch.
• READ FOR WRITE: caso especial en el que se accederı́a al siguiente o siguientes niveles de cache para traer una lı́nea que ocasionó un fallo y sobre la que se quiere escribir.
Este tipo de accesos no se verı́an en una configuración de cache con polı́tica de escritura
write-through.
• WRITE BACK: si la polı́tica de reemplazo ocasiona que se sustituya un bloque donde
el dirty-bit se encontraba a uno, se producirá un acceso de este tipo para escribir el
bloque antes de que se produzca el reemplazo.
• WRITE THROUGH: en una configuración con polı́tica de escritura write-through, los
accesos a los niveles posteriores al más próximo a la CPU tendrı́an este etiquetado.
• INVAL: acceso que se produce en todas las caches del mismo nivel para invalidar un
dato que podrı́a haberse modificado.
• BACK INVAL: acceso en casos de caches inclusivas. Cuando se producen desalojos
de bloques en niveles lejanos al procesador, hay que regresar a los más cercanos para
invalidarlos si fuera necesario, manteniendo la inclusividad.
Resultado: parámetro de entrada/salida que recoge si ha habido acierto o fallo.
36
CAPÍTULO 5. ARQUITECTURA DEL SIMULADOR
Figura 5.6: Punteros a objetos cache
1
void CacheAccess ( INT64 PC , ADDR address , enum TYPE_ACCESS typeAccess ) ;
Listado 5.3: Función de acceso a la cache
En la definición de la función, hay llamadas sucesivas a otras funciones miembro privadas que
se encargaban de lo siguiente:
Examinar el mapa de su cache asociado.
Averiguar si existı́a la lı́nea solicitada.
Proceder a realizar los reemplazos necesarios en caso de que se tratara de un fallo.
Lanzar las órdenes de invalidación sobre el resto de caches.
Actualizar las estadı́sticas de hit o miss para el PC responsable de la solicitud.
El número de objetos creados de la clase CACHE dependen de la configuración pasada por
parámetro a la pintool. En la Figura 5.6 (pág. 36) se presenta el modo de almacenar todos los
punteros a los diferentes objetos cache creados a través de una matriz.
CMP$im soporta aplicaciones multi-hilo. En la configuración inicial es posible indicar cuántos
threads acceden a determinado objeto de cache. Es un requerimiento que el número de hilos
esperados por la pintool sea potencia de dos, aunque los creados por la aplicación no lo sean.
Cada vez que un hilo quisiera acceder a su objeto de cache, existe una función que marca la
correspondencia entre el id del thread y el ı́ndice de la cache. Una vez obtenido el puntero, se
invocarı́a la función CacheAccess.
ESTADISTICAS
La clase que almacena las estadı́sticas es una clase de la que posteriormente hereda la clase
CACHE. Se encarga simplemente de proveer, para cada uno de los niveles de cache, de los atributos
y funciones miembro necesarios para contener estadı́sticas y volcarlas al final de la ejecución. La
mayorı́a de estos atributos son a su vez estructuras. Todos están indexados por thread y PC en
este orden. Entre los datos más importantes que se contabilizan figuran los siguientes: accesos,
aciertos, fallos, tipos de acceso, fallos forzosos, desalojos, vı́ctimas e invalidaciones.
5.3. PARÁMETROS DE EJECUCIÓN
5.3.
37
Parámetros de ejecución
La lı́nea utilizada para simular todas las aplicaciones se presenta en el Listado 5.4 (pág. 37).
Los knobs que en figuran en este listado son:
-cache: sirve para configurar los distintos niveles de cache. Habrá un knob por cada nivel.
1
- cache < identificador >: < tamano >: < tamano linea >: < asociatividad >: < bancos >
En nuestro caso, vamos a configurar 2 niveles, siendo el primero de ellos una cache de datos
L1 y el segundo una caché unificada L2. Los identificadores están predefinidos, por tanto
serán DL1 y UL2. La DL1 será de 32KB con lı́neas de 64B y asociatividad 8. La UL2 será de
1KB con lı́neas de 64B y asociatividad 16.
-tlb: para configurar los distintos niveles de TLB.
1
- tlb < identificador >: < lineas >: < tamano pagina >: < asociatividad >
Configuraremos dos niveles de TLB de datos. En este caso, de nuevo los identificadores están
predefinidos, por tanto seran DTLB y DTLB2. La DTLB tendrá de 64 lineas y un tamaño
de página de 4KB, permitiendo mapear 256KB. La DTLB2 tendrá 256 entradas y páginas
de 4KB para mapear 1MB.
-dl1lat: la latencia de la DL1 será 4.
-ul2lat: la latencia de la UL2 será 16.
-dtlblat: la latencia de la DTLB será 0.
-dtlb2lat: la latencia de la DTLB2 será 6.
-inclusive: se modelarán caches inclusivas, por tanto se activará indicándolo con un ’1’.
-threads: no se modelarán aplicaciones multihilo, por tanto se indicará que solo se quiere
1 hilo.
La latencia de la memoria principal se ha dejado a su valor por defecto, 350 ciclos. Esto
influirá tanto en los fallos de la UL2 como en los de la DTLB2.
1
2
3
4
5
6
7
8
9
10
pin -t cmpsim - cache DL1 :32:64:8:1
- cache UL2 :1024:64:16:1
- tlb DTLB :64:4096:8
- tlb DTLB2 :256:4096:8
- dl1lat 4
- l2lat 16
- dtlblat 0
- dtlb2lat 6
- inclusive 1
- threads 1 -- <app > < input >
Listado 5.4: Lı́nea para el lanzamiento de la simulación
Capı́tulo 6
Caracterización de benchmarks
En este capı́tulo se presenta la caracterización de los benchmarks descritos en la Sección 4.3
(pág. 23). Se fundamenta en el primero de los objetivos de la Sección 1.1 (pág. 2) que hacı́a referencia al análisis y clasificación de un conjunto de aplicaciones numéricas en función del grado de
vectorización. La caracterización se ha llevado a cabo de la siguiente manera: una vez seleccionada
la muestra de benchmarks, se han compilado y se ha extraı́do el porcentaje de vectorización. Para
la generación de este porcentaje se ha hecho uso de una herramienta interna disponible en Intel
que instrumentaba de manera muy rápida las instrucciones de una aplicación. Como en este punto
era importante cuantificar el numero de instrucciones vectoriales de que se compone, se realizó la
siguiente clasificación: instrucciones enteras e instrucciones en punto flotante. Las instrucciones
en punto flotante están clasificadas a su vez entre aquellas que operan sobre un único elemento,
llamadas escalares, vec 1, y las que lo hacen sobre n elementos, llamadas vectoriales, vec n. Adicionalmente, a partir del informe generado por el compilador, se ha extraı́do un resumen sobre
las causas encontradas por éste para no vectorizar. Esta información se presenta en dos gráficas por cada benchmark, una con el porcentaje de vectorización y otra con las causas de la no
vectorización.
6.1.
Polyhedron
La Figura 6.1 muestra el porcentaje de instrucciones escalares y vectoriales de los benchmarks
de Polyhedron. Están ordenados de izquierda a derecha desde los de menor ı́ndice de vectorización
hasta los de mayor, respectivamente. En la Tabla 6.1 (pág. 40) se adjunta el desglose numérico
del que se sostiene la figura mencionada.
A la derecha de la gráfica tenemos aplicaciones como channel, linpk y test fpu, cuyos ı́ndices
de vectorización son óptimos, sobre todo en el caso de channel. En el extremo opuesto tenemos
a tfft con el peor ratio de vectorización. Las aplicaciones protein y ace son ejemplos sobre los
que no merece la pena centrarse, ya que mayoritariamente tienen instrucciones enteras, las cuales
están fuera de ser objetivo de la vectorización. En su lugar, otras aplicaciones como fatigue, doduc,
mdbx, nf o induct, parecen interesantes candidatos a ser analizados.
Dada la importancia de comprobar el informe generado por el compilador, se presenta la Figura 6.2. El eje de coordenadas izquierdo representa la distribución en porcentajes de las razones
más significativas. En él decidimos incluir la distribución de bucles que sı́ han sido vectorizados.
El eje de coordenadas derecho presenta la cantidad real de regiones tratadas por el compilador. Se
visualizan con una marca blanca rectangular y sirve de referencia para determinar si la vectoriza39
40
CAPÍTULO 6. CARACTERIZACIÓN DE BENCHMARKS
Figura 6.1: Índice de vectorización de las aplicaciones de Polyhedron
ción en realidad ha sido buena. En la leyenda, una de las series se denomina Other. En esta serie
se han incluido aquellas que tenı́an poco peso. En el caso de Polyhedron, se incluyen las siguientes:
insufficient computational work, cannot vectorize empty loop, unsupported reduction, conditional
assignment to a scalar y statement cannot be vectorized. Conviene resaltar que esta gráfica no
deja de ser una representación estática del log del compilador. Existen bucles que, habiendo sido
vectorizados, o bien no se ejecutan debido a los datos de entrada, o bien el número de instrucciones
que contiene es pequeño, impidiendo que el ı́ndice de vectorización sea más favorable.
Polyhedron
channel
linpk
test fpu
induct
gas dyn
nf
capacita
air
mdbx
doduc
aermod
fatigue
ac
protein
tfft
Enteras
%
Escalares
%
Vectoriales
%
468.500.485
899.584.515
4.029.631.938
988.762.995
1.329.097.003
2.760.204.099
52.878.534.140
9.305.166.389
8.203.617.802
29.803.502.394
41.360.047.064
4.582.406.825
8.552.412.161
125.788.239.418
1.307.018.392
24,62
29,43
50,00
9,05
38,71
29,81
61,32
60,05
35,86
35,56
58,32
29,36
79,56
96,33
29,89
2.930.591
134.296.667
519.826.479
5.319.266.886
764.150.080
4.497.941.028
15.135.853.071
3.080.714.025
11.730.283.833
44.849.452.311
25.593.434.008
10.443.420.655
2.009.202.064
3.640.638.101
3.061.907.878
0,15
4,39
6,45
48,66
22,26
48,57
17,55
19,88
51,27
53,51
36,09
66,92
18,69
2,79
70,03
1.431.883.011
2.022.708.587
3.510.250.489
4.623.569.301
1.340.360.535
2.002.646.277
18.214.751.144
3.110.552.221
2.945.337.303
9.160.986.696
3.967.026.727
580.509.855
188.047.565
1.155.380.485
3.443.341
75,23
66,18
43,55
42,30
39,04
21,63
21,12
20,07
12,87
10,93
5,59
3,72
1,75
0,88
0,08
Tabla 6.1: Desglose de instrucciones de las aplicaciones de Polyhedron
Se observa que channel está en cabeza en cuanto al número de bucles vectorizados. Hacen un
total de 66, cantidad pequeña comparativamente. Pese a ello, el 82 % de dichos bucles está vectorizado permitiendo que la oportunidad de ejecutar una instrucción vectorial aumente. Esto se
traduce en un óptimo ı́ndice de vectorización (recuérdese la Figura 6.1 (pág. 40)). La aplicación
tfft es interesante porque mientras que el ı́ndice de vectorización era nulo, los bucles vectorizados
6.2. MANTEVO 1.0
41
Figura 6.2: Razones para no vectorizar bucles en Polyhedron
ocupan el 33 % del total. Se tratarı́a por tanto de bucles que no se ejecutan o que lo hacen con
muy poca frecuencia. Por otro lado, mientras que aermod destaca por la cantidad de bucles que
contiene, 2597, un 72 % no se ha vectorizado, siendo la razón de peso que los bucles no iteran lo
suficiente como para que sea eficiente, low trip count. En air, el número de bucles vectorizados
compite con los que no lo han sido. El motivo principal es not inner loop. Cada vez que el compilador se dispone a vectorizar un bucle de entre un conjunto de bucles anidados, para todos los
bucles externos registra como motivo que no son bucles internos. En air, se entiende entonces que
hay muchos bucles anidados.
6.2.
Mantevo 1.0
El ı́ndice de vectorización de las aplicaciones de Mantevo visualizadas en la Figura 6.3 (pág. 42)
no parecen dar tanto juego como en Polyhedron. La Tabla 6.4 (pág. 43) adjunta el desglose de
instrucciones. La aplicación CoMD, que podrı́a ser uno de los candidatos, tiene un 69,35 % de instrucciones enteras. Si bien podrı́a ser estudiado para reducir el número de instrucciones escalares,
vec 1, las enteras no parecen dejar mucho margen para conseguir una mejora más significativa.
En la Figura 6.4 (pág. 43) con las razones del compilador para no vectorizar, las opciones
englobadas en la categorı́a other son: low trip count, loop was transformed to memset or memcpy
y conditional assignment to a scalar. La aplicación CloverLeaf, que presentaba el mejor ı́ndice de
vectorización con un 73,13 % de instrucciones vec n, aquı́ se encontrarı́a por detrás de miniGhost
en cuanto a bucles vectorizados. Entre los motivos para no vectorizar, es mayoritaria la razón
not inner loop. En Polyhedron ya vimos que no es una razón de peso. Además, dado que es la
aplicación con mayor número de bucles, 1127, en realidad se trata de un caso de éxito. En la
misma lı́nea tenemos a miniGhost, con un resultado semejante en cuanto a bucles vectorizados,
42
CAPÍTULO 6. CARACTERIZACIÓN DE BENCHMARKS
Figura 6.3: Índice de vectorización de las aplicaciones de Mantevo 1.0
Mantevo
CloverLeaf
miniGhost
HPCCG
miniMD
miniFE
miniXyce
CoMD
Enteras
%
Escalares
%
Vectoriales
%
3.312.609.977
5.817.781.939
9.153.318.789
9.076.376.697
30.524.781.122
151.901.922.911
3.899.688.850
17,09
57,48
54,30
50,00
66,90
96,07
69,35
1.896.586.386
290.628.926
1.813.492.799
3.886.133.542
5.714.424.611
4.734.235.225
1.711.104.306
9,78
2,87
10,76
21,41
12,52
2,99
30,43
14.174.588.802
4.013.310.416
5.890.463.437
5.189.843.145
9.384.984.596
1.485.511.169
12.159.534
73,13
39,65
34,94
28,59
20,57
0,94
0,22
Tabla 6.2: Desglose de instrucciones de las aplicaciones de Mantevo
pero con 294 bucles. Volviendo a su ı́ndice de vectorización, 39,65 %, no deja de ser bajo debido a
las instrucciones enteras. Por otro lado, HPCCG sorprende, ya que es el que tiene menor número
de bucles, 78, pero pese a ser pocos, se consigue vectorizar un 46 %, que se traducen en un ı́ndice
de vectorización del 34,94 %. Finalmente, miniXyce y CoMD están a la cola como ocurrı́a con
sus ı́ndices de vectorización respectivos. Sorprende miniXyce, ya que tiene más bucles que CoMD,
pero su ı́ndice de vectorización es insignificante al ser una aplicación mayoritariamente entera.
6.3.
Sequoia
En la Figura 6.5 (pág. 44), las aplicaciones SPhotmk y Crystalmk, con un 50,5 % y un 46,69 %
de instrucciones escalares, respectivamente, parecen claros candidatos a elegir para su análisis.
Véase también el desglose de instrucciones en la Tabla 6.3 (pág. 43).
En la Figura 6.6 (pág. 44), los motivos incluidos en la categorı́a other son: unsupported loop
structure, cannot vectorize empty loop y conditional assignment to a scalar.
Las aplicaciones de este benchmark tienen pocos bucles, comparados a grosso modo con los
benchmarks vistos anteriormente. Aquel que más tiene, UMTmk, asciende a 105. Sin embargo,
6.4. NPB
43
Figura 6.4: Razones para no vectorizar bucles en Mantevo 1.0
Sequoia
IRSmk
UMTmk
Crystalmk
SPhotmk
Enteras
%
Escalares
%
Vectoriales
%
6.418.427.371
85.434.122
5.566.079.613
47.201.295
44,33
66,40
47,53
43,94
60.616.654
12.366.599
5.468.000.498
54.255.433
0,42
9,61
46,69
50,50
8.000.112.715
30.872.142
677.000.196
5.972.314
55,25
23,99
5,78
5,56
Tabla 6.3: Desglose de instrucciones de las aplicaciones de Sequoia
dejando de lado este dato, sorprende SPhotmk que, junto con Crystalmk, y pese a tener ambos los
peores ı́ndices de vectorización, tienen más bucles que la aplicación con mejor ı́ndice, IRSmk. Esto
significa dos cosas: el número de bucles vectorizados serı́a mayor proporcionalmente; al haber más
bucles, hay más candidatos a ser mejorados y por tanto son aplicaciones interesantes para abordarlas de cara al análisis. Finalmente, la aplicación IRSmk también sorprende porque, teniendo
mejor ı́ndice, en realidad el número de bucles tratados es inferior a 20. Por tanto, los bucles de que
consta, por pocos que sean, concentran la mayor parte del cómputo dando lugar a un rendimiento
óptimo pese a la limitación impuesta por las instrucciones enteras.
6.4.
NPB
La Tabla 6.4 (pág. 45) y la Figura 6.7 (pág. 45) muestran que las aplicaciones UA, IS, BT y
LU se corresponderı́an a los candidatos de más interés. Hasta ahora, en las gráficas mostradas
comprobamos que, pese a que una aplicación tenga un buen ı́ndice de vectorización, es en las
instrucciones escalares donde se encuentran las oportunidades para vectorizar.
44
CAPÍTULO 6. CARACTERIZACIÓN DE BENCHMARKS
Figura 6.5: Índice de vectorización de las aplicaciones de Sequoia
Figura 6.6: Razones para no vectorizar bucles en Sequoia
6.4. NPB
45
Figura 6.7: Índice de vectorización de las aplicaciones de NPB
NPB
FT
MG
EP
SP
CG
LU
BT
IS
UA
DC
Enteras
%
Escalares
%
Vectoriales
%
141.213.978
95.558.650
537.991.137
5.435.933.967
419.556.698
3.604.965.775
986.948.941
141.146.461
2.089.546.384
135.544.766.627
45,61
32,86
36,23
50,67
62,22
28,18
13,41
56,20
26,02
97,66
7.116.085
45.070.701
451.200.929
1.944.681.817
62.606.098
6.447.471.196
5.286.544.985
88.048.143
5.495.880.739
825.664.200
2,30
15,50
30,38
18,13
9,28
50,40
71,85
35,06
68,43
0,59
161.292.981
150.211.106
495.838.486
3.348.529.940
192.116.340
2.740.743.624
1.083.789.029
21.974.211
445.471.779
2.424.457.394
52,09
51,65
33,39
31,21
28,49
21,42
14,73
8,75
5,55
1,75
Tabla 6.4: Desglose de instrucciones de las aplicaciones de NPB
En la Figura 6.8 (pág. 46), los motivos englobados en la categorı́a other son: statement cannot
be vectorized, condition may protect exception, operator unsuited for vectorization, unsupported
data type y cannot vectorize empty loop.
La aplicación FT presenta el mejor ı́ndice de vectorización, 52,09 %, pero el informe generado
por el compilador para ella destaca por el bajo número de bucles tratados, 35. Se trata de otro caso
como IRSmk de Sequoia. El ı́ndice es bueno porque, al ser pocos bucles, aparentemente se han
vectorizado aquellos que se ejecutan más, incluso siendo el porcentaje de bucles vectorizados de
solo un 35 %. Por otro lado tenemos UA, con 906 bucles. Si de todos estos bucles un 36 % ha sido
vectorizado y un 31 % eran bucles externos, parece que no es suficiente la razón vectorization posible
but seems inefficient para justificar el bajo ı́ndice de vectorización obtenido, 5,55 %. Parece un caso
contrario al de FT, en el que los bucles vectorizados no tienen un peso suficiente en la ejecución.
Finalmente, comentar que el compilador ha tratado únicamente 16 bucles para la aplicación EP
y, teniendo en cuenta que tiene un ı́ndice de vectorización del 33,39 %, demuestra que el cómputo
general del programa está repartido entre pocos bucles.
46
CAPÍTULO 6. CARACTERIZACIÓN DE BENCHMARKS
Figura 6.8: Razones para no vectorizar bucles en NPB
6.5.
SPEC FP
Pese a que la Figura 6.9 (pág. 47) y la Tabla 6.5 (pág. 47) nos muestran que las aplicaciones
cactusADM y zeusmp están por detrás de lbm en el grupo de las aplicaciones con mejor ı́ndice de
vectorización, el porcentaje de instrucciones escalares no deja de ser menos significativo que otras
aplicaciones como namd. Al igual que namd, povray y milc presentan una buena candidatura por
el simple hecho de que su ı́ndice de vectorización es prácticamente inexistente.
En la Figura 6.10 (pág. 48) las razones incluidas en la categorı́a other son: statement cannot be
vectorized, condition may protect exception, operator unsuited for vectorization, unsupported data
type y cannot vectorize empty loop.
Destaca gamess por la gran cantidad de bucles tratados por el compilador, 52237. Sin embargo,
pese al 47 % de bucles vectorizados, el ı́ndice de vectorización no es más que del 13,08 %, con lo
que dichos bucles no tienen gran parte del peso del programa. Por su parte, lbm destaca por tener
muy poca cantidad de bucles, 64, pero con más de la mitad de ellos vectorizados ha sido suficiente
para obtener un ı́ndice de vectorización del 76,41 %.
Lo datos presentados dejan fehaciente que, pese a que el compilador esté especialmente diseñado
para explotar la vectorización, no se consiguen resultados favorables para todas las aplicaciones.
Esto se traducirı́a en un deficiente uso de la unidad de vectorización. También hay que tener
presente el contraste entre el carácter dinámico de la gráfica con el porcentaje de vectorización,
frente al carácter estático de aquella con el informe generado por el compilador. Estas razones dan
lugar a generar informes más completos del comportamiento de las aplicaciones, para lo cual es
necesario completar el simulador CMP$im.
6.5. SPEC FP
47
Figura 6.9: Índice de vectorización de las aplicaciones de SPEC fp
SPEC fp
470.lbm
436.cactusADM
434.zeusmp
437.leslie3d
481.wrf
410.bwaves
459.GemsFDTD
416.gamess
465.tonto
435.gromacs
482.sphinx3
450.soplex
454.calculix
433.milc
453.povray
444.namd
447.dealII
Enteras
%
Escalares
%
Vectoriales
%
247.043.444
87.202.676
3.098.091.055
3.466.782.814
1.430.157.094
5.608.197.533
2.579.077.218
814.275.217
1.735.105.509
1.077.444.401
3.949.495.345
37.411.459
101.212.053
2.126.321.655
1.225.769.598
18.465.424.606
34.050.212.478
17,79
17,90
22,22
42,50
60,39
61,63
79,49
64,01
63,42
58,22
84,16
80,35
91,84
13,85
65,92
40,98
74,97
80.478.947
717.855.566
5.964.021.317
1.932.662.908
379.485.584
1.506.737.948
135.223.572
291.456.487
686.200.465
567.698.837
256.885.880
7.610.447
5.847.184
12.860.333.431
597.196.874
26.139.516.627
11.045.728.175
5,80
44,73
42,78
23,69
16,02
16,56
4,17
22,91
25,08
30,68
5,47
16,34
5,31
83,76
32,11
58,00
24,32
1.061.043.618
599.834.350
4.879.315.996
2.757.121.526
558.461.158
1.985.594.787
530.125.307
166.450.557
314.702.162
205.426.782
486.219.594
1.539.792
3.139.907
366.588.682
36.644.135
460.045.395
321.833.495
76,41
37,38
35,00
33,80
23,58
21,82
16,34
13,08
11,50
11,10
10,36
3,31
2,85
2,39
1,97
1,02
0,71
Tabla 6.5: Desglose de instrucciones de las aplicaciones de SPEC FP
48
CAPÍTULO 6. CARACTERIZACIÓN DE BENCHMARKS
Figura 6.10: Razones para no vectorizar bucles en SPEC fp
Capı́tulo 7
Adaptación del Simulador
En este capı́tulo se describen las ampliaciones que se han realizado sobre el simulador CMP$impara
incluir capacidades de ejecución vectoriales. A la hora de decidir qué tipo de capacidades vectoriales incluir, se ha optado por realizar un modelado sencillo de la arquitectura del coprocesador
R Xeon PhiTM . No se pretendı́a realizar un análisis del rendimiento de este producto, sino
Intel
tener un modelo que se sabe hace uso de unidades vectoriales con el que generar estadı́sticas sobre
esta caracterı́stica de formas rápida y sencilla.
Para abordar una descripción detallada del simulador construido, se va a partir de las siguientes
tres preguntas:
R Xeon
¿Qué caracterı́sticas tiene que tener el pipeline para adaptarse a la arquitectura Intel
PhiTM Coprocessor?
¿Hay datos intermedios ya generados por CMP$imque puedan ser reutilizados?
¿Qué estadı́sticas particulares permitirán discernir sobre el uso de la unidad vectorial?
7.1.
Pipeline
Al instrumentar una aplicación, es sencillo contar el número de instrucciones ejecutadas pues
únicamente se trata de incrementar un contador. La contabilización de ciclos también serı́a una
tarea sencilla en caso de una máquina multiciclo, en la que hasta que no finaliza una instrucción,
no se ejecuta la siguiente. En el caso de una máquina segmentada esta tarea ya no es tan trivial.
Hay que tener en cuenta las peculiaridades de la arquitectura para situar a una instrucción en la
etapa correcta según avance la ejecución.
Rememorando lo comentado en la Sección 5.1 (pág. 31), se presenta la Figura 7.1 (pág. 50)
con el boceto sobre dónde se colocarı́a la simulación del pipeline. Sabemos que se alternan las
fases de instrumentación y análisis a medida que se avanza en la ejecución. La fase de simulación
de cache se hace durante la fase de análisis usando datos que se han registrado en la fase de
instrumentación. La simulación del pipeline es análoga y, en este caso ademas, necesita de la
simulación de cache para utilizar la información que de esta se genera. Por tanto, necesariamente
tiene que ir a continuación como se visualiza en la figura.
R Xeon Phi
R es una máquina en orden con terminación fuera de orden
El coprocesador Intel
(véase Sección 2.3 (pág. 8)). Para simplificar el modelo no se han implementado etapas, con lo
49
50
CAPÍTULO 7. ADAPTACIÓN DEL SIMULADOR
Figura 7.1: Pipeline dentro de CMP$im
que cuando una instrucción entra en nuestro pipeline, significa que se va a ejecutar, ya sea un
acceso a memoria o una división vectorial. Se han tomado las siguientes consideraciones a la hora
de implementar el pipeline:
Cada instrucción entrará en el pipeline un ciclo después de la entrada de la instrucción
anterior.
No hay lı́mite de recursos.
Los registros se escriben en el banco de registros después de la finalización de la instrucción.
Si se producen dependencias de instrucciones no harı́a falta esperar a la escritura en el banco.
Existen buses por donde recoger esa información antes de llegar al banco.
La latencia de una instrucción se caracteriza por los ciclos que necesita para completar una
operación sin contar las dependencias.
Un fallo en el primer nivel de cache que implique traer una lı́nea de los niveles siguientes,
congela automáticamente el pipeline tanto si la instrucción siguiente necesita el dato como
si no. Esto se produce con cualquier tipo de acceso a memoria bajo demanda (load, store,
software prefetch, etc.)
Los ciclos situados entre ciclo entrada ins actual − ciclo entrada ins anterior + 1 se utilizarán exclusivamente con fines estadı́sticos.
Para ejemplificarlo se utilizará un pequeño kernel escrito en Fortran y basado en el bucle s171
del conjunto de bucles del benchmark LCD. Véase el código en Listado 7.1 (pág. 51). El bloque con
mayor número de instrucciones ejecutadas mostrado en el Listado 7.2 (pág. 51) se corresponde
exactamente con el bucle do..continue del kernel. Como es un ejemplo simple para mostrar el
funcionamiento del pipeline no se ha compilado para la arquitectura AVX-512. Para el ejemplo
no se mostrará más que una iteración. La estructura de bloques disponibles es indispensable para
lo que serı́a la creación de un histograma con información estática de las instrucciones. Permitirı́a
entonces acceder a los datos que caracterizan a las instrucciones, como por ejemplo la latencia para
aquellos casos donde se realice una operación. Aparte, como se mostró en la Figura 7.1 (pág. 50),
para construir un pipeline ficticio es indispensable hacer uso de los datos proporcionados por las
rutinas de simulación de cache de CMP$im.
En la Tabla 7.1 (pág. 51) se muestra la información recopilada de las instrucciones de más
interés desenrolladas por el compilador. Esta información recoge datos de latencias de tlb, cache
y operación. La instrucciones que realizan las operaciones de suma y multiplicación entran dentro
de la clasificación de las FMA. Su latencia, 4, se puede consultar más adelante en el Listado 7.5
7.1. PIPELINE
51
(pág. 55). Véase la Figura 7.2 (pág. 52) con el detalle del pipeline. Téngase en cuenta que la
cuadrı́cula es una aproximación para entender el funcionamiento de nuestro kernel basado en el
R Xeon Phi.
R
coprocesador Intel
c
c
c
-- Fichero : main . f
integer n
real a (100000) , b (100000) , c (100000)
open ( unit =1 , file = " N " )
read (1 ,*) n
close (1)
call s171 (a , b , c , n )
end
c
c
c
-- Fichero : s171 . f
subroutine s171 (a , b , c , n )
integer n
real a ( n ) ,b ( n ) ,c ( n )
do 10 i = 1 , n
a(i) = a(i) + b(i) * c(i)
continue
return
end
10
Listado 7.1: Kernel s171 basado en el mismo bucle en LCD
1
2
3
4
5
6
7
8
9
10
11
4004 f9 :
4004 fd :
400502:
400506:
40050 b :
40050 f :
400514:
400518:
40051 d :
400521:
400524:
movups xmm0 , xmmword ptr [ rsi + rax *4]
movups xmm1 , xmmword ptr [ rsi + rax *4+0 x10 ]
mulps xmm0 , xmmword ptr [ rdx + rax *4]
mulps xmm1 , xmmword ptr [ rdx + rax *4+0 x10 ]
addps xmm0 , xmmword ptr [ rdi + rax *4]
addps xmm1 , xmmword ptr [ rdi + rax *4+0 x10 ]
movups xmmword ptr [ rdi + rax *4] , xmm0
movups xmmword ptr [ rdi + rax *4+0 x10 ] , xmm1
add rax , 0 x8
cmp rax , r9
jb 0 x4004f9
Listado 7.2: Bloque básico de s171 con mayor número de instrucciones ejecutadas
Latencias
TLB
Cache
load
356
370
Operación
load
4
mul
4
4
mul
4
4
370
4
add
4
4
store
4
store
4
add
356
Tabla 7.1: Latencias de memoria y de instrucción (load-op)
A continuación se explica en detalle el pipeline de la Figura 7.2 (pág. 52):
52
CAPÍTULO 7. ADAPTACIÓN DEL SIMULADOR
Figura 7.2: Pipeline del bloque de s171
load - movups xmm0, xmmword ptr [rsi+rax*4]: La latencia es alta porque se ha producido fallo tanto en TLB como en L1. Se ha tenido que llegar a la memoria principal. Como se
comentó, esta situación provoca la congelación del pipeline. La siguiente instrucción no entrarı́a
hasta el ciclo 726.
load - movups xmm1, xmmword ptr [rsi+rax*4+0x10]:
cache, DL1, por lo que entra en el 726 y termina en el 730.
Acierto en el primer nivel de
mul - mulps xmm0, xmmword ptr [rdx+rax*4]: Instrucción tipo load-op. Dependencia del
registro xmm0 sobre la primera carga sin consecuencias. La instrucción entrarı́a en el ciclo 726+1,
a continuación de la anterior porque el registro xmm0 estarı́a disponible. Como es load-op carga
primero el dato, acierto en DL1 (4 ciclos) y opera (4 ciclos), haciendo un total de 8 ciclos. Entrarı́a
pues en el 727 y saldrı́a en el 735.
mul - mulps xmm1, xmmword ptr [rdx+rax*4+0x10]: Instrucción tipo load-op. Dependencia del registro xmm1 en el segundo load. Idealmente entrarı́a en el 728, pero como la carga
de la que depende no termina hasta el 730, se retrasa 2 ciclos. De nuevo cargarı́a primero el dato
(4 ciclos) y operarı́a sobre él (4 ciclos). Entrarı́a pues en el ciclo 730 y sale en el 738.
add - addps xmm0, xmmword ptr [rdi+rax*4]: Instrucción tipo load-op. Dependencia del
registro xmm0 sobre la primera multiplicación. Idealmente entrarı́a en el 731, pero se retrasa 4
ciclos. Falla en el TLB y en la cache y tiene que ir hasta memoria, ocasionando que se congele
el pipeline. La siguiente instrucción no entrarı́a hasta que pasaran los 726 ciclos de memoria.
Finalmente realiza la suma (4 ciclos). Entra entonces en el ciclo 735 y sale en el 1465.
add - addps xmm1, xmmword ptr [rdi+rax*4+0x10]: Instrucción tipo load-op. Dependencia del registro xmm1 sobre la segunda multiplicación sin consecuencias. Entra en el ciclo 1461
debido a la congelación del pipeline de la instrucción anterior. Termina en el 1469 por el acceso a
memoria (4 ciclos) y la suma (4 ciclos).
store - movups xmmword ptr [rdi+rax*4], xmm0: Dependencia del registro xmm0 sobre
la primera suma. Idealmente entrarı́a en el ciclo 1462 pero se retrasa 3 ciclos. Entonces entra en
el 1465 y termina en el 1469. Acierta en la cache.
7.2. DETECCIÓN DE INSTRUCCIONES Y REGISTROS
53
store - movups xmmword ptr [rdi+rax*4+0x10], xmm1: Dependencia del registro xmm1
sobre la segunda suma. Idealmente entrarı́a en el 1466 pero se retrasa 3 ciclos hasta el 1469. Finaliza
en el 1473 pues acierta en el primer nivel de cache.
7.2.
Detección de instrucciones y registros
La caracterización de las instrucciones es un paso esencial para el funcionamiento de este
simulador. Permite conocer al detalle las instrucciones de cara a construir, por ejemplo, una tabla
de registros. También permitirı́a saber cuánto tiempo va a requerir para terminar la operación que
contenga, independientemente de las dependencias de los operandos. De cara a las instrucciones
exclusivamente de memoria o las load-op, que cargan un dato y operan sobre el mismo, no es
posible conocer los ciclos totales hasta la simulación sobre la cache.
La API de Pin para instrumentar instrucciones pone a disposición del usuario multitud de
funciones que ayudan a la caracterización. Sin embargo, mientras que existen funciones que detectan fácilmente si, por ejemplo, la instrucción es un salto o una comparación, el repertorio no
es suficientemente granular para caracterizar instrucciones vectoriales. No es capaz de discernir si
la instrucción es simplemente vectorial o si los registros son vectoriales. Lo mismo ocurre con la
caracterización de las instrucciones tipo load-op. Hay que tener en cuenta que las instrucciones que
se iban a instrumentar eran de las extensiones AVX-512. Estas instrucciones, en el momento de
construir el simulador, no eran públicas. Por tanto no era posible para Pin proporcionar una API
que permitiera reconocer los nuevos registros de 512 bits. Ya fuera entonces por la no publicidad
de la arquitectura, o porque simplemente no disponı́a de funciones con la suficiente granularidad,
se creó el código necesario para subsanar estas carencias.
7.2.1.
Instrucciones
INS IsVector: para discernir si una instrucción es vectorial o no, usamos librerı́as de uso
interno en donde todas y cada una de las instrucciones de AVX-512 estaban caracterizadas.
Mediante estas funciones era posible consultar campos como la categorı́a o extensión de una
instrucción. Para nuestros propósitos, bastaba con usar la extensión, cuya clasificación era
más genérica, reduciendo por tanto las lı́neas de código.
is scalar simd: de nuevo, mediante el uso de la misma librerı́a interna, era posible determinar si una instrucción estaba operando sobre un único o múltiples elementos de un
vector.
is load op: la heurı́stica consiste en contar el número de operandos de lectura totales de la
instrucción, x, y el número de operandos de memoria, y. Si x > y entonces la instrucción
es load-op, ya que el operando de más es aquel que se operará junto con el dato cargado de
memoria. Véase el detalle en el Listado 7.3 (pág. 53).
1
2
3
4
5
6
7
8
9
10
11
12
13
bool is_load_op ( INS ins )
{
if ( I s M e m I n s t r u c t i o n ( ins ) )
{
UINT32 operandCount = IN S _ O p e r a n d C o u n t ( ins ) ;
UINT32 readOp
= 0;
UINT32 memReadOp
= 0;
for ( UINT32 i = 0; i < operandCount ; i ++)
if ( IN S _O pe ra n dR ea d ( ins , i ) )
{
readOp ++;
if ( I N S _ O p e r a n d I s M e m o r y ( ins , i ) ) memReadOp ++;
54
CAPÍTULO 7. ADAPTACIÓN DEL SIMULADOR
14
15
16
17
18
19
20
}
if ( memReadOp && readOp > memReadOp )
return true ;
}
return false ;
}
Listado 7.3: Función para detectar instrucciones load-op
7.2.2.
Registros
La detección de registros pasó a formar parte del algoritmo general de instrumentación de
instrucciones. También se utilizaron funciones de librerı́as internas que reconocieran los registros
zmm de AVX-512. El modus operandi consistió en analizar individualmente cada operando y
clasificarlo del siguiente modo:
Operando con registro: es aquel sobre el que se va a operar, ya sea para leer o escribir.
Operando de memoria: tuvimos en cuenta que habrı́a registros con los que se opera para
obtener una dirección de memoria.
La ventaja de acceder a los registros de este modo es que nos permite conocer, a través del
operando, si es un registro del que se va a leer o sobre el que se va a escribir. Los operandos de
memoria nos permitieron hacer un análisis más granular mediante la clasificación de los registros
según el tipo de direccionamiento: base, ı́ndice o segmento.
7.2.3.
Latencias
En CMP$im, la latencia de las instrucciones se toma por defecto a 1. Para las instrucciones de
memoria se agregarı́a la cantidad de ciclos correspondientes a la latencia, ya suponga un acierto
en la L1 o se tuviera que traer la lı́nea desde memoria. Para este trabajo tuvimos que modificar el
tratamiento de latencias de las instrucciones alejándolo del sistema ideal por defecto de CMP$im.
En el Listado 7.4 (pág. 54) se presenta la clasificación que hicimos de las instrucciones. Por defecto,
los valores se mantuvieron a 1.
1
2
3
4
5
# define
# define
# define
# define
# define
NONV PU_LATE NCY
MISC_LATENCY
D I V _ S Q R T _ L A T EN C Y
RCP_RSQRT_LATENCY
FMA_LATENCY
1
1
1
1
1
Listado 7.4: Clasificación de las latencias por defecto
Veámoslas en detalle:
NONVPU : conjunto de instrucciones enteras.
DIV/SQRT : tanto la división como la raı́z cuadrada son operaciones muy costosas que tienen
semejante número de ciclos.
7.3. NUEVAS ESTRUCTURAS Y CLASES
55
RCP/RSQRT : las instrucciones recı́procas tienen equivalente latencia, incluyendo las raı́ces
recı́procas. La categorı́a recı́procas
indica que se está calculando el inverso: 1/x en el caso
√
de las rcp en general y −1/2 x en el caso de las rsqrt.
FMA: operaciones de punto flotante que realizan suma y multiplicación en el mismo paso.
MISC : resto de instrucciones que no entran en el perfil de las enteras, pero que sı́ pueden
ser vectoriales/escalares.
Dado que estas latencias deberı́an ser modificables, se tomó la decisión de incorporar un knob
que permitiera introducir esta configuración a través de un fichero. Véase el ejemplo del Listado 7.5
(pág. 55)
1
2
3
4
5
6
7
8
9
10
11
# Example configuration file for instruction latencies .
# Non - VPU instructions
nonvpu 1
# VPU instructions
rcprsqrt 8
divsqrt 30
fma 4
# Default VPU instructions
misc 2
Listado 7.5: Fichero de ejemplo con latencias
Finalmente, durante la caracterización de instrucciones vista en Sección 7.2.1 (pág. 53), se
agregó una nueva función denominada GetLatencyByIclass que, haciendo uso de funciones internas, permiten consultar la clase de la instrucción y asignarle una latencia en función de la
misma.
7.3.
Nuevas estructuras y clases
R Xeon PhiTM que se
Las estructuras nuevas para el núcleo basado en el coprocesador Intel
presenta en este apartado son el resultado de entenderlo como un módulo añadido a toda la simulación de CMP$im. Se necesita la información recogida por las instrucciones, ya sean de memoria
o no, y se tiene que respetar el funcionamiento original de CMP$im para ambos modos buffer e
instrucción a instrucción. Decidimos por tanto desarrollarlo en dos nuevos ficheros core.cpp y
core.h.
La localización de las llamadas al pipeline de este núcleo era un problema que habı́a que
abordar desde el punto de vista del análisis más que de instrumentación. Recordamos el análisis
realizado sobre el pipeline en la Sección 7.1 (pág. 49). Decidimos situarlas antes de la ejecución
de cada bloque. Véase la Figura 7.3 (pág. 56). Este esquema encaja con el modo buffer porque
previamente se han simulado las instrucciones del bloque anterior. En el caso del primer bloque
no se simulará nada, mientras que en el caso del último bloque será la función de finalización la
que lanzarı́a el pipeline. También encaja con el modo instrucción a instrucción porque cuando se
ha llegado al bloque siguiente, las instrucciones de memoria del anterior ya se han simulado en la
cache, aunque fuera individualmente.
La idea consistió en desarrollar una clase nueva denominada CORE. Este concepto de core
es ligeramente distinto a lo que venı́amos nombrando como núcleo. La clase CORE representa el
esquema de los cores que componen el coprocesador. Si quisiéramos simular la tarjeta completa,
se crearı́an 50 objetos. Dado que en este trabajo no se toca el campo multi-thread, con simular un
core era suficiente. La idea se asemeja a la clase CACHE explicada en la Sección 5.2 (pág. 33). De
56
CAPÍTULO 7. ADAPTACIÓN DEL SIMULADOR
Figura 7.3: Localización del simulador de pipeline en CMP$im
hecho, están ı́ntimamente relacionados, ya que cada objeto CORE creado tendrá su objeto CACHE
asociado. Cada objeto CORE además tendrı́a su propio pipeline y sus propias estructuras de
almacenamiento de estadı́sticas y del estado del core en cada momento. Por otro lado, al tener
que almacenar información detallada sobre todas las instrucciones instrumentadas, ya fueran de
memoria o no, era necesaria una estructura solo modificable en tiempo de instrumentación con
el footprint de la aplicación. Además, también serı́a necesario guardar los registros utilizados en
tiempo de análisis para detectar dependencias. Véase la Figura 7.4 (pág. 56) para obtener una
imagen visual de estos conceptos.
Figura 7.4: Idea para la implementación de KNC
Las estructuras y clases principales desarrolladas para ello fueron las siguientes:
Footprint
Basic Block State
Register File
State
Stats
Core
7.3. NUEVAS ESTRUCTURAS Y CLASES
57
FOOTPRINT
Durante la fase de instrumentación se analizan todas las instrucciones de la aplicación. Es
necesario por tanto almacenar la información estática recopilada de cada una de ellas para su
posterior uso durante la fase de análisis. Esta estructura se implementó para tal fin. Los campos
de que consta son los siguientes:
Tipo: enumerado que recoge la siguiente clasificación posible:
Entera
1
2
3
4
5
6
7
8
Escalar
Vectorial
Memoria
typedef enum
{
INS_TYPE_NONVPU ,
INS_TYPE_V_VECTOR ,
INS_TYPE_V_SCALAR ,
INS_TYPE_MEM ,
INS_TYPE_NUM
} INS_TYPE_t ;
Latencia operacional: si una instrucción tiene esperar a que otra termine de realizar una
operación para usar su resultado, es necesario conocer cuánto va a tardar la operación en sı́.
Esto incluye a las instrucciones denominadas load-op, las cuales a la vez que cargan un dato
en la cache, operan sobre él una vez disponible. Sin embargo, este campo no servirá para
almacenar la latencia provocada por una instrucción de memoria.
Tamaño de la instrucción: para saber exactamente en qué punto termina un bloque
básico.
Registros fuente y destino: necesarios para realizar el control de dependencias de registro.
Desensamblado: con fines de depuración.
Rutina: a qué rutina pertenece la instrucción.
Esta estructura se declaró sobre un mapa STL, siendo la clave de acceso el PC de la instrucción.
Véase el Listado 7.6 (pág. 57).
1
typedef map < UINT64 , INS_FOOT_PRINT_t > FOOT_PRINT ;
Listado 7.6: Footprint de las instrucciones de la aplicación
REGISTER TABLE
La detección de las dependencias entre registros requiere llevar un seguimiento de las escrituras
de los mismos. Por ello, se creó una pequeña estructura que almacena la siguiente información.
Ciclo: se actualiza cada vez que se escriba el registro. Por tanto, indica el ciclo en el que un
registro estará disponible.
Dirección: PC de la instrucción que ha sido la última en escribir dicho registro. Sirve para
poder responsabilizar a una instrucción de que otra ha tenido que atrasar su ejecución.
Desglose de ciclos: para casos en que una instrucción de memoria haya escrito el registro,
se almacena el desglose de ciclos generados para conseguir el dato. Esta información es
temporal y solo influye en tiempo de simulación, no en los resultados finales. Serán vectores
de tamaño fijo dado por las macros KNC TLB LVLS y KNC CACHE LVLS.
58
CAPÍTULO 7. ADAPTACIÓN DEL SIMULADOR
Figura 7.5: Instrucción que toca dos lı́neas
Esta estructura se declaró en el interior de un mapa STL indexado por el código del registro.
Habrá tantas entradas como registros se hayan accedido en la ejecución. Véase el Listado 7.7
(pág. 58). Esta tabla solo se actualiza durante la simulación, puesto que ya tenemos disponibles
los registros utilizados por cada instrucción. Estos fueron previamente almacenados durante la
instrumentación de las instrucciones.
1
typedef map < UINT32 , REG_FILE_STATE_t > REG_FILE ;
Listado 7.7: Mapa STL de la Tabla de Registros
BASIC BLOCK STATE
Para llevar un seguimiento del comportamiento de las instrucciones de memoria para cada
uno de los bloques básicos ejecutados por la aplicación, presentamos la siguiente estructura. Tiene
carácter temporal. Los campos definidos son los siguientes:
Nivel de hit de TLB: es el último nivel de TLB al que se ha accedido. Este nivel incluirı́a
la memoria principal como último nivel en caso de tener que acceder a la tabla de páginas
del proceso.
Nivel de hit de Cache: análogo al campo anterior, pero para el caso de la jerarquı́a de
cache.
Desglose de ciclos de la TLB y de la CACHE: aquı́ se van almacenando los ciclos
requeridos por una instrucción al acceder a distintos niveles de ambas caches. Serán vectores
de tamaño fijo dado por las macros KNC TLB LVLS y KNC CACHE LVLS.
Fue declarada como parte de un mapa STL indexado por una clave que tiene dos campos <PC,
acceso>. Véase el Listado 7.8 (pág. 59) con la declaración con los tipos de datos requeridos. Al
realizar una instrucción un acceso a memoria, existe la posibilidad de que tenga que realizar más
de un acceso. Es posible que el dato no esté alineado, teniendo que tocar dos lı́neas de cache.
Véase la Figura 7.5 (pág. 58). En estos casos es necesario almacenar los accesos por separado ya
que el primero puede ser un fallo y el segundo un acierto. Si este histograma se indexara solo por
el PC de la instrucción, destruirı́amos la información del primer acceso al registrar el segundo.
Esto también ocurre de forma totalmente distinta, en el caso de instrucciones tipo gather. Esta
instrucción tiene la especialidad que no toca dos lı́neas porque el dato no está alineado, el interés
radica en que se despliega en todo un conjunto de accesos independientes los cuales a su vez pueden
estar desalineados. Al acceder a la estructura con el par <PC, acceso> el problema desaparece.
En el mapa de ciclos el primer acceso tendrá el valor <PC,0>, y el resto de accesos, por numerosos
que fueran, se indexarı́an incrementando el segundo campo.
7.3. NUEVAS ESTRUCTURAS Y CLASES
1
2
59
typedef std :: pair < UINT64 , UINT32 > BBL_ENTRY_KEY ;
typedef map < BBL_ENTRY_KEY , BBL_STATE_t > BBL_STATE ;
Listado 7.8: Mapa STL de los accesos a cache de las instrucciones de memoria de un BBL
STATE
Para hacer una fotografı́a al estado actual de la simulación se creó esta estructura. Engloba campos creados a partir de las estructuras anteriores BBL ST AT E y REG F ILE. Además
almacenará los siguientes campos:
Ciclo de issue: en el momento de la simulación, es importante conocer el ciclo en el que se
inserta una instrucción en el pipeline. De este modo se podrá determinar cuándo empezarı́a
la siguiente.
Ciclo de write back: en el caso de instrucciones que escriban en un registro, ya sea el
resultado de una operación o una carga de memoria, es importante saber en qué ciclo escribe
de cara a entregarle ese dato a la tabla de registros.
Estado de la memoria: desglose de ciclos de la última instrucción de memoria ejecutada.
Se creó para abrir la posibilidad de lanzar una aplicación multi-thread.
Última instrucción: información sobre la última instrucción que ocupó el pipeline.
STATS
La estadı́sticas son una parte fundamental. En esta clase se acumuları́an los datos necesarios
para su tratamiento a posteriori. En la Sección 7.4 (pág. 62) se explica al detalle este posttratamiento. Las estadı́sticas se acumulan desde dos perspectivas: a nivel de instrucción y a nivel
global.
Los campos de que consta las estadı́sticas por instrucción son los siguientes:
Bytes cargados: para propósitos de validación. Si sabemos exactamente cuántos elementos
va a recorrer un bucle y de qué tipo son, podemos localizar el bloque del bucle en el informe
final y validar que sus instrucciones de memoria han cargado el número de bytes esperados.
Ciclos totales de parada: número acumulado de ciclos que una instrucción ha tenido que
esperar, o ha tenido que hacer esperar, para entrar en el pipeline. Estas dos opciones se
corresponden con dos modos: productor y consumidor. Se trata de decidir a qué instrucción
culpar del retraso experimentado por una instrucción. Véase Sección 7.4 (pág. 62) para más
detalles.
Desglose de ciclos de parada (stalls): cada vez que una instrucción tiene que entrar
más tarde al pipeline, ocasiona un incremento en el cómputo total de ciclos del programa
impidiendo un IPC de 1. De cara a conocer qué recursos son los que están trabajando más
para generar el dato motivo de la dependencia, es necesario almacenarlo.
1
2
3
4
5
6
typedef enum
{
INS_STALL_ISSUE ,
INS_STALL_NONVPU ,
INS_STALL_V_SCALAR ,
INS_STALL_V_VECTOR ,
60
CAPÍTULO 7. ADAPTACIÓN DEL SIMULADOR
7
8
INS_STALL_NUM
} INS_STALL_t ;
Listado 7.9: Tipos de stalls
A los tipos de stalls presentados en el Listado 7.9 (pág. 59) se tendrı́an que añadir aquellos
relacionados con los accesos a memoria. Sin embargo las paradas por memoria se almacenarán
en vectores individuales que no necesitan el uso de un enumerado. Léase la descripción de
todas las opciones a continuación:
• issue: ciclos correspondientes a las instrucciones que lograron entrar al pipeline exactamente un ciclo después que la anterior. Es un motivo optimista.
• nonvpu: ciclos que las instrucciones tuvieron que esperar para entrar al pipeline a causa
de dependencias con instrucciones que no son ni escalares ni vectoriales.
• vpu 1 y vpu N : ciclos a esperar a causa de dependencias con instrucciones escalares y
vectoriales respectivamente.
Desglose de ciclos de parada por memoria: en el caso de que el motivo de la parada
fuera la memoria, se almacenará en vectores separados para el TLB y para el resto de la
jerarquı́a de cache. Léase descripción detallada:
• tlb: ciclos a esperar a causa de la traducción de direcciones. Este campo estarı́a a su
vez desglosado según la jerarquı́a establecida para los tlbs.
• cache: ciclos que se ha tenido que esperar en los niveles de cache que se hayan configurado, incluyendo la memoria principal, a causa tanto de dependencias de datos de
memoria como por congelaciones ocurridas en el pipeline debido a fallos en el primer
nivel de cache. Estos se tienen que resolver antes de que las siguientes instrucciones se
sigan ejecutando.
Las estadı́sticas por instrucción fueron definidas en un mapa cuyo ı́ndice es el PC de la instrucción.
1
typedef map < UINT64 , STATS_INS_s > STATS_INS_t ;
Listado 7.10: Mapa STL de las estadı́sticas por instrucción
Los campos de que consta las estadı́sticas globales son los siguientes:
Sumatorio global de ciclos: número global de ciclos consumidos en el core por la aplicación.
Desglose global de ciclos de parada (stalls y memoria): como en el caso del desglose
por instrucción, en este caso son los datos globales del core.
Al final se creó una estructura sencilla ambas estadı́sticas:
1
2
3
4
5
typedef struct
{
STATS_INS_t * stats_ins ;
STATS_GLB_t * stats_glb ;
} STATS ;
Listado 7.11: Estadı́sticas por instrucción y globales
7.3. NUEVAS ESTRUCTURAS Y CLASES
61
CORE
CORE es una clase que englobará todas las estructuras anteriores excepto el Footprint, puesto
que ésta es una estructura global para la aplicación y no especı́fica del core. El resto sı́ forman
R
parte de la especificación detallada de un CORE del coprocesador IntelXeon
PhiTM . La visión
simplificada de la misma es la siguiente:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class CORE {
STATE state ;
STATS stats ;
UINT32 coreID ;
void I n s e r t I n P i p e l i n e (
UINT32 tid ,
BBL_STATE :: iterator ins ) ;
void D i s t r i b u t e C y c l e s (
UINT32 tid ,
UINT64 storeLIP ,
COUNTER cycles ,
BBL_ENTRY_KEY culprit ,
bool regStall ,
bool memStall ,
BBL_ENTRY_KEY currentIP = make_pair (0 ,0) ,
INT32 regDependency = -1) ;
void I n s e r t B r e a k d o w n S t a t s (
UINT32 tid ,
STATS_INS_t :: iterator ,
UINT32 cycles ,
BREAKDOWN_t breakdown ,
UINT32 index ) ;
public :
CORE ( UINT32 coreID , UINT32 nThreads ) ;
~ CORE () ;
void Pipeline ( UINT32 tid , BBInfo * bbl ) ;
string P r i n t Gl o b a l S t a t s ( UINT32 tid ) ;
}
Listado 7.12: Clase CORE
Estas son las funciones destacadas:
Pipeline: pública. Se describió a grosso modo en la Sección 7.1 (pág. 49). Una vez terminada la simulación de la cache, es la función encargada de organizar el pipeline para las
instrucciones del bloque ya simulado en cuestión.
InsertInPipeline: privada. Inserta una única instrucción en el pipeline. Será invocada desde
la función Pipeline.
DistributeCycles: privada. Una vez insertada una instrucción en el pipeline, se invocará para distribuir los ciclos que ha tardado en entrar al pipeline, ya hubiera habido stalls o no.
InsertBreakdownStats: privada. Función genérica para determinar sobre qué vector se va
a insertar el desglose de ciclos generado por una instrucción.
62
CAPÍTULO 7. ADAPTACIÓN DEL SIMULADOR
Finalmente se creó un array de punteros a objetos de la clase CORE sobre el que se insertarı́an
todos aquellos que se establecieran en la configuración.
1
extern CORE * CoreArray [ MA X_ EX P ER IM EN T S ][ M AX _ NU M_ TH R EA DS ];
Listado 7.13: Array de punteros a objetos CORE
7.4.
Estadı́sticas
Durante el procedimiento de construcción del pipeline, no solo se tendrá información del número
total de ciclos, sino que para cada bloque tratado también se dispondrá especı́ficamente las razones
por las que las instrucciones tuvieron que entrar más tarde al pipeline. En el primer caso habı́a
que tener en cuenta que el último ciclo de la aplicación no tiene por qué ser el ciclo de fin de la
última instrucción del programa. El motivo es la terminación en desorden. Respecto a la segunda
información, es necesario recogerla para poder saber qué recursos son aquellos que impiden al
programa ir más rápido.
Una parte de la información sobre las instrucciones se encuentra insertada en el FOOTPRINT y
otra en la rama de la estructura STATS dedicada a instrucciones exclusivamente. Ambas han sido
ya expuestas en la Sección 7.3 (pág. 55). A la hora de almacenar los ciclos que ocasionan stalls se
planteó la posibilidad de decidir sobre qué instrucción almacenarlos. Se trataba de determinar a
cual culpabilizar. De cara a visualizar el cómputo total de ciclos esta decisión es indiferente, pero
no lo es si quisiéramos consultar información de un bloque concreto. Las dos alternativas sobre
las que responsabilizar son:
Productor: si una instrucción depende de que otra genere un dato, será la productora sobre
la que recaerı́an los ciclos.
Consumidor: en este caso, culparı́amos a la instrucción dependiente por tener que necesitar
un dato.
La opción por defecto fue responsabilizar al consumidor.
Finalizada por tanto la ejecución de la aplicación, y la recogida de las estadı́sticas correspondientes, es el turno de la función que realmente pone fin a toda la simulación. Si se recuerda la
Sección 5.1 (pág. 31), en caso de que la ejecución fuera en modo buffer, al no haber más bloques
que se encarguen de lanzar la última simulación de cache, es necesario que esta función se haga
cargo de subsanarlo. Posteriormente, tanto para modo buffer como instrucción a instrucción, se
lanzarı́a la construcción del pipeline y se recogerı́an las estadı́sticas. Finalmente, llegarı́a el momento de invocar a las funciones que se encargan de escribir toda la información recopilada en un
fichero para su posterior tratamiento. Ni qué decir tiene que CMP$im ya almacenaba información
acerca de los accesos y fallos de las instrucciones desglosadas por nivel de cache, entre otros. Esta
información será aprovechada para completar nuestras estadı́sticas.
El objetivo reside en crear una tabla en el informe de salida con toda la información desglosada
por cada una de las regiones tratadas: funciones, bloques e instrucciones; y separadas por comas,
’,’. La cabecera contendrı́a los siguientes campos:
Región: cada una de las entradas estarı́a clasificada con las palabras clave FUN, BEG, INS y
GLB, significando función, bloque, instrucciones y global, respectivamente. La global será una
región mostrada al final de la tabla con el acumulado de todos los datos.
7.4. ESTADÍSTICAS
63
Direcciones de comienzo y fin: en el caso de funciones y bloques, serı́an las direcciones de
comienzo y fin. La dirección de comienzo coincide con el PC de la primera instrucción, y la
dirección de fin coincide con la primera instrucción del bloque siguiente. El objetivo es que
esta información incluya el tamaño de estas regiones sin tener que agregar más campos. En
el caso de las instrucciones, aparecerı́a únicamente su PC, quedando el campo de dirección
de fin vacı́o.
Ejecuciones del bloque: solo se mostrará si la región es el bloque, quedando vacı́o para las
regiones restantes. Indica el número de veces que se ha ejecutado el bloque.
Número de instrucciones: para funciones y bloques, contendrá el número total de instrucciones ejecutadas.
Número total de bytes: se corresponde con el total de bytes que han sido solicitados por
las instrucciones de carga, load. Su finalidad es, entre otras, de validación, para comprobar
que efectivamente el número de bytes cargados por un bloque particular es el esperado. Se
mostrará para todas las regiones.
Número total de ciclos: es un modo de tener una idea del número total de ciclos requeridos
por todas las instrucciones sin necesidad de realizar cálculos adicionales.
Accesos desglosados: esta información se encontraba ya disponible para cada instrucción,
desglosada por nivel de cache. Se presentará pues para cada instrucción tal y como está ya,
y se realizará el acumulado para mostrarlo por bloque y función.
Fallos desglosados: es análogo a los accesos, pero mostrando los fallos.
Ciclos desglosados: se corresponden con toda la retahı́la de razones explicadas al inicio de
esta sección.
En este punto, lo tenemos todo para visualizar la información. Sin embargo, si recordamos el
punto sobre los bloques básicos de la Sección 5.2 (pág. 33), originalmente se almacenaban en una
lista para no ralentizar la simulación. Es ahora cuando se tiene que trabajar sobre la lista para
convertirla en un mapa con bloques únicos. Inicialmente, se lleva a cabo generando una worklist
indexada por una clave de dos campos, <PC,PC>, correspondientes a la dirección de la primera
instrucción y de la última. Véase la declaración en el Listado 7.14 (pág. 63). A continuación, se
analizan los bloques de dos en dos, como se muestra en la Figura 7.6 (pág. 63) y la Figura 7.7
(pág. 64), para cortarlos y fusionarlos siempre que sea necesario. Una vez hecho esto, el proceso
consiste en un bucle que se recorre mientras haya entradas (bloques) en la worklist. Para evitar
problemas al tratar el último bloque, se introduce al final de la worklist uno ficticio con unas claves
ficticias. A medida que se recorre la worklist, en caso de no haber ningún corte se elimina el primer
bloque para introducirlo en el mapa de bloques únicos. Si se produce un corte, se modifican los
bloques consecuentemente y se vuelve a iterar. Cuando la lista solo contiene el bloque ficticio se
finaliza el proceso.
1
map < pair < UINT64 , UINT64 > , COUNTER > BBInfoMap ;
Listado 7.14: Mapa STL de Bloques Básicos
Figura 7.6: Bloques con ningún y un corte
Finalmente, se escribirı́a toda la información en el informe final y se finalizarı́a el proceso.
64
CAPÍTULO 7. ADAPTACIÓN DEL SIMULADOR
Figura 7.7: Bloques con 2 cortes
7.5.
Invocación activando la funcionalidad vectorial
La lı́nea de invocación que veı́amos en la Sección 5.3 (pág. 37) se incrementa en un knob con
el que se activa la funcionalidad vectorial. La entrada de datos del knob nuevo es el fichero de
configuración de las latencias de instrucciones que se presentaron en la Sección 7.2.3 (pág. 54).
1
2
3
4
5
6
7
8
9
10
11
pin -t cmpsim - cache DL1 :32:64:8:1
- cache UL2 :1024:64:16:1
- tlb DTLB :64:4096:8
- tlb DTLB2 :256:4096:8
- dl1lat 4
- l2lat 16
- dtlblat 0
- dtlb2lat 6
- inclusive 1
- threads 1
- dependencies inslat . conf -- <app > < input >
Listado 7.15: Lı́nea para el lanzamiento de la simulación
Capı́tulo 8
Estudio experimental
En el Capı́tulo 6 (pág. 39) presentamos la caracterización inicial de los benchmarks seleccionados, desde los puntos de vista del indice de vectorización y el desglose de motivos determinados
por el compilador para no vectorizar. Teniendo esto en cuenta este capı́tulo fue dividido en dos
partes: resultados y diagnóstico. En la primera presentaremos gráficamente los resultados obteniR
dos a partir de la simulación en el CMP$im modificado con las capacidades vectoriales del Intel
Xeon PhiTM descrito en el Capı́tulo 7 (pág. 49). Estos resultados incluyen las ejecuciones escalares
y vectoriales de todas las aplicaciones. Querı́amos conocer, tanto a nivel de instrucciones como
de ciclos, cuán diferente es la versión vectorizada frente a aquella en la que no se activó la vectorización. El motivo reside en que esta comparativa proporciona un punto de vista adicional al
ya visto con los ı́ndices de vectorización. Con esta información decidiremos entonces acerca de
qué aplicaciones analizar en profundidad para detectar los focos donde se pueda explotar la vectorización en mayor medida. En la segunda parte del capı́tulo, el diagnóstico, dividiremos el análisis
en dos partes: software y hardware. En el diagnóstico software se analizan los bloques básicos
más ejecutados de la aplicación, el código fuente y el compilado. En el diagnóstico hardware se
presentarán los resultados obtenidos al modificar la cache, de cara a comprobar qué efectos tendrı́a
en aquellas aplicaciones que, pese a tener un buen ı́ndice de vectorización, se encuentran limitadas
en su ejecución por los accesos a memoria.
8.1.
Resultados
8.1.1.
Polyhedron
La Figura 8.1 (pág. 66) muestra la relación entre el número de instrucciones y de ciclos de
las versiones vectorizada y no vectorizada de los benchmarks de Polyhedron. El eje de abscisas
se corresponde con la relación de instrucciones ejecutadas de la versión vectorizada frente a la
no vectorizada. El eje de ordenadas se corresponde con la relación de ciclos, obtenidos a través
de la versión modificada de CMP$im, también entre las versiones vectorizada y no vectorizada.
Además, se han dibujado segmentos de los siguientes tres colores:
verde: marca los puntos de la gráfica en los que la relación de instrucciones para ambas
versiones es igual a 1.
rojo: análogamente al segmento verde, marca los puntos de la gráfica en los que la relación
de ciclos para ambas versiones es igual a 1.
65
66
CAPÍTULO 8. ESTUDIO EXPERIMENTAL
morado: indica los puntos donde las relaciones de instrucciones y ciclos sen han visto afectadas de forma equitativa.
A continuación se explican en detalle las diferentes situaciones e implicaciones de cada punto
en esta gráfica desde los puntos de vista de las instrucciones y los ciclos:
Instrucciones
== 1: linea verde. Se pueden dar dos circunstancias: o bien no se ha vectorizado, o bien sı́ se
ha hecho pero la inclusión de instrucciones adicionales relacionadas con la vectorización ha
hecho que el número total de instrucciones no disminuya. Es común ver a las aplicaciones
que cumplen esto arremolinadas en el punto de intersección (1, 1).
< 1: se ha conseguido vectorizar. Se encontrarı́an dentro del área cuadrada encerrada por
los segmentos verde y rojo.
> 1: se ha vectorizado pero, debido a comprobaciones o instrucciones relacionadas con la
vectorización, el número de instrucciones ejecutadas ha resultado ser mayor. En la gráfica,
estas aplicaciones estarı́an fuera del área cuadrada formada por los segmentos verde y rojo.
Figura 8.1: Versión vectorizada vs no vectorizada de Polyhedron
Ciclos
== 1: linea roja. Hay dos circunstancias en relación a la relación de instrucciones vista:
1. relación de instrucciones == 1: si no hay mejora de instrucciones no la hay de ciclos.
2. relación de instrucciones < 1: pese al éxito de la vectorización, la aplicación está limitada
por memoria. Las aplicaciones se encontrarı́an adyacentes al segmento rojo.
< 1: de nuevo se producen dos circunstancias:
1. la reducción de instrucciones y de ciclos siguen más o menos la misma lı́nea. Aquellas
próximas al segmento morado cumplen este patrón.
8.1. RESULTADOS
67
2. pese a que se hayan reducido el número de instrucciones, esta reducción no se ha traducido en una reducción de ciclos equivalente. Se encontrarı́an dentro del área encerrada
por los segmentos morado y verde.
> 1: la vectorización provocó un aumento de ciclos.
Volviendo a Polyhedron, la caracterización de las aplicaciones tfft, fatigue, doduct, mdbx, nf
e induct, las hacı́a destacar como posibles candidatas para su análisis dado alto porcentaje de
instrucciones escalares frente a las vectoriales. Entre ellas, la Figura 8.1 (pág. 66) nos destaca
fatigue y, como novedad, gas dyn. La primera porque al vectorizar se ha obtenido un 3 % más de
instrucciones que han supuesto un 9 % más de ciclos. La segunda porque en su caracterización
pasaba desapercibida entre el resto, mientras que aquı́ destaca frente a la versión escalar, con un
90 % menos de instrucciones ejecutadas y un 86 % menos de ciclos.
Por otro lado, aplicaciones como channel, linpk y test fpu, que eran las que mejor ı́ndice de
vectorización mostraban en su caracterización, en esta gráfica se muestran como un claro ejemplo
de estar limitadas por la memoria. Se observa que la reducción de instrucciones no se traduce
en la misma medida en la de ciclos. En el caso de channel, por ejemplo, es un 79 % menos de
instrucciones frente a un 9 % menos de ciclos.
El resto de aplicaciones se encuentran o bien distribuidas a lo largo del segmento morado, el
cual es un buen resultado ya que indica que la vectorización ha funcionado, o bien aglomeradas
en el punto (1, 1), donde la vectorización no ha dado buenos resultados.
Figura 8.2: Ciclos desglosados de las aplicaciones de Polyhedron
La Figura 8.2 (pág. 67), que muestra en forma de ciclos las razones por las que los IPCs de las
aplicaciones son inferiores a 1, sirve para confirmar que las aplicaciones channel, linpk y test fpu,
están claramente penalizadas por los continuos accesos a la memoria principal. Las denominaremos
memory bound. tfft, pese a que en su caracterización presentaba el peor ı́ndice de vectorización,
0,07 %, resulta que también es memory bound, con lo que una posible mejora en la vectorización
podrı́a no traducirse en una mejora de ciclos. Las mejoras para abordar esta aplicación residirı́an
en encontrar una configuración de memoria adaptada a ella, para luego identificar las deficiencias
en su código. En el caso de capacita, que también entra en esta categorı́a, es la única que presenta
un perfil semejante, ya que tiene un 38 % de ciclos a consecuencia de fallos en el segundo nivel de
TLB. En este caso, parece viable probar a ejecutarla modificando el número de lı́neas en el TLB
de segundo nivel.
Siguiendo la lı́nea de las aplicaciones memory bound, tenemos a gas dyn como caso particular.
Pese a su excelente resultado respecto a la versión escalar, su IPC es 0,19. Esto se debe a los fallos
68
CAPÍTULO 8. ESTUDIO EXPERIMENTAL
en DL1, que se traducen en un porcentaje alto de aciertos en UL2. Por muy buen uso de la unidad
vectorial que haga, el rendimiento general de la aplicación no es acorde. Ya que los responsables
son los fallos en la DL1 y no en la UL2, serı́a interesante de analizar.
Al lado derecho de la gráfica tenemos aplicaciones como induct, doduc, aermod y fatigue,
que tienen los mayores porcentajes de ciclos ocasionados por dependencias sobre instrucciones
escalares. Si se aumentara el número de vectoriales que sustituyeran a las escalares, se reducirı́an
estos ciclos. Es posible que, como efecto colateral, se trasladaran las dependencias entre escalares
a las vectoriales. Serı́a una solución aceptable pues habrı́a mejorado el uso efectivo de la unidad
vectorial del procesador, pudiéndose plantear soluciones de otra ı́ndole. De todos modos, hay que
recordar que el compilador conseguı́a vectorizar un 67 % de bucles de doduc, por lo que no da
mucho margen de maniobra. No es ası́ con induct, fatigue y aermod, que se situaban a la cola. La
de más interés parece ser aermod al tener mas de 2500 bucles registrados.
Finalmente tenemos a protein, aplicación con un 96 % de instrucciones enteras. Fue descartada
de entrada como candidata a ser analizada debido a que los esfuerzos deberı́an centrarse en la
reducción de instrucciones escalares. Como las enteras tienen una latencia de un ciclo por defecto,
provocan que el porcentaje de instrucciones que entran correctamente en el pipeline aumente,
incrementando entonces el IPC y situándola a la cabeza. Recuérdese que si una instrucción entraba
correctamente en el pipeline, el ciclo se clasificaba con el nombre Issue. Sin embargo, dejando a
un lado este detalle, el lı́mite para que se obtenga un resultado mejor son los accesos a DL1.
En realidad estos accesos son en su mayorı́a aciertos en la DL1, ya que no se ve la participación
ni de la UL2 ni de memoria principal. Por tanto, estos ciclos implican dependencias de otras
instrucciones sobre los datos cargados por loads. Representan un 47 % del total de ciclos. Este
tipo de aplicaciones serı́a mejor englobarla fuera de las memory bound para clasificarlas en su
lugar como cache dependence bound.
Las candidatas a un análisis más profundo, según los razonamientos arriba expuestos, son:
aermod: su objetivo es simular el modelo de dispersión de aire ISCST2.
induct: su propósito es generar la entrada de PSpice que sirva para el modelado de las
propiedades electromagnéticas de baja frecuencia de un sistema de comunicaciones res-q.
fatigue: sirve para modelar la fatiga de los metales dúctiles.
gas dyn: destinada a resolver ecuaciones de continuidad de masa, momento y energı́a con
el objetivo de modelar el flujo de un gas es una dimensión.
8.1.2.
Mantevo
La caracterización de las aplicaciones de Mantevo invitaba a examinar algunas de ellas como
Cloverleaf, ya que su ı́ndice de vectorización ascendı́a a un 73 % de las instrucciones ejecutadas
del programa. El resto de aplicaciones con un ı́ndice inferior al 40 % y con poco peso en cuanto
a instrucciones escalares, no parecı́an ser candidatas de interés. Entre ellas, la que obtenı́a peores
resultados era CoMD, con un 0,21 % de instrucciones vectoriales frente a un 30 % de escalares,
datos que parecı́an concordar con el informe del compilador.
La Figura 8.3 (pág. 69), que se presenta con la comparación entre las versiones escalar y vectorial, muestra un Cloverleaf que presumiblemente va a estar limitado por la memoria principal. La
reducción de ciclos es de un 19 % frente al 81 % sobre las instrucciones. Este mismo comportamiento lo presentan HPCCG, miniFE y miniGhost. CoMD se reitera como candidata a ser analizada
porque se encuentra fuera del área encerrada por los segmentos.
8.1. RESULTADOS
69
Figura 8.3: Versión vectorizada vs no vectorizada de Mantevo
El desglose de ciclos de la Figura 8.4 (pág. 69) confirma que Cloverleaf, HPCCG, miniFE y
miniGhost se engloban dentro del grupo memory bound. Por otro lado, pese a que en CoMD se da
la circunstancia mencionada sobre que se encuentra fuera del área en la primera figura, significando
que la versión vectorial ha sido peor que la escalar, presenta aquı́ un interesante porcentaje de
dependencias con instrucciones escalares sobre las que se puede centrar el análisis. Esto reafirma
una vez más su candidatura.
Figura 8.4: Ciclos desglosados de las aplicaciones de Mantevo
Finalmente vemos que miniXyce es una aplicación con un comportamiento análogo al manifestado por protein de Polyhedron. Su caracterización mostraba un 69 % de instrucciones enteras
de latencia 1, que se traducen aquı́ en un gran porcentaje de ciclos Issue que incrementan el IPC
falseando su apariencia de buena candidata.
La única candidata de este benchmark para ser analizada en profundidad, será:
CoMD: es un proxy para computación en aplicaciones tipo Molecular dynamics. Todas las
aplicaciones de este tipo tienen caracterı́sticas compartidas, como son los parámetros x, y, z
70
CAPÍTULO 8. ESTUDIO EXPERIMENTAL
de cada partı́cula, operaciones en las que unas partı́culas interaccionan con otras, etc.
8.1.3.
Sequoia
Sequoia presentaba ya dos candidaturas para realizar un diagnóstico software: SPhotmk y
Crystalmkm. En la Figura 8.5 (pág. 70) Crystalmk presenta un comportamiento a ser analizado
por su empeoramiento respecto a la versión escalar, que asciende a un 13 % de empeoramiento en
cuanto a instrucciones y un 15 % en ciclos. IRSmk y UMTmk presumiblemente estarán limitadas
por memoria, pese a tener unos ı́ndices de vectorización del 54 % y 24 %, respectivamente. Este
hecho se ve corroborado en la Figura 8.6 (pág. 70).
Figura 8.5: Versión vectorizada vs no vectorizada de Sequoia
Figura 8.6: Ciclos desglosados de las aplicaciones de Sequoia
En el caso de SPhotmk y Crystalmk presentan altos porcentajes de dependencias ocurridas por
instrucciones escalares, por tanto siguen siendo candidatas:
SPhotmk: consiste en un conjunto seleccionado de pequeñas porciones de código de otros
8.1. RESULTADOS
71
paquetes mayores. No realiza cálculos fı́sicos per se, sino que engloba procesos como un
solucionador de sistemas lineales por Cholesky, bucles con divisiones, bucles con llamadas a
funciones matemáticas de tipo built.in, etc.
SPhotmk: sirve para medir el rendimiento de la CPU y comprobar que los resultados que
proporciona son correctos.
8.1.4.
NPB
Las aplicaciones que inicialmente se consideraron candidatas fueron UA, IS, BT y LU. En
éstas, el grado de instrucciones escalares era del 68, 35, 71 y 50 %, respectivamente. El hecho de
que el número de instrucciones escalares sea grande ayuda a que, localizando los bloques a los que
pertenecen, se puedan concentrar los esfuerzos en ayudar al compilador a vectorizarlos.
Figura 8.7: Versión vectorizada vs no vectorizada de NPB
En el área delimitada por los segmentos rojo y morado de la Figura 8.7 (pág. 71), se encuentran
las aplicaciones limitadas por la memoria principal, más próximas al segmento rojo que al morado.
En este benchmark, a diferencia de los anteriores, nos encontramos con muy pocas aplicaciones
en el punto (1,1), donde figurarı́an aquellas cuyos resultados de la versión vectorial se asemejarı́an
a la versión escalar. En cambio, la mayorı́a se encuentra limitada por memoria en mayor o menor
medida. Entre las destacadas por la limitación de memoria se encuentran MG, con un 78 % de
reducción de instrucciones frente al 26 % de mejora en ciclos; FT, con un 63 % de reducción frente
al 29 % de ciclos, SP con un 55 % y 16 %, respectivamente, y CG con un 51 % de instrucciones
reducidas frente al 5 % de ciclos. De las mencionadas, FT fue la que presentaba el mejor ı́ndice
de vectorización, pese a ser memory bound, con un 52 % de instrucciones vectoriales ejecutadas
frente al 2 % de escalares. EP, por el contrario, tenı́a un ı́ndice de vectorización que competı́a con
el de instrucciones escalares (33 % y 30 %, respectivamente). Sin embargo, aquı́ es la que presenta
una mayor mejora, teniendo un 65 % de reducción de instrucciones y 53 % de ciclos.
En el otro extremo tenemos DC. Su ı́ndice de vectorización mostraba un 97 % de instrucciones
enteras ejecutadas. Aquı́ figura como que el intento de vectorización no ha hecho sino empeorar el
número de instrucciones en un 2 %. La relación de ciclos se ha mantenido constante, presumiblemente por el gran número de instrucciones enteras. Por este motivo, no merece la pena pararse a
72
CAPÍTULO 8. ESTUDIO EXPERIMENTAL
analizar las razones de esta situación, cuando en general no vamos a poder aumentar el número
de instrucciones vectoriales de la misma.
Figura 8.8: Ciclos desglosados de las aplicaciones de NPB
En la Figura 8.8 (pág. 72) los ciclos desglosados presentan una fuerte tendencia hacia la limitación provocada por los accesos a memoria principal. MG, CG, SP y UA encabezan esta tendencia.
En el caso de UA es importante saber que la reducción de instrucciones respecto a la versión
escalar es pequeña, 17 %. Por tanto, aunque fuera una candidata a mejorar, si la limitación por
memoria está presente, mejorar el ı́ndice de vectorización no va a erradicar su perfil memory bound.
La aplicación IS es interesante porque presenta muchos accesos a memoria por culpa de fallos en
ambos niveles de TLB. Estos ciclos, junto a aquellos por fallos en la UL2, hacen un total del 75 %
de ciclos de memoria. Por tanto, pese a que esta aplicación se presentaba como una posible candidata, serı́a necesario aplicar mejoras sobre la jerarquı́a de memoria en primer lugar. La estadı́stica
de EP, con un 35 % de ciclos por dependencias de instrucciones vectoriales, confirma su ı́ndice de
vectorización del 33 %. Igualmente, no se trata de algo positivo porque no se consigue un buen
IPC. Esta aplicación entonces se clasificarı́a como dependence bound.
Finalmente tenemos la aplicación DC, dominada por instrucciones enteras, la cual repite un
comportamiento que viene siendo habitual en todos los bechmarks cuando se identifican aplicaciones principalmente enteras, esto es, tener el mejor IPC. Sin embargo, esta vez ni es tan alto ni
se refleja en un gran porcentaje de ciclos Issue debido a los fallos de TLB y en la jerarquı́a de
cache. Igual no la podrı́amos clasificar como memory bound, porque ya se sabı́a que parte de los
ciclos de DL1 que engordan su barra son ocasionados por dependencias con instrucciones de carga
de datos. Por ello, se la clasificarı́a como memory dependence bound.
Tras todo este análisis, las aplicaciones seleccionadas son:
BT: de Block Tridiagonal, es una aplicación que presenta un solucionador de sistemas no
lineales de ecuaciones con derivadas parciales usando matrices de bloques tridiagonales.
LU: de Lower-Upper, es una aplicación que tiene el mismo objetivo que BT, es decir, la
resolución de un sistema, pero aquı́ es realizada aplicando el método de factorización LU por
Gauss-Seidel.
8.1.5.
SPEC fp
Las aplicaciones de más interés mencionadas durante la caracterización fueron namd, povray
y milc, porque presentaban un ı́ndice de vectorización muy pequeño: 1 %, 2 % y 2,4 %, respectiva-
8.1. RESULTADOS
73
mente. Predominaba en todas ellas el porcentaje de instrucciones escalares: 58 %, 32 % y 84 %.
Figura 8.9: Versión vectorizada vs no vectorizada de SPEC fp
La Figura 8.9 (pág. 73) nos muestra que lbm, teniendo el mejor ı́ndice de vectorización, ascendiendo este a un 76 %, no se ve acompañada por una mejora semejante en el número de ciclos.
Se clasifica por tanto en el conjunto de aplicaciones memory bound. Independientemente de este
hecho, es una ventaja que el único frente que haya que abordar sean los accesos a memoria, ya
que la mejora en el número de instrucciones es de un 77 % y el ı́ndice de vectorización no deja
lugar a dudas de que es una aplicación que hace un uso efectivo de los procesadores vectoriales.
Otro caso es el de leslie3d, que muestra una mejora semejante tanto a nivel de instrucciones como
de ciclos. En este caso el ı́ndice de vectorización ascendı́a solo al 33 %.
Figura 8.10: Ciclos desglosados de las aplicaciones de SPEC fp 2006
Zeusmp y cactusADM son interesantes porque ambas tenı́an un ı́ndice de vectorización semejante: 35 % y 37 %, respectivamente. Sin embargo, zeusmp no mejora tanto respecto al número
de ciclos (36 %), categorizándose como memory bound. Por su parte, cactusADM presenta una
mejora del 62 %. La aplicación Wrf, comparándola con zeusmp por proximidad, pese a presentar
un ı́ndice de vectorización del 24 % obtiene mejor resultados respecto a la versión escalar: 73 %
de reducción de instrucciones, frente al 72 % de zeusmp, y 40 % de reducción de ciclos, frente al
74
CAPÍTULO 8. ESTUDIO EXPERIMENTAL
36 % de zeusmp. Esto significa, que pese a que una aplicación tenga peores resultados respecto al
número de instrucciones vectorizadas, su cómputo global respecto a la versión no vectorial puede
ser mejor. Gromacs, al igual que cactusADM, tiene incluso una mejor relación entre reducción de
ciclos e instrucciones, siendo de un 53 y un 55 % respectivamente.
En el extremo del punto (1, 1) se observa la concentración del resto de aplicaciones, algunas
más limitadas por memoria y otras sin cambios aparentes, pero en general con mejoras discretas.
Fuera de estas, se observan namd y povray sin mejora alguna. Igualmente, este empeoramiento
es pequeño de cara al número de instrucciones, siendo de un 0,49 % en namd y un 0,46 % en
povray. Lo más significativo es el empeoramiento de povray respecto al número de ciclos, siendo
un aumento de un 4,7 %.
En la Figura 8.10 (pág. 73), en el extremo de las aplicaciones que no están limitadas por
memoria, nos encontramos a calculix. Su elevado IPC era de esperar gracias a las instrucciones
enteras que lo componen, suponiendo éstas un 92 % de las instrucciones ejecutadas por la aplicación. De las aplicaciones que le siguen, soplex también es una aplicación mayoritariamente entera,
con un 81 % de instrucciones de este tipo. Pese a esto, las instrucciones escalares y vectoriales que
aportan un 3 % y un 16 % respectivamente al total, provocan dependencias que impiden obtener
un porcentaje de ciclos Issue mayor.
Finalmente, comentar que el interés principal de esta gráfica radica en centrarnos en si aquellas
que aportan mayor número de ciclos por culpa de dependencias de instrucciones escalares, tienen
caracterı́sticas destacadas por el resto de gráficas para determinar su candidatura. Es el caso por
ejemplo de namd y povray, que se mencionaron en la caracterización. Por tanto, las seleccionadas
para su análisis serán:
namd: es una aplicación que simula grandes sistemas biomoleculares.
povray: realiza un tratamiento sobre una imagen de dimensiones 1280x1024 que contiene
un paisaje con objetos abstractos, usando para ello un filtro de ruido denominado Perlin,
por su autor Ken Perlin.
8.2.
Diagnóstico Software
El diagnóstico es un proceso en el que, conociendo a priori el comportamiento de la aplicación,
tanto de cara al perfil estático que nos proporciona el compilador como durante su ejecución, nos
adentramos en su interior para determinar qué bloques básicos son los que están impidiendo un
uso más efectivo de la unidad vectorial. Estos bloques básicos serán los que al final nos den las
claves para plantear una posible solución con el interés de incrementar el número de instrucciones
vectoriales ejecutadas por la aplicación.
La forma de operar en esta sección será la siguiente: se tomarán cada uno de las aplicaciones
seleccionadas en la Sección 8.1 (pág. 65) y, para cada una, se consultarán los bloques básicos
más ejecutados, se analizarán las porciones de código fuente que se corresponden con ellos en la
búsqueda de bucles potencialmente vectorizables, se consultará el resultado del compilador para
con los bucles encontrados y se tratará de proponer alguna solución que pudiera ser viable para
su mejora. Respecto a las soluciones propuestas, no era objetivo de este trabajo implementarlas,
sino dejarlas como una posible guı́a de cara a trabajos futuros.
8.2. DIAGNÓSTICO SOFTWARE
8.2.1.
75
Polyhedron
Las aplicaciones del benchmark Polyhedron seleccionadas para un análisis en profundidad son:
Fatique, Induct, Aermod y Gas dyn.
Fatigue
Recordando las caracterı́sticas que se presentaron de Fatigue durante la caracterización, tenı́a
91 bucles, de los cuales un 48 % no se habı́a vectorizado. La razón considerada por el compilador
era que la vectorización, pese a ser posible, resultaba ineficiente.
PC
Función
Instrucciones
%
Ciclos
%
1
0x407a50
perdida m mp generalized
hookes law
3.583.528.433
23,0
5.074.024.833
8,59
2
0x40238e
MAIN
3.328.267.995
21,3
8.311.166.827
14,1
Tabla 8.1: Bloques 1 y 2 de la lista de bloques básicos más ejecutados en Fatigue, Polyhedron
La Tabla 8.1 (pág. 75) muestra los dos bucles más ejecutados de la aplicación. Los campos de
esta tabla son: posición del bucle en la lista, PC de la primera instrucción del bloque, nombre de la
función, instrucciones ejecutadas, porcentaje respecto al total, ciclos contabilizados y porcentaje
respecto al total de la aplicación. El primero de los bloques se corresponde con una función
denominada generalized hookes que se encuentra dentro del fichero fatigue.f90 de la aplicación.
1110 ! - - - - - - - - - - - - FUNCTION G E N E R A L I Z E D _ H O O K E S _ L A W - - - - - - - - - - - - - - - - - - - - - 1111
1112 function g e n e r a l i z e d _ h o o k e s _ l a w ( strain_tensor , lambda , mu ) result (←stress_tensor )
Con una primera mirada al código de la función, vemos que está formado por multitud de operaciones realizadas sobre los elementos de un vector de dimensión 6 y de una matriz de dimensiones
6x6. A modo de ejemplo se muestran algunas lı́neas:
1158
1159
1160
1161
1162
1163
g e n e r a l i z e d _ c o n s t i t u t i v e _ t e n s o r (: ,:)
g e n e r a l i z e d _ c o n s t i t u t i v e _ t e n s o r (1 ,1)
g e n e r a l i z e d _ c o n s t i t u t i v e _ t e n s o r (1 ,2)
g e n e r a l i z e d _ c o n s t i t u t i v e _ t e n s o r (1 ,3)
g e n e r a l i z e d _ c o n s t i t u t i v e _ t e n s o r (2 ,1)
g e n e r a l i z e d _ c o n s t i t u t i v e _ t e n s o r (2 ,2)
=
=
=
=
=
=
0.0 _LONGreal
lambda + 2.0 _LONGreal * mu
lambda
lambda
lambda
lambda + 2.0 _LONGreal * mu
Además, existe un pequeño bucle dentro que el compilador no ha vectorizado por considerar
dicha vectorización como ineficiente.
1183
1184
1185
1186
do i = 1 , 6
g e n e r a l i z e d _ s t r e s s _ v e c t o r ( i ) = dot_product (
g e n e r a l i z e d _ c o n s t i t u t i v e _ t e n s o r (i ,:) ,
g e n e r a l i z e d _ s t r a i n _ v e c t o r (:) )
end do
Pese a que 6 son pocas vueltas para un bucle, y que el número de datos en punto flotante
es pequeño como para llegar a llenar los 16 que puede albergar un registro vectorial de 512 bits,
76
CAPÍTULO 8. ESTUDIO EXPERIMENTAL
se podrı́a utilizar el pragma vector always como primera opción de mejora. Al mismo tiempo, se
podrı́an convertir todos las lı́neas que operan sobre los vectores y las matrices en bucles.
Después de revisar el flujo de ejecución del programa, vemos que el bloque correspondiente a
esta función solo es accedido desde un único call. La única función que invoca a ésta se denomina
perdida. Pese a que no tiene bucles, si se comprueba el bloque desde el que se invoca, descubrimos
que en realidad se ha hecho inline en la función main. Este bloque en cuestión se corresponde
además con el tercero más ejecutado. Véase la Tabla 8.2 (pág. 76).
3
PC
Función
Instrucciones
%
Ciclos
%
0x401eb4
MAIN
2.873.120.235
18,4
7.140.131.627
12,1
Tabla 8.2: Bloque 3 de la lista de bloques básicos más ejecutados en Fatigue, Polyhedron
A continuación se muestra una pequeña porción de la función main donde se invoca a perdida:
1435 do n = 1 , n u m b e r _ o f _ s a m p l e _ p o i n t s
1436
if (. not . failed ( n ) ) then
1437
call perdida ( dt , lambda , mu , yield_stress , R_infinity , b ,
1438
gamma , eta , plastic_strain_threshold , stress_tensor (: ,: , n ) ,
1439
strain_tensor (: ,: , n ) , p l a s t i c _ s t r a i n _ t e n s o r (: ,: , n ) ,
1440
s t r a i n _ r a t e _ t e n s o r (: ,: , n ) , a c c u m u l a t e d _ p l a s t i c _ s t r a i n ( n ) ,
1441
b a c k _ s t r e s s _ t e n s o r (: ,: , n ) , i s o t r o p i c _ h a r d e n i n g _ s t r e s s ( n ) ,
1442
damage ( n ) , failure_threshold , c r a c k _ c l o s u r e _ p a r a m e t e r )
1443
end if
1444 end do
Sin embargo, el compilador indica que el bucle anterior no es un bucle interno:
fatigue.f90(1435): (col. 10) remark: loop was not vectorized: not inner loop
Revisando de nuevo el código de la función perdida, encontramos operaciones realizadas sobre
todos los elementos de una matriz de este modo:
927
928
929
...
940
941
...
950
...
955
stress_tensor (: ,:) = (1.0 _LONGreal - damage ) *
g e n e r a l i z e d _ h o o k e s _ l a w ( strain_tensor (: ,:) p l a s t i c _ s t r a i n _ t e n s o r (: ,:) , lambda , mu )
stress_tensor (: ,:) = (1.0 _LONGreal - c ra c k_ pa ra m et er * damage ) /
(1.0 _LONGreal - damage ) * stress_tensor (: ,:)
d e v i a t o r i c _ s t r e s s _ t e n s o r (: ,:) = stress_tensor (: ,:)
d a m a g e d _ d e v _ s t r e s s _ t e n s o r (: ,:) = d e v i a t o r i c _ s t r e s s _ t e n s o r (: ,:) /
(1.0 _LONGreal - cr ac k _p ar am e te r * damage )
El operador (:, :) del código anterior, usado para operar sobre los elementos de una matriz, es
una manera corta de indicar que en realidad la operación tiene que ser realizada sobre todos los
elementos. Es por tanto un bucle simplificado. Tiene entonces sentido que el compilador notificara
que el bucle que engloba estas lı́neas no es un bucle interno. En concreto, el segundo bloque con
mayor número de instrucciones mostrado en la Tabla 8.1 (pág. 75) se corresponde con las lı́neas
siguientes:
950
...
955
d e v i a t o r i c _ s t r e s s _ t e n s o r (: ,:) = stress_tensor (: ,:)
d a m a g e d _ d e v _ s t r e s s _ t e n s o r (: ,:) = d e v i a t o r i c _ s t r e s s _ t e n s o r (: ,:) /
(1.0 _LONGreal - cr ac k _p ar am e te r * damage )
8.2. DIAGNÓSTICO SOFTWARE
...
960
961
962
77
e q u i v a l e n t _ s t r e s s = sqrt (1.5 _LONGreal * sum (( d a m a g e d _ d e v _ s t r e s s _ t e n s o r (: ,:)
- b a c k _ s t r e s s _ t e n s o r (: ,:) ) **2) )
yie ld_funct ion = e q u i v a l e n t _ s t r e s s - i s o t r o p i c _ h a r d e n i n g _ s t r e s s
- yield_stress
Para todas ellas el compilador indica lo siguiente:
fatigue.f90(950): (col.
but seems inefficient
fatigue.f90(955): (col.
but seems inefficient
fatigue.f90(955): (col.
but seems inefficient
fatigue.f90(960): (col.
but seems inefficient
fatigue.f90(960): (col.
but seems inefficient
7) remark: loop was not vectorized: vectorization possible
7) remark: loop was not vectorized: vectorization possible
7) remark: loop was not vectorized: vectorization possible
46) remark: loop was not vectorized: vectorization possible
46) remark: loop was not vectorized: vectorization possible
Hasta ahora hemos encontrado dos puntos de interés en este análisis. El primero fue en la
función generalized hookes donde habı́a un pequeño bucle y varios accesos a vectores y matrices
completamente desenrollados. Para el bucle, con el pragma vector always podrı́a ser suficiente. Sin
embargo, en el caso de los accesos a los vectores y matrices habrı́a que reescribirlos para convertirlos
en bucles. El segundo punto de interés estaba en la función perdida y sus accesos a vectores a
través del operador (:,:). En este caso, la solución propuesta consistirı́a en convertir todos accesos
en bucles y usar de nuevo el pragma vector always.
Induct
Induct es una aplicación con reparto bastante homogéneo de las instrucciones en punto flotante.
Consta de un 42,3 % de instrucciones vectoriales y un 48,66 % de instrucciones escalares. Estos
datos son chocantes si tenemos en cuenta que solo un 16 % de sus 246 bucles habı́a sido vectorizado.
Aparte, comparativamente respecto a la versión escalar de la aplicación, seguı́a a gas dyn en el
segmento gracias a la mejora tanto en instrucciones como en ciclos. Sin embargo, el 36 % de
ciclos de penalización de que constaba su simulación debido a las dependencias entre instrucciones
escalares no podı́a quedar sin investigar.
PC
Función
1
0x408799
2
0x407cc9
4
0x405f18
5
0x406b96
mqc m mp mutual
quad cir coil
mqc m mp mutual
quad cir coil
mqr m mp mutual
quad rec coil
mqr m mp mutual
quad rec coil
Instrucciones
%
Ciclos
%
ind
2.851.200.000
26,1
12.700.800.000
31,4
ind
2.851.200.000
26,1
12.700.814.660
31,4
ind
950.400.000
8,69
4.233.614.640
10,5
ind
950.400.000
8,69
3.952.800.000
9,8
Tabla 8.3: Bloques 1, 2, 4 y 5 de la lista de bloques básicos más ejecutados en Induct, Polyhedron
La Tabla 8.3 (pág. 77) muestra cuatro de los cinco bloques básicos más ejecutados. Las funciones asociadas se encuentran en el único fichero de la aplicación, llamado induct.f90. Vemos que
hay dos bloques en cada una de estas funciones con el mismo número de instrucciones ejecutados.
La cifra correspondiente a los ciclos no tiene por qué coincidir necesariamente, ya que existe el
78
CAPÍTULO 8. ESTUDIO EXPERIMENTAL
factor dependencias que ocasiona variaciones en la cuenta.
Al comprobar el código de la aplicación correspondiente a las funciones quad cir coil y
quad rec coil, vemos que prácticamente eran iguales salvo en alguna lı́nea aislada que no influı́a
en lo que buscábamos. El esquema general de ambas funciones consistı́a en 2 grupos de 3 bucles
encadenados cada una. Hacı́an un total de 4 grupos, cada uno de ellos asociado a uno de los cuatro
bloques de la Tabla 8.3 (pág. 77). A continuación se presenta el contenido de uno de los bucles
internos:
1772
1773
1774
1775
...
1779
1780
1781
...
1785
1786
1787
1788
1789
1790
1791
do k = 1 , 9
q_vector (1) = 0.5 _longreal * a * ( x2gauss ( k ) + 1.0 _longreal )
q_vector (2) = 0.25 _longreal * b2 * ( y2gauss ( k ) + 1.0 _longreal )
q_vector (3) = 0.0 _longreal
rot_q_vector (1) = dot_product ( rotate_quad (1 ,:) , q_vector (:) )
rot_q_vector (2) = dot_product ( rotate_quad (2 ,:) , q_vector (:) )
rot_q_vector (3) = dot_product ( rotate_quad (3 ,:) , q_vector (:) )
numerator = w1gauss ( j ) * w2gauss ( k ) *
dot_product ( coil_current_vec , curre nt_vect or )
denominator = sqrt ( dot_product ( rot_c_vector - rot_q_vector ,
rot_c_vector - rot_q_vector ) )
l12_upper = l12_upper + numerator / denominator
end do
El informe del compilador indica que se han podido vectorizar los bucles internos presentes en
cada uno de los cuatro bloques:
induct.f90(1772): (col. 15) remark: LOOP WAS VECTORIZED
induct.f90(1772): (col. 15) remark: remainder loop was not vectorized:
low trip count
induct.f90(1660): (col. 15) remark: LOOP WAS VECTORIZED
induct.f90(1660): (col. 15) remark: remainder loop was not vectorized:
low trip count
induct.f90(2077): (col. 15) remark: LOOP WAS VECTORIZED
induct.f90(2077): (col. 15) remark: remainder loop was not vectorized:
low trip count
induct.f90(2220): (col. 15) remark: LOOP WAS VECTORIZED
induct.f90(2220): (col. 15) remark: remainder loop was not vectorized:
low trip count
Los bloques denominados reminder son aquellos que se generan después del bucle principal en
caso de que el compilador, al vectorizar, no haya podido abarcar todos los elementos del vector. En
este caso el número de iteraciones que realiza, 9, no es lo suficientemente alto como para generar
un bloque reminder adicional. Veamos un pequeño fragmento del primero de los bloques básicos:
1
2
3
4
5
6
7
8
9
vaddsd xmm28 , k0 , xmm26 , xmm13
vaddsd xmm27 , k0 , xmm25 , xmm14
vmulpd zmm14 , k0 , zmm19 , qword ptr [ rax *8+0 x696340 ]{ b }
vaddsd xmm30 , k0 , xmm24 , xmm15
vbroadcastsd zmm31 , k0 , xmm28
vbroadcastsd zmm1 , k0 , xmm27
vbroadcastsd zmm15 , k0 , xmm30
vsubpd zmm0 , k0 , zmm31 , zmm22
vsubpd zmm29 , k0 , zmm1 , zmm21
8.2. DIAGNÓSTICO SOFTWARE
10
11
12
13
14
15
16
79
vmulpd zmm1 , k0 , zmm14 , zmm17
...
vmulsd xmm14 , k0 , xmm29 , xmm29
vfmadd213sd xmm13 , xmm13 , xmm14
vfmadd213sd xmm15 , xmm15 , xmm13
vsqrtsd xmm15 , xmm15 , xmm15
vdivsd xmm13 , xmm3 , xmm15
Está bien vectorizado y, como es lógico, necesita intercalar algunas instrucciones escalares a
las vectoriales para realizar operaciones intermedias. Esto significa que induct funciona bien y
esta haciendo un uso efectivo de la unidad vectorial. Sin embargo, esto no se traduce en un buen
IPC debido a las dependencias de tanto las instrucciones escalares como las vectoriales y, dado
que hay abundantes escalares en los bloques ya vectorizados, lo que parecı́a un buen intento de
mejorar la aplicación centrándose en estas instrucciones, no ha dado el resultado esperado. Una
posible solución que se deja planteada consiste en hacer uso de la directiva unroll para indicar al
compilador que desenrolle por 2. Si consigue hacer un reordenamiento adecuado, se reducirı́an las
dependencias y mejorarı́a el IPC.
Aermod
Aermod, como vimos en la caracterización, es una aplicación con una gran cantidad de bucles,
2597, y con un ı́ndice de vectorización muy pequeño, 5,59 %. Al analizar el listado de bloques
básicos con mayor número de instrucciones, vemos que éstos le confieren un perfil plano. Esto
significa que el reparto de instrucciones ejecutadas es muy homogéneo no destacando ningún
bloque por encima de los demás. Por este motivo, se tomó la decisión de visitar uno a uno todos
los bloques que proporcionaban información útil, clasificados por las funciones que los relacionaban
entre ellos. Véase a continuación el desglose.
PC
Función
Instrucciones
%
Ciclos
%
1
0x5bd009
powf.L
3.437.413.941
4,85
4.054.989.375
2,32
2
0x5bd0b0
powf.L
3.261.136.303
4,60
5.552.965.738
3,18
Tabla 8.4: Bloques 1 y 2 más ejecutados de Aermod, Polyhedron
La función powf.L que se muestra en la Tabla 8.4 (pág. 79), es en realidad una función de
librerı́a. Para este caso, se buscó la región desde donde se estaban realizando la mayor cantidad
de llamadas a la misma. Con una de las herramientas internas que permitı́a visualizar el control
de flujo de cada una de las funciones de la aplicación, era posible hacer una consulta sobre el
primero de los bloques de la función para conocer aquellos que contenı́an llamadas a la misma. El
resultado se muestra en la tabla siguiente:
PC
Función
Instrucción
Ejecuciones
%
0x5ba120
sigz
call 0x5ba120
25.157.031
28,5
Pese a que sea la función sigz la que se indica, después de analizar el código más a fondo
se llegó a una función llamada szsfcl, de la que se habı́a hecho inline en sigz. Se muestra
a continuación un fragmento de la misma. El operador ** que se visualiza en la función es la
potencia: x**n = xn . Las lı́neas no mostradas son comentarios y declaraciones de variables:
80
CAPÍTULO 8. ESTUDIO EXPERIMENTAL
45207 SUBROUTINE SZSFCL ( XARG )
...
45256
IF ( UNSTAB . AND . SURFAC ) THEN
45257
SZSURF = BSUBC *(1.0 -10.*( CENTER / ZI ) ) ** ALPHA1 *( USTAR / UEFFD )
45258
*( USTAR / UEFFD ) *( XARG * XARG / ABS ( OBULEN ) )
45259
45260
ELSEIF ( STABLE ) THEN
45261
45262
SZSURF = ( RTOF2 / RTOFPI ) * USTAR *( XARG / UEFF ) *(1.0+0.7* XARG / OBULEN )
45263
**( -0.333333)
45264
45265
ELSE
45266
SZSURF = 0.0
45267
45268
ENDIF
45269
45270
CONTINUE
45271 END
Esta función no tiene bucles que tratar, ni siquiera un acceso especial a algún vector o matriz.
Asimismo, la función desde donde se invoca, sigz, tampoco tiene. Si continuamos con este mecanismo de comprobar quién invoca a quién, encontramos llamadas a sigz desde funciones que no
tienen bucles. Un ejemplo es una función llamada adisz, desde donde se invocaba 26 millones de
veces. A ésta también se la llamaba desde una función llamada iblval un total de 18 millones de
veces. Ninguna de estas piezas de código contenı́a bucles, con lo que en principio parecen simples
bloques que, por el número de instrucciones que contienen y por las veces que se las invoca desde
diferentes funciones, tienen todas las papeletas para aparecer en el top de bloques básicos.
Volviendo a los 10 bloques más ejecutados, encontramos tres de ellos pertenecientes a la misma
función: anyavg. Véase la Tabla 8.5 (pág. 80).
PC
Función
Instrucciones
%
Ciclos
%
3
0x50db17
anyavg
2.256.034.787
3,18
8.790.756.239
5,03
5
0x50da34
anyavg
1.867.063.272
2,63
5.367.809.396
3.07
10
0x50d94c
anyavg
1.307.726.462
1,84
1.307.726.462
0,748
Tabla 8.5: Bloques 3, 5 y 10 más ejecutados de Aermod, Polyhedron
Estos bloques ni son bucles ni están contenidos dentro de un bucle mayor. Al realizar la búsqueda de los bloques desde donde se invoca a ésta función, encontramos que los más significativos se
encuentran contenidos en la función iblval. Dado que iblval se mencionó anteriormente porque
aparecı́a como función raı́z desde donde se acababa invocando a powf.L y que, dentro de los 10
bloques más ejecutados aparecen otros que también forman parte de ella, se analizarán para ver
si guardan alguna relación con anyavg o powf.L. Véanse en la Tabla 8.6 (pág. 80).
PC
Función
Instrucciones
%
Ciclos
%
7
0x50d260
iblval
1.397.144.910
1,97
2.395.105.560
1,37
9
0x50d2af
iblval
1.394.701.126
1,97
2.390.916.216
1,37
Tabla 8.6: Bloques 7 y 9 más ejecutados de Aermod, Polyhedron
Todos ellos son idénticos en contenido de instrucciones y, al comprobar las lı́neas de código a
las que pertenecen, se descubre que son de una función llamada Locate. Esto significa que, de
nuevo, se ha hecho inline de una función desde diferentes puntos. En este caso todos los puntos
forman parte de iblval. Véase a continuación un fragmento de la función:
8.2. DIAGNÓSTICO SOFTWARE
81
21698 SUBROUTINE LOCATE ( PARRAY , LVLBLW , LVLABV , VALUE , NDXBLW )
...
21729
IMPLICIT NONE
21730
21731
INTEGER LVLABV , LVLBLW , NDXBLW , JL , JM , JU
21732
REAL PARRAY ( LVLABV ) , VALUE
...
21740
JL = LVLBLW - 1
21741
JU = LVLABV + 1
21742
21743
DO WHILE ( ( JU - JL ) . GT .1 )
21744
21745
JM = ( JU + JL ) /2
21746
21747
IF ( VALUE . GE . PARRAY ( JM ) ) THEN
21748
JL = JM
21749
ELSE
21750
JU = JM
21751
ENDIF
21752
21753
ENDDO
21754
21755
NDXBLW = JL
21756
21757
CONTINUE
21758 END
Después de encontrar el primer bucle, comprobamos que no se ha vectorizado dando como
razón el compilador que no es una estructura soportada:
aermod.f90(21743): (col. 7) remark: loop was not vectorized: unsupported loop structure
En el bucle se está accediendo al vector parray(jm) dentro de una condición. El ı́ndice, jm,
se calcula en cada una de las iteraciones, convirtiendo la vectorización en una tarea complicada,
ya que habrı́a que calcular previamente todos los ı́ndices para poder acceder a varios elementos
simultáneamente. Esto se podrı́a traducir en una instrucción de tipo gather que cargara cada uno
de los valores del vector según los ı́ndices que se hubieran calculado previamente. Sin embargo, el
hecho de que dicho cálculo necesite del acceso al vector previamente, impide la vectorización.
Relacionando esto con los dos últimos bloques mostrados de la función iblval en la Tabla 8.6
(pág. 80), encontramos que las llamadas a la función Locate están ı́ntimamente relacionadas con
las llamadas a la función anyavg. En multitud de ocasiones aparecen bloques en donde, previo
a llamar a anyavg, se divisa el bloque correspondiente al inline de Locate. Esta estructura de
llamadas se repite 4 veces en todo el código, estando tres de ellas en el interior de la función
iblval. Las siguientes lı́neas muestran la disposición de las llamadas que tienen más peso de entre
todo el conjunto de invocaciones del código:
21285
21286
21287
21288
21289
21290
21291
CALL LOCATE ( GRIDHT ,1 , MXGLVL , ZHI , NDXBHI )
CALL LOCATE ( GRIDHT ,1 , MXGLVL , ZLO , NDXBLO )
NDXALO = NDXBLO + 1
CALL ANYAVG ( MXGLVL , GRIDHT , GRIDWS , ZLO , NDXALO , ZHI , NDXBHI , UEFF )
CALL ANYAVG ( MXGLVL , GRIDHT , GRIDSV , ZLO , NDXALO , ZHI , NDXBHI , SVEFF )
CALL ANYAVG ( MXGLVL , GRIDHT , GRIDSW , ZLO , NDXALO , ZHI , NDXBHI , SWEFF )
CALL ANYAVG ( MXGLVL , GRIDHT , GRIDTG , ZLO , NDXALO , ZHI , NDXBHI , TGEFF )
Viendo que se invoca en cuatro ocasiones a anyavg, se propone el siguiente planteamiento:
indicar al compilador que haga inline de las llamadas a anyavg con el pragma inline y comprobar
su comportamiento. Otra opción seria rescribir el código englobando las llamadas en un bucle.
Finalmente, se analizará el bloque mostrado en la Tabla 8.7 (pág. 82).
82
CAPÍTULO 8. ESTUDIO EXPERIMENTAL
8
PC
Función
Instrucciones
%
Ciclos
%
0x480379
sigz
1.395.399.168
1,97
2.946.185.353
1,69
Tabla 8.7: Bloque 8 más ejecutado de Aermod, Polyhedron
La función sigz ya se ha mencionado previamente porque se invocaba desde la función iblval.
Sin embargo, se descubrió que el bloque mostrado no se corresponde con esta función en concreto,
sino con Locate, la cual también se ha mencionado y de la que sabemos que se hace inline en
diferentes puntos del código. Dado que en todos los casos iblval resulta ser la raı́z común de
todo el análisis, podrı́amos seguir analizando los niveles superiores en la cadena de llamadas, para
averiguar qué funciones la invocan y cómo se comportan. Dado que es algo que venı́amos haciendo
desde el inicio, el panorama se presenta negativo porque de entre las funciones que la invocan
ninguna contenı́a bucles que justifiquen tantas llamadas. Veamos a continuación algunas de las
funciones donde se invoca iblval :
PC
Función
Instrucción
Ejecuciones
%
0x47bf6f
pwidth
call 0x50b890
9.248.524
46,1
0x47d009
plumef
call 0x50b890
8.797.813
43,9
0x4a948f
aercalc
call 0x50b890
1.010.878
5,04
0x4a0bcf
volcalc
call 0x50b890
986.280
4,92
Al llegar a este punto se cesó el análisis, ya que se convirtió más en una cruzada de buscar
posibles bucles de interés en un código de 51.885 lı́neas, que en un estudio de cómo mejorar la
aplicación para hacer un uso más efectivo de la unidad vectorial. Igualmente, llama la atención
que siendo una aplicación con más de 2.000 bucles, se den circunstancias como que los bloques
de mayor peso en la aplicación no contuvieran más que un bucle o ninguno. Si bien la mayorı́a
de bucles que contenı́a resultaron ser pequeños y vectorizables, muchos de ellos no se llegaban ni
siquiera a ejecutar. Por ende, aermod es una aplicación que pese a haber sido candidata, no se ha
obtenido el análisis que se esperaba de ella.
Gas dyn
Gas dyn es una aplicación que tenı́a buen ı́ndice de vectorización, 39,04 %, y la versión vectorial
frente a la escalar daba los mejores resultados de todas las aplicaciones simuladas. Sin embargo el
IPC era únicamente 0,19. Por este motivo se tomó la decisión de proceder a su análisis.
Instrucciones
Enteras
Escalares
Vectoriales
Total
Versión Escalar
%
Versión Vectorial
%
7.815.080.079
23.91
1.329.097.003
38,71
23.909.735.894
23,89
764.150.080
22,26
1.051.987.585
3,2
1.340.360.535
39,04
32.776.803.558
3.433.607.618
Tabla 8.8: Desglose de instrucciones de las versiones escalar y vectorial de Gas dyn, Polyhedron
El desglose de instrucciones de las versiones vectorial y escalar se muestra en la Tabla 8.8
(pág. 82). Nótese que la versión escalar contiene 1 millón de instrucciones vectoriales. Tras realizar
comprobaciones varias con el equipo del compilador, no se llego a una conclusión factible del
8.2. DIAGNÓSTICO SOFTWARE
83
comportamiento del compilador. Dejándose este detalle a un lado, si dividimos los 24 millones de
instrucciones escalares de la versión escalar entre 16, para intentar realizar una aproximación a
mano alzada de la reducción, obtenemos 1,5M de instrucciones vectoriales. La versión vectorial
contenı́a 1,3M de instrucciones vectoriales. El objetivo no era realizar ninguna validación, sino ver
como, al utilizar registros vectoriales de 512 bits que pueden contener hasta 16 datos en coma
flotante de 4 bytes, se ven reducidas las instrucciones escalares y aumentadas las vectoriales.
1
3
PC
Función
Instrucciones
%
Ciclos
%
0x409375
0x409060
eos
eos
718.555.926
234.311.715
20,9
6,82
3.435.096.196
4.271.972.900
22,4
27,9
Tabla 8.9: Bloques 1 y 3 más ejecutados de Gas dyn, Polyhedron
En la Tabla 8.9 (pág. 83) se muestran dos de los bloques más ejecutados de la aplicación,
formando ambos parte de la función eos. El primer bloque se corresponde con la lı́nea 410 del
siguiente código y el tercer bloque con las lı́neas 407 a 409.
360
361
...
390
391
392
...
406
407
408
409
410
411
...
432
SUBROUTINE EOS ( NODES , IENER , DENS , PRES , TEMP , GAMMA , CS , SHEAT ,
&
CGAMMA , WT )
&
INTEGER NODES , I
REAL SHEAT , CGAMMA , WT
REAL , DIMENSION ( NODES ) :: IENER , DENS , PRES , TEMP , GAMMA , CS
IF ( SHEAT >0.0 . AND . CGAMMA >0.0) THEN
TEMP (: NODES ) = IENER (: NODES ) / SHEAT
PRES (: NODES ) = ( CGAMMA - 1.0) * DENS (: NODES ) * IENER (: NODES )
GAMMA (: NODES ) = CGAMMA
CS (: NODES ) = SQRT ( CGAMMA * PRES (: NODES ) / DENS (: NODES ) )
ELSE
ENDIF
Pese a que no veamos aparentemente un bucle, la utilización de :NODES para acceder a un vector
indica implı́citamente que se va a realizar la operación a todos los elementos del vector, entendiéndolo el compilador como si fueran varios bucles uno a continuación del otro. Esta caracterı́stica
R Cilk TM Plus y se utiliza tanto en el compilador ICC como IFORT[inte][intd].
se denomina Intel
Al comprobar la compilación de esta porción de código, se aprecia que crean bloques prólogo,
normal y epı́logo para las lı́neas 407 a 409 por un lado, y para la lı́nea 410 por otro. Para las lı́neas
407 a 409 creó un fused loop, esto es, fusionó el acceso a los vectores TEMP, PRES y GAMMA en un
mismo bloque. Las lı́neas del informe del compilador son las siguientes:
gas
gas
gas
gas
dyn.f90(410):
dyn.f90(410):
dyn.f90(410):
dyn.f90(410):
(col.
(col.
(col.
(col.
11)
11)
11)
11)
remark:
remark:
remark:
remark:
unroll factor set to 2
LOOP WAS VECTORIZED
PEEL LOOP WAS VECTORIZED
REMAINDER LOOP WAS VECTORIZED
gas dyn.f90(407): (col. 11) remark: FUSED LOOP WAS VECTORIZED
gas dyn.f90(407): (col. 11) remark: PEEL LOOP WAS VECTORIZED
gas dyn.f90(407): (col. 11) remark: REMAINDER LOOP WAS VECTORIZED
El hecho de que el compilador este generando código separado para los tres primeros vectores
por un lado y para el último por otro, es una de las raı́ces del problema que estábamos viendo
en la Sección 8.1.1 (pág. 65), sobre la gran cantidad de ciclos ocasionados por los accesos a la
UL2. En la lı́nea 410 de la función se está accediendo a los vectores PRES y DENS después de
haber sido accedidos en la lı́nea 408. Como la 410 tiene que trabajar con ellos, se traduce en
que la vectorización no hace un buen aprovechamiento de la localidad temporal de la cache, en
84
CAPÍTULO 8. ESTUDIO EXPERIMENTAL
detrimento del rendimiento global de la aplicación. La solución, que en este caso sı́ se implemento
debido a su sencillez, consistió en rescribir el código transformando los accesos a los bucles usando
:nodes por un bucle simple 1..n, manteniendo el desenrollamiento en factor 2 que generaba el
compilador. Véase a continuación el código resultante y consúltense la directivas de Fortran en la
Sección 4.5 (pág. 29).
411 ! DIR$ UNROLL (2)
412
DO 10 I = 1 , NODES
413
TEMP ( I ) = IENER ( I ) / SHEAT
414
PRES ( I ) = ( CGAMMA - 1.0) * DENS ( I ) * IENER ( I )
415
GAMMA ( I ) = CGAMMA
416
CS ( I ) = SQRT ( CGAMMA * PRES ( I ) / DENS ( I ) )
417
10 CONTINUE
Al convertir el acceso a los vectores en un bucle explı́cito que itera uno a uno sobre todos los
elementos, el compilador no generará dos secciones separadas para el bucle. Se llevó a la práctica
y se obtuvieron los siguientes resultados de la Figura 8.11 (pág. 84) y el mensaje del compilador
siguiente:
gas
gas
gas
gas
dyn.f90(412):
dyn.f90(412):
dyn.f90(412):
dyn.f90(412):
(col.
(col.
(col.
(col.
14)
14)
14)
14)
remark:
remark:
remark:
remark:
unroll factor set to 2
PARTIAL LOOP WAS VECTORIZED
PEEL LOOP WAS VECTORIZED
REMAINDER LOOP WAS VECTORIZED
Figura 8.11: Comparación entre las versiones :nodes y do de gas dyn
En primer lugar se aprecia que la lı́nea del código a la que hace referencia es únicamente la 412
de inicio del bucle, aparte de que genera solamente tres bloques. En la Figura 8.11 (pág. 84) se
presenta una reducción del 6,51 % en el total de ciclos. Esta reducción es debido a la disminución
de accesos en la UL2 en un 16,09 %. Ahora sı́ se está realizando un aprovechamiento de la localidad
temporal, mejorando por tanto el rendimiento de la aplicación.
8.2.2.
Mantevo
La aplicación del benchmark Mantevo seleccionada para un análisis en profundidad es CoMD.
8.2. DIAGNÓSTICO SOFTWARE
85
CoMD
La caracterización y los resultados obtenidos por el simulador CMP$im modificado, muestran
a CoMD como una aplicación complicada de abordar. Su 0,22 % de instrucciones vectoriales y su
30,43 % de instrucciones escalares da poco margen para realizar cambios. Aparte, el resultado de
la comparación entre las versiones escalares y vectoriales mostraban que vectorizar la aplicación no
servı́a sino para empeorar el rendimiento de la misma tanto en número de instrucciones ejecutadas
como en ciclos consumidos. Estudiando los bloques con más peso en el programa, encontramos
que la sección de código fundamental en donde se está realizando la mayor parte del cómputo son
los siguientes bucles encadenados:
188 for ( ioff = ibox * MAXATOMS , ii =0; ii < nIBox ; ii ++ , ioff ++) { /* loop over atoms in ←ibox */
189
int joff ;
191
s - > stress -= s - > p [ ioff ][0]* s - > p [ ioff ][0]/ s - > mass [ ioff ];
192
int i = s - > id [ ioff ]; /* the ij - th atom in ibox */
193
for ( joff = MAXATOMS * jbox , ij =0; ij < nJBox ; ij ++ , joff ++) { /* loop over atoms ←in ibox */
194
int m ;
195
real_t dr [3];
196
int j = s - > id [ joff ]; /* the ij - th atom in ibox */
197
if ( j <= i ) continue ;
198
r2 = 0.0;
199
for ( m =0; m <3; m ++) {
200
dr [ m ] = drbox [ m ]+ s - > r [ ioff ][ m ] -s - > r [ joff ][ m ];
201
r2 += dr [ m ]* dr [ m ];
202
}
203
204
if ( r2 > r2cut ) continue ;
...
212
r2 =( real_t ) 1.0/ r2 ;
214
r6 = ( r2 * r2 * r2 ) ;
216
s - > f [ ioff ][3] += 0.5* r6 *( s6 * r6 - 1.0) ;
217
s - > f [ joff ][3] += 0.5* r6 *( s6 * r6 - 1.0) ;
218
etot += r6 *( s6 * r6 - 1.0) - eshift ;
...
221
fr = - 4.0* epsilon * s6 * r6 * r2 *(12.0* s6 * r6 - 6.0) ;
222
for ( m =0; m <3; m ++) {
223
s - > f [ ioff ][ m ] += dr [ m ]* fr ;
224
s - > f [ joff ][ m ] -= dr [ m ]* fr ;
225
}
226
s - > stress += 1.0* fr * dr [0]* dr [0];
...
232
} /* loop over atoms in jbox */
233 } /* loop over atoms in ibox */
Estos bucles se encargan de computar la interacción entre los átomos dentro de contenedores
denominados boxes. El recorrido sobre todos los boxes se realiza con otros dos bucles encadenados
que no se visualizan aquı́ pero que engloban a estos, haciendo un total de 4 bucles. El contenido
completo de esta sección de código se puede consultar en el fichero ljForce.c de la aplicación.
Los diversos bloques generados por el compilador presentan en su mayorı́a instrucciones enteras
intercaladas con escalares, como por ejemplo el siguiente:
1
2
3
4
5
6
7
8
9
10
mov rbx , qword ptr [ rbp +0 x58 ]
vaddss xmm8 , xmm3 , dword ptr [ rbx + r8 *1+0 x4 ]
vaddss xmm1 , xmm4 , dword ptr [ rbx + r8 *1]
vaddss xmm9 , xmm2 , dword ptr [ rbx + r8 *1+0 x8 ]
lea r10 , ptr [ rdx + rbx *1]
vsubss xmm8 , xmm8 , dword ptr [ r10 + r14 *1+0 x4 ]
vsubss xmm1 , xmm1 , dword ptr [ r10 + r14 *1]
vsubss xmm9 , xmm9 , dword ptr [ r10 + r14 *1+0 x8 ]
vmulss xmm11 , xmm8 , xmm8
vfmadd231ss xmm11 , xmm1 , xmm1
86
11
12
13
14
CAPÍTULO 8. ESTUDIO EXPERIMENTAL
vfmadd231ss xmm11 , xmm9 , xmm9
vcmpss k0 , k0 , xmm11 , xmm17 , 0 xe
kortestw k0 , k0
jnz 0 x4049ab
El problema que encuentra el compilador para vectorizar el código, son las dependencias entre
iteraciones:
ljForce.c(193): (col. 3) remark: loop was not vectorized: existence of vector
dependence
Estas dependencias se producen porque los boxes que se tratan podrı́an coincidir, es decir,
cuando ibox == jbox. En este caso las variables ioff y joff se iniciarı́an con el mismo valor y el
compilador lo está detectando como una dependencia. Una idea para solucionarlo radicarı́a en
rescribir el código generando dos situaciones separadas: una en la que ibox fuera igual a jbox,
en cuyo caso seguirı́a sin vectorizar porque se seguirı́an produciendo las mismas dependencias,
y un else donde incluyéramos los pragmas ivdep y vector always, ya que sabrı́amos que no se
producirı́an dependencias y es seguro usarlas. El esquema general serı́a el siguiente:
if ( ibox == jbox )
for ( ioff = ibox * MAXATOMS , ii =0; ii < nIBox ; ii ++ , ioff ++) {
/* loop over atoms in ibox */
...
for ( joff = MAXATOMS * ibox , ij =0; ij < nIBox ; ij ++ , joff ++) {
/* loop over atoms in ibox */
...
else
# pragma ivdep
# pragma vector always
for ( ioff = ibox * MAXATOMS , ii =0; ii < nIBox ; ii ++ , ioff ++) {
/* loop over atoms in ibox */
...
for ( joff = MAXATOMS * jbox , ij =0; ij < nJBox ; ij ++ , joff ++) {
/* loop over atoms in ibox */
...
A la hora de aplicar la solución, es necesario tener en cuenta que la aplicación no ofrece muchas
más oportunidades para vectorizar. Cuando tratamos con aplicaciones como esta en la que más
de la mitad del código está formado por instrucciones enteras, 69,35 %, las versiones escalares y
vectoriales serán muy semejantes y el uso de la unidad vectorial, pese a que puede mejorar, no lo
hará mucho.
8.2.3.
Sequoia
Las aplicaciones del benchmark Sequoia seleccionadas para un análisis en profundidad son:
Crystalmk y SPhotmk.
Crystalmk
La caracterización mostraba que el 46,7 % de las escalares, frente al 5,8 % de las vectoriales, era
un porcentaje interesante sobre el que centrarse de cara a su reducción. Además, en la Sección 8.1.3
(pág. 70) se vio que la versión vectorial respecto a la escalar no habı́a obtenido buenos resultados, ya
que la relación de instrucciones y ciclos era de 1,13 y 1,15 % respectivamente. Por tanto, después
de analizar el conjunto de bloques básicos más ejecutados, la principal diferencia entre ambas
8.2. DIAGNÓSTICO SOFTWARE
87
versiones radica en el procedimiento Crystal div del fichero Crystal div.c. Véase a continuación
los bucles de las lı́neas 43, 49, 53, 61 y 65 del código de la función:
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
void Crystal_div ( int nSlip ,
double deltaTime ,
double slipRate [ M S _ X T A L _ N S L I P _ M A X ] ,
double dSlipRate [ M S _ X T A L _ N S L I P _ M A X ] ,
double tau [ M S _ X T A L _ N S L I P _ M A X ] ,
double tauc [ M S _ X T A L _ N S L I P _ M A X ] ,
double rhs [ M S _ X T A L _ N S L I P _ M A X ] ,
double dtcdgd [ M S _ X T A L _ N S L I P _ M A X ][ M S _ X T A L _ N S L I P _ M A X ] ,
double dtdg [ M S _ X T A L _ N S L I P _ M A X ][ M S _ X T A L _ N S L I P _ M A X ] ,
double matrix [ M S _ X T A L _ N S L I P _ M A X ][ M S _ X T A L _ N S L I P _ M A X ])
{
double
double
double
double
double
bor_array [ M S _ X T A L _ N S L I P _ M A X ];
sgn [ M S _ X T A L _ N S L I P _ M A X ];
rateFact [ M S _ X T A L _ N S L I P _ M A X ];
tauN [ M S _ X T A L _ N S L I P _ M A X ];
err [ M S _ X T A L _ N S L I P _ M A X ];
double
double
double
double
double
rate_offset
tauA
tauH
rate_exp
bor_s_tmp
=
=
=
=
=
1. e -6;
30.;
1.2;
0.01;
0.0;
int n = 0;
int m = 0;
for ( n = 0; n < nSlip ; n ++) {
sgn [ n ] = 1.0;
rateFact [ n ] = 0.9 + (0.2 * n ) / M S _ X T A L _ N S L I P _ M A X ;
}
// ---- M S _ X t a l _ P o w e r T a y
for ( n = 0; n < nSlip ; n ++) {
bor_array [ n ] = 1 / ( slipRate [ n ]* sgn [ n ] + rate_offset ) ;
}
for ( n = 0; n < nSlip ; n ++) {
tau [ n ] = tauA * rateFact [ n ] * sgn [ n ];
for ( m = 0; m < nSlip ; m ++)
dtcdgd [ n ][ m ] = tauH * deltaTime * rateFact [ n ];
dtcdgd [ n ][ n ] += tau [ n ] * rate_exp * sgn [ n ] * bor_array [ n ];
}
// ----- M S _ X t a l _ S l i p R a t e C a l c
for ( n = 0; n < nSlip ; n ++) {
bor_array [ n ] = 1/ dtcdgd [ n ][ n ];
}
for ( n = 0; n < nSlip ; n ++) {
tauN [ n ] = tau [ n ];
for ( m = 0; m < nSlip ; m ++) {
bor_s_tmp = dtdg [ n ][ m ]* deltaTime ;
tauN [ n ] += bor_s_tmp * dSlipRate [ m ] ;
matrix [ n ][ m ] = ( - bor_s_tmp + dtcdgd [ n ][ m ]) * bor_array [ n ];
}
err [ n ] = tauN [ n ] - tauc [ n ];
rhs [ n ] = err [ n ] * bor_array [ n ];
}
}
En la versión escalar, cuando el compilador genera el código para este procedimiento, identifica
que todos los bucles iteran con la misma variable y por tanto condensa todas las operaciones en
88
CAPÍTULO 8. ESTUDIO EXPERIMENTAL
el mismo bucle. Ası́ se consigue eliminar código de control que hubiese estado repetido en cada
uno de los bucles. El bloque generado lo denomina fused loop y consta de 1.173 instrucciones. En
la versión vectorial una parte de este bucle es vectorizada mientras que otra no, ocasionando que
las instrucciones del bloque escalar ahora se repartan en tres bloques diferentes. El bloque más
grande de los tres se corresponde con los bucles de las lı́neas 61 y 65.
En el Capı́tulo 6 (pág. 39) comprobamos lo habitual de la situación en la que el compilador
intenta vectorizar el bucle interno. Sabemos que si lo consigue indicará que el bucle externo no se
ha vectorizado por no ser un bucle interno, not inner loop. En el caso del bucle de la lı́nea 65, que
contiene un bucle interno que empieza en la lı́nea 67, ocurrı́a algo distinto. El compilador estaba
intentando vectorizar desde el bucle externo:
Crystal div.c(65): (col. 4) remark: loop was not vectorized: existence of vector
dependence
Esta dependencia era de esperar porque en el bucle interno se realizan tantos cálculos sobre
el elemento n del vector tauN como indica la variable nSlip, antes de volver a ser utilizado en el
bucle externo. La solución consistirı́a en indicar al compilador que vectorice el bucle interno con
el pragma vector always, puesto que dentro del bucle interno no hay dependencias.
SPhotmk
El ı́ndice de vectorización de SPhotmk era bajo, 5,56 %, frente a un 50,5 % de instrucciones
escalares. La comparativa entre las versiones escalar y vectorial mostraba que no se obtenı́a ni
reducción de instrucciones ni de ciclos. En la Tabla 8.10 (pág. 88) figuran los bloques con mayor
peso de cómputo. Entre ellos encontramos los de la librerı́a logaritmo pertenecientes a la función
log.L. Cuando parte de los bloques más ejecutados provienen de librerı́as como ésta, un modo de
intentar buscar una solución para vectorizar consistirı́a en conseguir que el compilador invoque la
función de la librerı́a adaptada al cálculo sobre vectores, siempre y cuando estuviera disponible.
1
2
3
4
PC
Función
Instrucciones
%
Ciclos
%
0x46dda3
0x405670
0x40581b
0x46dd20
log.L
execute
execute
log.L
7.772.068
5.913.530
5.406.656
4.054.992
7,23
5,50
5,03
3,77
12.145.985
8.278.942
11.602.509
5.575.614
4,39
2,99
4,19
2,02
Tabla 8.10: Bloques 1, 2, 3 y 4 más ejecutados de SPhotmk, Sequoia
Los bloques básicos situados en las posiciones 2 y 3 se corresponden con las funciones ranfmodmult
del fichero random.f y execute del fichero execute.f respectivamente. Para ambos ficheros se presentan dos pequeños extracto a continuación:
random . f
338
...
352
...
361
362
363
364
365
366
367
subroutine ranfmodmult ( A , B , C )
dimension A ( IRandNumSize ) , B ( IRandNumSize ) , C ( IRandNumSize )
a1
a2
a3
b1
b2
b3
j1
=
=
=
=
=
=
=
A (1)
A (2)
A (3)
B (1)
B (2)
B (3)
a1 * b1
8.2. DIAGNÓSTICO SOFTWARE
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
89
j2 = a1 * b2 + a2 * b1
j3 = a1 * b3 + a2 * b2 + a3 * b1
j4 = a1 * B ( 4 ) + a2 * b3 + a3 * b2 + A ( 4 ) * b1
k1
k2
k3
k4
=
=
=
=
j1
j2 + k1 / 4096
j3 + k2 / 4096
j4 + k3 / 4096
C(
C(
C(
C(
1
2
3
4
)
)
)
)
=
=
=
=
mod (
mod (
mod (
mod (
k1 ,
k2 ,
k3 ,
k4 ,
4096
4096
4096
4096
)
)
)
)
return
end
execute . f
372
373
dist = - log ( ranf ( mySeed ) ) / sig ( ir , ig )
dcen = ( tcen - age ) * 2.99793 d10
! distance to collision
! distance to census
El fichero principal es excecute.f. Este contiene una serie de bucles encadenados que abarcan la
mayor parte de la aplicación. Un código de estas caracterı́sticas dificulta la vectorización porque
para todos los bucles el compilador indica que no se ha vectorizado ninguno por no ser bucles
internos. Cuando al final llega al bucle más interno, resulta que no lo vectoriza debido a la existencia de dependencias. Éstas fueron muy difı́ciles de identificar:
execute.f(271):
dependence
execute.f(268):
execute.f(193):
execute.f(154):
execute.f(103):
(col. 19) remark: loop was not vectorized: existence of vector
(col.
(col.
(col.
(col.
16)
16)
13)
10)
remark:
remark:
remark:
remark:
loop
loop
loop
loop
was
was
was
was
not
not
not
not
vectorized:
vectorized:
vectorized:
vectorized:
not
not
not
not
inner
inner
inner
inner
loop
loop
loop
loop
Las dos lı́neas de código presentadas de excecute.f se encuentran en el interior del bucle más
externo de dos bucles anidados. Contienen las funciones log y ranf. La función ranf es la encargada de invocar a la función ranfmodmult que mencionamos previamente. Por tanto, estas dos
lı́neas son responsables de que los bloques más ejecutados sean los mostrados en el top de bloques básicos. La búsqueda de soluciones se dificulta cuando todos los datos tratados en la función
ranfmodmult resultan ser enteros. Esto viene a decir que uno de los bloques con mayor número
de instrucciones ejecutadas en el programa, participa dentro del 44 % de instrucciones enteras
presentes en la aplicación. Por estos motivos, para mejorarla no basta simplemente con modificar
ligeramente el código o incluir pragmas en los bucles más externos. En este caso se propone hacer
un estudio más exhaustivo del código y proceder a reescribirlo en la mayor medida posible.
8.2.4.
NPB
Las aplicaciones del benchmark NPB seleccionadas para un análisis en profundidad son: BT
y UT.
BT
La aplicación BT fue seleccionada por su elevado porcentaje de instrucciones escalares, 71,85 %.
En la Tabla 8.11 (pág. 90) se encuentra el primer y único bloque básico de los bloques más
90
CAPÍTULO 8. ESTUDIO EXPERIMENTAL
ejecutados que se va a analizar. Es un bloque importante porque supone un 56.2 % del montante
total de instrucciones de la aplicación.
1
PC
Función
Instrucciones
%
Ciclos
%
0x4060c0
binvcrhs
4.134.959.136
56,2
14.866.089.846
26,5
Tabla 8.11: Bloque 1 de los más ejecutados de BT, NPB
El código generado por el compilador está formado por 6 instrucciones enteras, 596 escalares y
14 vectoriales, de las cuales solo 3 utilizan vectores de 512 bits. Su código pertenece enteramente a
una función denominada binvcrhs que se encuentra en el fichero solve subs.f. Este fichero recoge
todas las funciones de interés que se invocan desde el código contenido en los ficheros x solve vec.f,
y solve vec.f y z solve vec.f para la versión vectorizada, y desde x solve.f, y solve.f y z solve.f para la
no vectorizada. La peculiaridad de estos ficheros es que en las versiones vectorizadas los bucles que
contienen las llamadas a binvcrhs traen ya el pragma ivdep incorporado. Véase en el fragmento
de código siguiente:
344 ! dir$ ivdep
345
do i = 1 , grid_points (1) -2
346
call binvcrhs ( lhs (1 ,1 , bb ,i ,0) ,
347
>
lhs (1 ,1 , cc ,i ,0) ,
348
>
rhs (1 ,i ,j ,0) )
349
enddo
Sin embargo, no se obtiene el resultado esperado por parte del compilador:
x solve vec.f(347): (col. 18) remark: vectorization support: call to function
binvcrhs cannot be vectorized
x solve vec.f(346): (col. 10) remark: loop was not vectorized: existence of vector
dependence
Analizando el código de la función vemos que no tiene ningún bucle sobre el que trabajar. El
único bucle es el que aparece en el fragmento de código anterior desde el que se realiza la llamada.
Véase la siguiente porción de la función binvcrhs:
206
...
218
219
...
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
subroutine binvcrhs ( lhs ,c , r )
dimension lhs (5 ,5)
double precision c (5 ,5) , r (5)
pivot = 1.00 d0 / lhs (1 ,1)
lhs (1 ,2) = lhs (1 ,2) * pivot
lhs (1 ,3) = lhs (1 ,3) * pivot
lhs (1 ,4) = lhs (1 ,4) * pivot
lhs (1 ,5) = lhs (1 ,5) * pivot
c (1 ,1) = c (1 ,1) * pivot
c (1 ,2) = c (1 ,2) * pivot
c (1 ,3) = c (1 ,3) * pivot
c (1 ,4) = c (1 ,4) * pivot
c (1 ,5) = c (1 ,5) * pivot
r (1)
= r (1) * pivot
coeff = lhs (2 ,1)
lhs (2 ,2) = lhs (2 ,2) - coeff * lhs (1 ,2)
lhs (2 ,3) = lhs (2 ,3) - coeff * lhs (1 ,3)
lhs (2 ,4) = lhs (2 ,4) - coeff * lhs (1 ,4)
lhs (2 ,5) = lhs (2 ,5) - coeff * lhs (1 ,5)
c (2 ,1) = c (2 ,1) - coeff * c (1 ,1)
c (2 ,2) = c (2 ,2) - coeff * c (1 ,2)
8.2. DIAGNÓSTICO SOFTWARE
244
245
246
247
c (2 ,3)
c (2 ,4)
c (2 ,5)
r (2)
=
=
=
=
c (2 ,3)
c (2 ,4)
c (2 ,5)
r (2)
-
91
coeff * c (1 ,3)
coeff * c (1 ,4)
coeff * c (1 ,5)
coeff * r (1)
Los diversos accesos a los vectores que se muestran representan bucles que se han desenrollado
manualmente y de forma completa. La idea que se propone es generar bucles en todas aquellas
secciones de código de la función binvcrhs donde sea posible. De este modo el compilador puede
intentar vectorizar dichos bucles y, a partir de ese punto, se puede afrontar el análisis de acuerdo
a las dificultades que haya encontrado el compilador para no vectorizar.
LU
La aplicación LU, al igual que BT, fue seleccionada por su porcentaje de instrucciones escalares,
50,4 %. En la Tabla 8.12 (pág. 91) se muestran únicamente los dos primeros bloques del conjunto
de bloques básicos más ejecutados, porque destacan por encima de los demás al contener un 23 %
y 21 % del total de instrucciones ejecutadas en el programa, respectivamente.
1
2
PC
Función
Instrucciones
%
Ciclos
%
0x41751b
0x41c8c8
buts
blts
2.968.107.121
2.717.028.573
23,2
21,2
13.636.253.898
10.258.244.038
11,0
8,30
Tabla 8.12: Bloques 1 y 2 de los más ejecutados de LU, NPB
El primero contiene parte del código de la función buts del fichero buts vec.f y el segundo
bloque parte de blts del fichero blts vec.f. Ambas funciones tienen la misma estructura, pero
cada una se encarga de trabajar con una de las dos matrices U y L, respectivamente. El código
se compone de dos secciones diferenciadas: una primera con varios bucles encadenados que el
compilador consigue vectorizar, y una segunda que está desenrollada manualmente en un factor
de 5. Esta segunda situación es semejante a la que vimos en BT. A continuación se muestra un
pequeño fragmento esto último:
77 ! ! dir$ unroll 5
78 !
manually unroll the loop
79 !
do m = 1 , 5
80
tv ( 1 , i , j
81
> + omega * ( udy ( 1 , 1 ,
82
>
+ udx ( 1 , 1 ,
83
>
+ udy ( 1 , 2 ,
84
>
+ udx ( 1 , 2 ,
85
>
+ udy ( 1 , 3 ,
86
>
+ udx ( 1 , 3 ,
87
>
+ udy ( 1 , 4 ,
88
>
+ udx ( 1 , 4 ,
89
>
+ udy ( 1 , 5 ,
90
>
+ udx ( 1 , 5 ,
91
tv ( 2 , i , j
92
> + omega * ( udy ( 2 , 1 ,
93
>
+ udx ( 2 , 1 ,
94
>
+ udy ( 2 , 2 ,
95
>
+ udx ( 2 , 2 ,
96
>
+ udy ( 2 , 3 ,
97
>
+ udx ( 2 , 3 ,
98
>
+ udy ( 2 , 4 ,
99
>
+ udx ( 2 , 4 ,
...
135 !
end do
) = tv ( 1 , i , j )
i , j ) * v ( 1 , i , j +1 ,
i , j ) * v ( 1 , i +1 , j ,
i , j ) * v ( 2 , i , j +1 ,
i , j ) * v ( 2 , i +1 , j ,
i , j ) * v ( 3 , i , j +1 ,
i , j ) * v ( 3 , i +1 , j ,
i , j ) * v ( 4 , i , j +1 ,
i , j ) * v ( 4 , i +1 , j ,
i , j ) * v ( 5 , i , j +1 ,
i , j ) * v ( 5 , i +1 , j ,
) = tv ( 2 , i , j )
i , j ) * v ( 1 , i , j +1 ,
i , j ) * v ( 1 , i +1 , j ,
i , j ) * v ( 2 , i , j +1 ,
i , j ) * v ( 2 , i +1 , j ,
i , j ) * v ( 3 , i , j +1 ,
i , j ) * v ( 3 , i +1 , j ,
i , j ) * v ( 4 , i , j +1 ,
i , j ) * v ( 4 , i +1 , j ,
k
k
k
k
k
k
k
k
k
k
)
)
)
)
)
)
)
)
)
) )
k
k
k
k
k
k
k
k
)
)
)
)
)
)
)
)
92
CAPÍTULO 8. ESTUDIO EXPERIMENTAL
El compilador indica que parte de este código es vectorizado como bloques en vez de como bucle.
buts vec.f(80): (col. 19) remark: BLOCK WAS VECTORIZED
buts vec.f(312): (col. 13) remark: BLOCK WAS VECTORIZED
blts vec.f(83): (col. 19) remark: BLOCK WAS VECTORIZED
Del resto de partes del código que también se encuentran desenrolladas no muestra ningún
mensaje de por qué no ha vectorizado. Por tanto, la idea que se plantea consiste en aplicar la
misma solución que para BT, para comprobar si transformando el código desenrollado en un bucle
y utilizando el pragma vector always, o el pragma ivdep, se consigue vectorizar.
8.2.5.
SPEC fp
Las aplicaciones del benchmark SPEC fp seleccionadas para un análisis en profundidad son:
Namd y Povray.
Namd
La aplicación Namd se caracterizaba por tener unicamente un 1,02 % de instrucciones vectoriales. Además, su 58 % de instrucciones escalares generaba una gran cantidad de dependencias
que reducı́an el IPC de la aplicación. En concreto, estas dependencias ocasionaban que un 48,33 %
de los ciclos de la aplicación fueran por este motivo. En el código fuente de Namd existe un fichero
denominado ComputeNonbondedBase2.h, sobre el que bascula gran parte del código generado. A
continuación se muestra un pequeño fragmento inicial de dicho código:
7 EXCLUDED ( FAST ( foo bar ) )
8 EXCLUDED ( MODIFIED ( foo bar ) )
9 EXCLUDED ( NORMAL ( foo bar ) )
10 NORMAL ( MODIFIED ( foo bar ) )
11
12 for ( k =0; k < npairi ; ++ k ) {
13
14
const int j = pairlisti [ k ];
15
register const CompAtom * p_j = p_1 + j ;
16
17
register const BigReal p_ij_x = p_i_x - p_j - > position . x ;
18
register BigReal r2 = p_ij_x * p_ij_x ;
19
register const BigReal p_ij_y = p_i_y - p_j - > position . y ;
20
r2 += p_ij_y * p_ij_y ;
21
register const BigReal p_ij_z = p_i_z - p_j - > position . z ;
22
r2 += p_ij_z * p_ij_z ;
23
...
28 FAST (
29
const LJTable :: TableEntry * lj_pars =
30
lj_row + 2 * mol - > atomvdwtype ( p_j - > id ) MODIFIED (+ 1) ;
...
35 SHORT (
36
const BigReal * const fast_i = table_four + 16* table_i + 8;
37
BigReal fast_a = fast_i [0];
38 )
39 )
40 FULL (
41
const BigReal * const scor_i = table_four + 16* table_i + 8 SHORT (+ 4) ;
42
BigReal slow_a = scor_i [0];
43 )
44
45
r2f . i &= 0 xfffe0000 ;
8.2. DIAGNÓSTICO SOFTWARE
93
Véanse las macros FAST y FULL en este fragmento. Estas, además de otras macros que
aparecen en el mismo bucle, determinan qué secciones de la función son compiladas dependiendo
de donde se usen. En todas las regiones donde se hace uso de alguna de ellas se hace inline del
bucle con el código correspondiente a la macro. Esto da lugar a que en el informe del compilador
haya múltiples resultados registrados para este bucle en concreto.
Cuando consultamos los bloques básicos con mayor número de instrucciones y ciclos, nos encontramos que muchos coincidı́an debido a lo mencionado sobre el inline de las macros en el bucle.
Además, todos tenı́an instrucciones escalares. Consultando las razones del compilador para no
vectorizar este bucle en ninguna de las ocasiones, encontramos una única razón: las dependencias.
ComputeNonbondedBase2.h(12): (col. 5) remark: loop was not vectorized: existence
of vector dependence
ComputeNonbondedBase2.h(76): (col. 7) remark: vector dependence: assumed
FLOW dependence between f j line 76 and p j line 21
ComputeNonbondedBase2.h(21): (col. 47) remark: vector dependence: assumed
ANTI dependence between p j line 21 and f j line 76
ComputeNonbondedBase2.h(76): (col. 7) remark: vector dependence: assumed
FLOW dependence between f j line 76 and f i line 76
ComputeNonbondedBase2.h(76): (col. 7) remark: vector dependence: assumed
ANTI dependence between f i line 76 and f j line 76
Según el compilador, las dependencias se encuentran entre las instrucciones p j y f j, ası́ como
f j y f i. En el caso de p j y f j, encontramos que p j era un puntero a un objeto de una clase
llamada CompAtom, que tomaba su valor de la variable p 1. La variable f j era una referencia a
un objeto de una clase llamada Force que tomaba su valor de f 1[j]. El problema radicaba en
que se estaba leyendo la posición x de p j y también se estaba escribiendo la posición x de f j.
Para el compilador existı́a una ambigüedad respecto a si las direcciones donde estaban apuntando
ambas variables era la misma.
register const CompAtom * p_j = p_1 + j ;
register const BigReal p_ij_x = p_i_x - p_j - > position . x ;
Force & f_j = f_1 [ j ];
register BigReal tmp_x = force_r * p_ij_x ;
f_j . x -= tmp_x ;
const CompAtom * p_1 = params - > p [1];
Force * f_1 = params - > ff [1];
La variable params es un puntero a la estructura de tipo nonbonded, la cual efectivamente tiene
dos campos p y ff de tipos CompAtom y Force respectivamente.
21 struct nonbonded {
22
CompAtom * p [2];
23
Force * ff [2];
25
Force * fullf [2];
...
41 };
Podemos pues asegurar que tanto p j como f j son dos variables que apuntan a zonas de memoria diferentes y no hay dependencia alguna. Entonces, para conseguir vectorizar se podrı́a hacer
uso del pragma ivdep que indica que se ignoren las dependencias. También se podrı́a modificar el
código de modo que el procesador entienda que tanto p j como f j son diferentes. Por ejemplo, se
podrı́a encerrar el código bajo la condición de que ambas variables fueran diferentes, lo cual serı́a
cierto siempre.
94
CAPÍTULO 8. ESTUDIO EXPERIMENTAL
Povray
Las instrucciones y ciclos de esta aplicación están muy repartidas entre todos los bloques que la
componen. Pese a esto, el primer bloque del conjunto de bloques más ejecutados que se muestra en
la Tabla 8.13 (pág. 94) presentaba un porcentaje mayor de instrucciones y ciclos que los siguientes
en la lista. Por este motivo se procede a su análisis.
1
PC
Función
Instrucciones
%
Ciclos
%
0x5fad40
pov::All Sphere Intersections
(pov::Object Struct*,
pov::Ray Struct*,
pov::istack struct*)
145.789.254
7,84
399.197.976
6,22
Tabla 8.13: Bloques 1 y 2 de los más ejecutados de Povray, SPEC FP
De este bloque fue muy útil consultar la información generada por otra de las herramientas
internas disponibles. A continuación se presentan dichas instrucciones junto con el detalle de las
lı́neas del código a las que pertenecen:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
sub rsp , 0 x58
add qword ptr [ rip +0 x1e8d24 ] , 0 x1
mov r8 , rsi
vmovsd xmm2 , qword ptr [ rdi +0 x80 ]
xor eax , eax
vmovsd xmm3 , qword ptr [ rdi +0 x78 ]
vmovsd xmm0 , qword ptr [ rdi +0 x88 ]
vmovsd xmm4 , qword ptr [ rdi +0 x90 ]
vmulsd xmm7 , xmm4 , xmm4
vsubsd xmm16 , k0 , xmm2 , qword ptr [ r8 +0 x8 ]
vsubsd xmm5 , xmm3 , qword ptr [ r8 ]
vsubsd xmm9 , xmm0 , qword ptr [ r8 +0 x10 ]
vmulsd xmm8 , k0 , xmm16 , xmm16
vmulsd xmm6 , k0 , xmm16 , qword ptr [ r8 +0 x20 ]
vfmadd231sd xmm8 , xmm5 , xmm5
vmovsd xmm4 , qword ptr [ r8 ]
vmovsd xmm3 , qword ptr [ r8 +0 x8 ]
vmovsd xmm2 , qword ptr [ r8 +0 x10 ]
vfmadd231sd xmm8 , xmm9 , xmm9
vfmadd231sd xmm6 , xmm5 , qword ptr [ r8 +0 x18 ]
vmovsd xmm0 , qword ptr [ r8 +0 x20 ]
vmovsd xmm1 , qword ptr [ r8 +0 x28 ]
vcmpsd k0 , k0 , xmm8 , xmm7 , 0 xd
vfmadd132sd xmm9 , xmm6 , qword ptr [ r8 +0 x28 ]
kortestw k0 , k0
jz 0 x5fadf4
#
#
#
#
#
#
#
#
#
#
#
#
#
spheres . cpp :123
frame . h :980
spheres . cpp :123
vector . h :90
spheres . cpp :129
vector . h :89
vector . h :91
spheres . cpp :131
vector . h :296
vector . h :90
vector . h :89
vector . h :91
vector . h :223
#
#
#
#
vector . h :89
vector . h :90
vector . h :91
vector . h :223
# spheres . cpp :288
# vector . h :223
# spheres . cpp :288
Las instrucciones terminadas en sd son escalares. Los ficheros que participan en el bloque son
spheres.cpp, vector.h y frame.h. Véase primero un fragmento de la función All Sphere Intersections
del fichero spheres.h:
spheres . cpp
122 static int A l l _ S p h e r e _ I n t e r s e c t i o n s ( OBJECT * Object , RAY * Ray , ISTACK *←Depth_Stack )
123 {
124
register int I n t e r s e c t i o n _ F o u n d ;
125
DBL Depth1 , Depth2 ;
126
VECTOR IPoint ;
127
SPHERE * Sphere = ( SPHERE *) Object ;
128
8.2. DIAGNÓSTICO SOFTWARE
129
130
131
95
I n t e r s e c t i o n _ F o u n d = false ;
if ( I n t e rs e c t _ S p h e r e ( Ray , Sphere - > Center , Sqr ( Sphere - > Radius ) , & Depth1 , &←Depth2 ) )
132
{
133
if (( Depth1 > D EP TH _T O LE RA NC E ) && ( Depth1 < Max_Distance ) )
134
{
135
VEvaluateRay ( IPoint , Ray - > Initial , Depth1 , Ray - > Direction ) ;
136
137
if ( Point_In_Clip ( IPoint , Object - > Clip ) )
138
{
139
push_entry ( Depth1 , IPoint , Object , Depth_Stack ) ;
140
141
I n t e r s e c t i o n _ F o u n d = true ;
142
}
143
}
144
145
if (( Depth2 > D EP TH _T O LE RA NC E ) && ( Depth2 < Max_Distance ) )
146
{
147
VEvaluateRay ( IPoint , Ray - > Initial , Depth2 , Ray - > Direction ) ;
148
149
if ( Point_In_Clip ( IPoint , Object - > Clip ) )
150
{
151
push_entry ( Depth2 , IPoint , Object , Depth_Stack ) ;
152
153
I n t e r s e c t i o n _ F o u n d = true ;
154
}
155
}
156
}
157
158
return ( I n t e r s e c t i o n _ F o u n d ) ;
159 }
La lı́nea principal es la 131 porque contiene la llamada a la función Intersect Sphere, dentro
del mismo fichero, la cual contiene las llamadas a funciones de los ficheros frame.h y vector.h que
participan en el bloque. A continuación se muestra un fragmento de Intersect Sphere.
275 int I n t er s e c t _ S p h e r e ( RAY * Ray , VECTOR Center , DBL Radius2 , DBL * Depth1 , DBL ←* Depth2 )
276 {
277
DBL OCSquared , t_Closest_Approach , Half_Chord , t _ H a l f _ C h o r d _ S q u a r e d ;
278
VECTOR O r i g i n _ T o _ C e n t e r ;
279
280
I n c r e a s e _ C o u n t e r ( stats [ R a y _ S p h e r e _ T e s t s ]) ;
281
282
VSub ( Origin_To_Center , Center , Ray - > Initial ) ;
283
284
VDot ( OCSquared , Origin_To_Center , O r i g i n _ T o _ C e n t e r ) ;
285
286
VDot ( t_Closest_Approach , Origin_To_Center , Ray - > Direction ) ;
En la lı́nea 280 está la llamada a la función Increase Counter de frame.h. El compilador hizo
inline de esta función. A continuación se muestra la única lı́nea que la compone:
frame . h
978 inline void I n c r ea s e _ C o u n t e r ( COUNTER & x )
979 {
980 x ++;
981 }
Las funciones VSub y VDot de las lı́neas 282, 284 y 286 son funciones también inline del
fichero vector.h. Ambas están sobrecargadas para diferente tipo de parámetros, por lo que todas
comparten el mismo contenido. A continuación se muestran un par de ellas a modo de ejemplo:
96
CAPÍTULO 8. ESTUDIO EXPERIMENTAL
vector . h
87 inline void VSub ( VECTOR a , const VECTOR b , const VECTOR c )
88 {
89 a [ X ] = b [ X ] - c [ X ];
90 a [ Y ] = b [ Y ] - c [ Y ];
91 a [ Z ] = b [ Z ] - c [ Z ];
92 }
221 inline void VDot ( DBL & a , const VECTOR b , const VECTOR c )
222 {
223 a = b [ X ] * c [ X ] + b [ Y ] * c [ Y ] + b [ Z ] * c [ Z ];
224 }
Si echamos un vistazo a la función VSub de la lı́nea 87, vemos que las tres instrucciones que
la componen se podrı́an condensar en un único bucle para invitar al compilador a vectorizarlas
con el pragma vector. Además, este fichero contiene multitud de funciones que se rigen bajo el
mismo patrón pese a que no participan en el bloque básico del que partió el análisis. El resultado
de comprimir las tres operaciones en un bucle serı́a como se ve en el siguiente código:
87
88
89
90
91
92
inline void VSub ( VECTOR a , const VECTOR b , const VECTOR c )
{
# pragma vector
for ( unsigned int i = 0; i < 3; i ++)
a [ i ] = b [ i ] - c [ i ];
}
Una vez hecho esto se podrı́a volver a compilar y comprobar si el compilador ha vectorizado
estos bucles. En caso de que sea ası́, se podrı́a traducir en un incremento de las instrucciones
vectoriales en sustitución de las escalares que minaban el bloque principal.
8.3.
Diagnóstico Hardware
En esta sección se presentan los experimentos realizados sobre aquellas aplicaciones limitadas
por memoria que clasificamos con la denominación memory bound. Cuando se mostraron los resultados de las simulaciones realizadas con CMP$im, habı́a aplicaciones fuertemente limitadas por
memoria cuyo análisis fue descartado independientemente de su caracterización vectorial. En un
80 % de los casos se trataba de aplicaciones que presentaban un nivel de vectorización importante,
pero debido a las limitaciones de memoria el IPC de la aplicación era deficiente. La siguiente lista
indica aquellas que presentaban este perfil:
channel y linpk de Polyhedron,
Cloverleaf de Mantevo,
IRSmk y UMTmk de Sequoia,
MG, FT, SP y CG de NPB
470.lbm, 434.zeusmp y 437.leslie3d de SPEC fp.
Las pruebas realizadas consistieron en doblar por un lado el tamaño de la cache unificada UL2,
y por otro el tamaño del segundo nivel de TLB de datos. Se eligieron estos niveles de memoria
porque querı́amos visualizar la mejora en caso de que hubiera más aciertos en los niveles de cache
previos a la memoria principal. La latencia fue otro de los campos a considerar. Sin embargo, dado
8.3. DIAGNÓSTICO HARDWARE
97
que la latencia configurada para la memoria principal era 22x respecto al último nivel de cache,
lo descartamos. El objetivo fue por tanto tener una visión general sobre qué ocurrirı́a cuando este
tipo de aplicaciones no tienen que visitar tanto la memoria principal.
8.3.1.
Incremento de UL2
Tomamos la decisión de simular solamente las aplicaciones en las que la limitación de memoria
superaba un 30 % de los ciclos de toda la aplicación. A la hora de entender las hipotéticas mejoras
experimentadas, hubo que tener en cuenta que, en la manipulación de porcentajes, no es lo mismo
que se haya experimentado una reducción del 90 % de ciclos de memoria en una aplicación donde
suponı́an un 5 % del total que en otra donde suponı́an un 50 %.
Por otro lado, como nos interesaba ver únicamente las aplicaciones con mejor ı́ndice de mejora,
en la Figura 8.12 (pág. 97) se visualizaron aquellas cuya mejora del IPC ascendı́a a más de un
1 %. El orden de las aplicaciones en el eje de abscisas está determinado por la mejora del IPC.
Nótese que las mejoras en la memoria se presentan con un porcentaje negativo (menos es mejor).
Esto se traduce en un incremento positivo del IPC. En la Tabla 8.14 (pág. 97) se muestran las
aplicaciones que teniendo más de 30 % de ciclos de memoria en la aplicación, no han conseguido
un mı́nimo de un 1 % de mejora en el IPC.
Figura 8.12: Resultado de doblar la UL2 de 1024Kb a 2048Kb
Benchmark
Aplicación
Polyhedron
linpk nf
Mantevo
miniMD miniGhost miniFE CloverLeaf HPCCG
Sequoia
UMTmk IRSmk
NPB
FT CG IS MG
SPEC fp
470.lbm
Tabla 8.14: Aplicaciones con una mejora inferior al 1 %
98
CAPÍTULO 8. ESTUDIO EXPERIMENTAL
La mejora de la aplicación SPEC fp/433.milc, visualizada en detalle en la Figura 8.13 (pág. 98),
dobla su IPC. La reducción del 82,2 % de ciclos de memoria ha supuesto una reducción del 52,5 %
de ciclos de la aplicación, que se traduce en un 110 % más de IPC. A la derecha se visualiza la
distribución del porcentaje de ciclos de la aplicación antes y después. Dejó de ser una aplicación
limitada exclusivamente por memoria principal, para pasar a ser una aplicación que, con un 87 %
de instrucciones escalares, serı́a candidata a ser analizada para mejorar el ı́ndice de vectorización.
Figura 8.13: Mejora de SPEC fp/433.milc al doblar la L2
Ténganse en cuenta casos como NPB/BT donde, pese a haber sufrido un decremento de ciclos
de memoria del 71,95 %, no repercute más que un 28,54 % de mejora del IPC. Esto ocurre porque
su porcentaje de ciclos de memoria estaba en el lı́mite del 30 % establecido como corte para el
experimento. Entonces, aunque se hayan reducido los ciclos de memoria, no se puede reducir más
el IPC. Otro caso caso particular es el de polyhedron/test fpu. En la gráfica se aprecia, junto al
25,2 % de mejora de ciclos de memoria, un 4 % en la UL2. Para analizar el resultado tenemos que
tener en cuenta los dos conceptos fundamentales sobre:
Ciclos de memoria: estos son el resultado de los fallos producidos en la cache unificada
de nivel 2 y, debido a la fuerte penalización en ciclos de memoria principal, las instrucciones
asociadas siempre aparecerı́an en el camino crı́tico.
Ciclos de UL2: que estos ciclos formen parte del camino crı́tico depende de los fallos
producidos en la caché de nivel 1. Sin embargo, como la penalización por un acierto en UL2
no es tan grande como la que generarı́a un fallo, si gracias a haber doblado el tamaño de
la UL2 se aumentan sus aciertos, significa que estos ciclos podrı́an haber dejado de formar
parte del camino crı́tico si la instrucción siguiente dependiera de una load-op anterior de
fuerte latencia (ej. división). Véase la Figura 8.14 (pág. 99).
Entonces, sabiendo que el número de accesos a la cache es obviamente el mismo y que no hemos
doblado el tamaño de la DL1, la reducción del 4 % tiene que estar ı́ntimamente relacionada con
que algunas instrucciones que antes formaban parte del camino crı́tico por fallo en la UL2, ahora
con el acierto, ya no lo son.
8.3. DIAGNÓSTICO HARDWARE
99
Figura 8.14: Consecuencia posible por aumento de aciertos en L2
8.3.2.
Incremento de TLB
Análogamente, no para todas las aplicaciones tenı́a sentido simular su comportamiento con el
doble de lı́neas en el segundo nivel de TLB. Dado que habı́a muy pocas aplicaciones con muchos
ciclos de TLB, se bajó el umbral de selección a un 10 %. No se bajó más porque se considera que
en una aplicación con menos del 10 % de ciclos de TLB es irrelevante aplicar una mejora costosa
como es aumentar el número de lı́neas. Incluso ası́, solo cumplieron el requisito cuatro aplicaciones.
Éstas se muestran en la Figura 8.15 (pág. 100) siguiendo la nomenclatura usada anteriormente.
La aplicación NPB,IS presenta una mejora del 64,50 % de ciclos que repercuten en una mejora
del IPC del 35,10 %. A su lado tenemos a NPB,DC que presenta el mismo comportamiento que
veı́amos para Polyhedron,test fpu en el apartado anterior. En este caso, además del 93,82 % de
mejora de los ciclos de memoria por fallos de TLB, también hay una reducción del 26,37 % en
los ciclos de acceso al TLB de segundo nivel. En el modelo, un fallo de TLB que provoca un
acceso a la tabla de páginas de la memoria principal acarrea una latencia importante. Si por
aumentar el número de lı́neas se reducen estos accesos, es factible que, como en el caso anterior,
las instrucciones que fueran responsables de dichos accesos hayan dejado de formar parte del
camino crı́tico de la aplicación. De todos modos, pese al 93,82 % de mejora de los ciclos, como la
reducción se aplica solamente sobre un 13,3 % de ciclos de TLB, al final el IPC solo mejora un
12,5 %. En la Figura 8.16 (pág. 100) se presenta en detalle el resultado de mejorar NPB,IS.
En la parte izquierda de la Figura 8.16 (pág. 100), vemos el decremento en ciclos y el aumento
de IPC de la aplicación IS de NPB. Pese a que es una mejora tı́mida, la gráfica derecha nos
devuelve a la realidad porque nos recuerda que sigue siendo una aplicación limitada por memoria.
Además, si recordamos la Tabla 8.14 (pág. 97), era una de las aplicaciones con una mejora inferior
al 1 % cuando se dobló el tamaño de la caché unificada L2. Por tanto, para esta arquitectura en
concreto, este tipo de mejoras es insuficiente y no deja otro camino que abrir el paso hacia una
posible mejora de la arquitectura en sı́ como alternativa.
El diagnóstico hardware realizado en este apartado quiso marcar el final de la búsqueda del
modo de hacer un uso efectivo de la unidad vectorial. Hemos visto aquı́ que, pese a que el objetivo
principal del trabajo se cumpla, el perfil de una aplicación al ser ejecutado sobre una máquina
con una configuración de memoria concreta puede impedir que se obtengan las mejoras que se
esperarı́an al hacer un buen uso de la unidad vectorial. En definitiva, la memoria no se puede
ignorar y podrı́a ser necesario hacer un buen estudio previo sobre el uso de las estructuras de datos
de la aplicación, para, o bien para maximizar el rendimiento de la aplicación sobre la máquina, o
bien para mejorar la máquina.
100
CAPÍTULO 8. ESTUDIO EXPERIMENTAL
Figura 8.15: Resultado de doblar las lı́neas de DTLB2 de 256 a 512
Figura 8.16: Mejora de IS de NPB al doblar la TLB
Capı́tulo 9
Conclusiones
La vectorización es una herramienta potente que puede proporcionar buenos resultados cuando se consigue usar plenamente. Teniendo esto presente, el estudio de la utilización efectiva de
procesadores vectoriales presentado ha consistido en el proceso que se describe a continuación.
En primer lugar se tomaron el conjunto de benchmarks Polyhedron, Mantevo, NPB, Sequoia
y SPEC fp, de los cuales se seleccionaron, o bien todas las aplicaciones, o bien solo una muestra según el tamaño de los ficheros de entrada disponibles. Se compilaron con los compiladores
R
R
IntelICC
e IntelIFORT
según el lenguaje en el que estuvieran escritas, y se realizó una primera caracterización de las mismas para conocer sus ı́ndices de vectorización y recopilar las causas
proporcionadas por el compilador para no vectorizar. Este primer acercamiento mostró que, en
un 21 % de las aplicaciones, el número de instrucciones vectoriales era igual o superior al 38 % del
total.
A continuación se incorporó al simulador de cache CMP$im una funcionalidad nueva: un modeR Xeon PhiTM . Con los resultados obtenidos
lo simplificado de los núcleos del coprocesador Intel
con el simulador modificado, y apoyándonos en la comparativa de la versión vectorizada frente a
la no vectorizada, se descubrió que un 57 % de aplicaciones ejecutaban menos instrucciones que
sus respectivas versiones no vectorizadas. Aunque podrı́a parecer un dato positivo, hay que tener
en cuenta que solo un 21 % de aplicaciones tenı́a un 38 % o más de instrucciones vectoriales. Por
este motivo, pese a que las versiones vectorizadas hubieran mejorado, el uso de la unidad vectorial
es bajo en el 79 % de las aplicaciones. Además, a esto hay que añadir que un 32 % del mencionado
57 % de aplicaciones, mostraron un comportamiento fuertemente limitado por los accesos a la
memoria principal.
En este punto se tenı́a información suficiente sobre las aplicaciones no limitadas por memoria,
para seleccionar las más significativas desde el punto de vista de la mejora del uso de la unidad
vectorial. Se estudiaron tanto a nivel de bloques básicos como a nivel de código fuente. Los objetivos
eran identificar las regiones más ejecutadas que el compilador no habı́a vectorizado, estudiar las
causas y proponer una posible solución. Solo se implementó la solución en una de las aplicaciones
porque era una tarea fuera de los objetivos de este trabajo.
Finalmente, se decidió realizar dos pruebas sobre las aplicaciones que, estando limitadas por
memoria, tenı́an un buen ı́ndice de vectorización: doblar el tamaño de la L2 y el número de
lı́neas del segundo nivel de TLB. Se consideraron, para la primera prueba, aquellas cuyos ciclos
de memoria principal suponı́an un 30 % o más de los ciclos de la aplicación. Se observó que solo
un 45 % obtuvo una mejora superior al 1 %. De ese 45 %, un 50 % experimentó una mejora del
IPC superior al 20 %. Para la segunda prueba se consideraron aquellas aplicaciones cuyos ciclos
de TLB eran superiores o igual al 10 % del total. Solo se simularon 4 aplicaciones de las cuales 2
101
102
CAPÍTULO 9. CONCLUSIONES
obtuvieron mejora. Sin embargo solo una de ellas mejoró en más de un 20 % el IPC.
Todos los datos presentados muestran de forma clara la fuerte dependencia que existe entre
la vectorización, la memoria, la programación y el compilador. La complejidad que puede llegarse
a alcanzar para poder maximizar el aprovechamiento de la unidad vectorial de un procesador es
fuerte. Además, el hecho de que el compilador sea muy bueno y proporcione multitud de funcionalidades, podrı́a resultar inútil si el código no está bien escrito, o si no lo está como el compilador
lo espera. Podrı́a llegarse al punto en que hubiera que reescribirse el código completo. Esto hace
recaer una gran parte de la responsabilidad en el programador y su habilidad para explotar las facilidades de que se dispongan para maximizar la vectorización de la aplicación. Las limitaciones de
memoria, por su parte, resultaron ser un motivo importante que impidió obtener buen rendimiento
en aplicaciones que hacı́an ya un buen uso de la unidad vectorial. El diagnóstico hardware quiso
demostrar precisamente que la memoria no se puede ignorar y que podrı́a ser necesario estudiar
bien el uso de las estructuras de datos de la aplicación para saber qué configuración de memoria
le vendrı́a mejor.
Por tanto, el estudio de la utilización efectiva de la unidad vectorial realizado ha servido para
afirmar finalmente lo siguiente:
La vectorización es posible pero en ocasiones muy difı́cil de conseguir.
El compilador es una herramienta crucial en todo el proceso.
La penalización de los accesos a memoria resulta ser en muchos casos un cuello de botella
importante en el balance final del cómputo de la aplicación, pese a la vectorización.
9.1.
Trabajo Futuro
Entre los trabajos futuros que han quedado pendientes en el presente estudio, y que podrı́an
por tanto conferirle una mayor completitud, se encuentran los siguientes:
Implementar las propuestas presentadas en el Sección 8.2 (pág. 74) para cada una de las
aplicaciones seleccionadas:
• Usar el pragma vector always pese a que las iteraciones de un bucle sean pequeñas.
• Reescribir el código en caso de que con pragmas sea insuficiente o porque el código sea
muy complejo.
• Usar el pragma ivdep en caso de que las dependencias no sean tales debido a ambigüedades de los punteros.
• Usar el pragma unroll para invitar al compilador a reordenar instrucciones en caso de
problemas por dependencias.
• Generar bucles en aquellas regiones donde el acceso a cada uno de los elementos de un
vector o matriz se realizó con instrucciones sueltas.
Completar CMP$im para simular aplicaciones multi-threading y ver el comportamiento haR Xeon PhiTM .
ciendo uso de todos los núcleos del coprocesador Intel
Añadir etapas y lı́mite de recursos a CMP$im para obtener resultados más detallados.
Estudiar diferentes configuraciones de memoria, ası́ como su viabilidad, para mejorar el
rendimiento de las aplicaciones limitadas por memoria.
Bibliografı́a
[Bar07]
Blaise Barney. Introduction to parallel computing. 2007.
[Bar08]
Miquel Barceló. Una historia de la informática. Editorial UOC, 2008.
[Dı́06]
Domingo Benı́tez Dı́az. Arquitectura de Computadores. Manual de teorı́a. Universidad
de Las Palmas de Gran Canaria. Vicerrectorado de Planificacion y Calidad, 2006.
[fly]
Flynn’s taxonomy.
http://en.wikipedia.org/wiki/Flynn’s_taxonomy.
[Fly72]
Michael J. Flynn. Some computer organizations and their effectiveness. IEEE Transactions on Computers, C-21(9):948–960, 1972.
[HP02]
John L. Hennessy and David A. Patterson. Computer architecture a quantitative approach. Morgan Kaufmann, tercera edición edition, 2002.
[inta]
Avx-512 instructions.
https://software.intel.com/en-us/blogs/2013/avx-512-instructions.
[intb]
R xeon phiTM coprocessor - the architecture.
Intel
https://software.intel.com/en-us/articles/intel-xeon-phi-coprocessor-\
codename-knights-corner.
[intc]
R software documentation library.
Intel
http://software.intel.com/en-us/intel-software-technical-documentation/.
[intd]
R cilkTM plus.
Introduction to Intel
http://software.intel.com/en-us/videos/introduction-to-intel-cilk-plus/.
[inte]
R c++ compiler.
An introduction to vectorization with the Intel
http://d3f8ykwhia686p.cloudfront.net/1live/intel/An_Introduction_to_
Vectorization_with_Intel_Compiler_021712.pdf.
[JCLJ06]
Aamer Jaleel, Robert S. Cohn, Chi-Keung Luk, and Bruce Jacob. Cmp$im: A binary
instrumentation approach to modeling memory behavior of workloads on cmps. 2006.
[JCLJ08]
Aamer Jaleel, Robert S. Cohn, Chi-Keung Luk, and Bruce Jacob. Cmp$im: A pinbased on-the-fly multi-core cache simulator. 2008.
[LCM+ 05] Chi-Keung Luk, Robert Cohn, Robert Muth, Harish Patil, Artur Klauser, Geoff Lowney, Steven Wallace, Vijay Janapa Reddi, and Kim Hazelwood. Pin: Building customized program analysis tools with dynamic instrumentation. In Proceedings of the 2005
ACM SIGPLAN conference on Programming language design and implementation, pages 190–200, 2005.
[LPG13]
Dominic Orchard Leaf Petersen and Neal Glew. Automatic simd vectorization for
haskell. 2013.
103
104
BIBLIOGRAFÍA
[man]
Mantevo benchmarks.
http://mantevo.org/.
[MS03]
Pratyusa Manadhata and Vyas Sekar. Vector processors. 2003.
[nas]
Nas parallel benchmarks.
http://www.nas.nasa.gov/publications/npb.html.
[nvi]
Nvidia’s cuda toolkit: Parallel thread execution isa version 4.0.
http://docs.nvidia.com/cuda/parallel-thread-execution/index.html.
[pin]
Pin - a dynamic binary instrumentation tool. http://pintool.org/.
[Pip12]
R fortran compiler. 2012.
Chuck Piper. An introduction to vectorization with the intel
[pol]
Polyhedron fortran benchmarks.
www.polyhedron.com.
[seq]
Asc sequoia benchmark codes.
https://asc.llnl.gov/sequoia/benchmarks/.
[SLA05]
Rodric Rabbah Samuel Larsen and Saman Amarasinghe. Exploiting vector parallelism
in software pipelined loops. 2005.
[SMP11]
Maria J. Garzarán Tommy Wong Saeed Maleki, Yaoquing Gao and Daivd A. Padua.
An evaluation of vectorizing compilers. 2011.
[spe]
Spec cpu 2006.
http://www.spec.org/cpu2006/.
Apéndice A
R ICC Specific Pragmas
Intel
R Specific Pragmas[intc] son pragmas desarrollados especı́ficamente para el compilaLos Intel
R El listado se puede comprobar en la Tabla A.1.
dor ICC de Intel.
alloc section
Asigna una variable a una sección especı́fica.
cilk grainsize
Especifica el número máximo de iteraciones que se
ejecutaran en serie en un cilk for. Es un tipo especial de bucle que permite que varias conjuntos de
iteraciones se ejecuten paralelamente, pero las iteraciones de cada uno de esos conjuntos se ejecutan
en serie. Por tanto, el grainsize es precisamente el
número máximo de loops en cada conjunto a ejecutar paralelamente.
distribute point
Indica al compilador que en la región indicada se
prefiere distribución de bucles.
inline
Indica al compilador la preferencia de que una llamada a un procedimiento o función concretos se haga
inline.
intel omp task
Sirve para indicar una unidad de trabajo, ejecutada probablemente por un hilo distinto, de cara a la
delegación de tareas.
intel omp taskq
Define el entorno en el que se encolarán cada una de
las unidades de trabajo especificadas, de cara a la
delegacion de tareas.
ivdep
Indica al compilador que ignore las dependencias vectoriales que ha detectado en el bucle que le sigue en
el código.
loop count
Indica el número de veces que el bucle que le sigue
se va a ejecutar.
105
R ICC SPECIFIC PRAGMAS
APÉNDICE A. INTEL
106
nofusion
Impide que el bucle siguiente se fusione con otros
bucles adyacentes.
novector
Indica que el bucle siguiente no debe ser vectorizado.
offload
La instrucción siguiente al pragma se ejecutara en el
R MIC
target especificado. Aplicable solo sobre Intel
Architecture.
offload attribute
Sirve para especificar que las variables y funciones
declaradas a continuacion del pragma, estarán dispoR
nibles en el coprocesador. Aplicable solo sobre Intel
MIC Architecture.
offload transfer
Para iniciar transferencias de datos ası́ncronas o iniciar y completar transferencias de datos sı́ncronas.
R MIC Architecture
Aplicable solo sobre Intel
offload wait
Establece un punto de espera para actividades
ası́ncronas iniciadas previamente. Aplicable solo soR MIC Architecture.
bre Intel
omp atomic
Sirve para asegurar que la actualización de una determinada posición de memoria sea atómica, con el
objetivo de impedir la posibilidad de que los hilos
realicen lecturas y escrituras múltiples o simultáneas.
omp task
Define la región de una tarea.
omp taskyield
Habilita o deshabilita optimizaciones sobre determinadas funciones. Es en cierto grado compatible con
la implementación de MicrosoftTM del pragma optimize.
omp taskwait
Especifica un punto de espera para la finalización de
tareas generadas desde el comienzo de la ejecución
de la tarea actual.
optimize
Habilita o deshabilita optimizaciones sobre todo el
código escrito a continuación del pragma, hasta que
se encuentre con otro pragma optimize o con el fin
de la unidad de compilación.
optimization level
Restringe el nivel de optimización de una función
concreta. Mientras que para todo el programa se puede haber indicado -O3 desde la lı́nea de compilación,
con #pragma optimization 1 se indica que a la función que le acompaña se le aplicará -O1.
optimization parameter
Indica al compilador la tarea de generar código especı́fico, a nivel de función, para un tipo de procesador. Es semejante a la opción de compilación
-m(arch).
107
parallel/ noparallel
Sirve para indicar al compilador que resuelva dependencias de bucles mediante la auto-paralelización del
bucle situado inmediatamente a continuación. En el
caso de noparallel, impide la auto-paralelización
del bucle inmediatamente a continuación.
prefetch/ noprefetch
Invita al compilador a emitir prefetches de datos
a memoria. En el caso de noprefetch deshabilita
R
el prefetching de datos. Aplicable solo sobre Intel
MIC Architecture.
simd
Sirve para forzar al compilador a vectorizar el bucle
sobre el que se defina.
unroll/ nounroll
Indica al compilador el número de veces que tiene
que desenrollar un bucle. En el caso de nounroll, le
impide aplicar esta técnica.
unroll and jam/
nounroll and jam
Estos pragmas invitan o impiden que el compilador
desenrolle y fusione bucles. Estos bucles solo pueden
ser de tipo FOR.
unused
Indica variables que no se van a usar con el objetivo de impedir la generación de warnings durante la
compilación.
vector
Indica al compilador que el bucle siguiente deberı́a
ser vectorizado de acuerdo a los siguientes parámetros: always, aligned, unaligned, nontemporal,
temporal.
R ICC Specific Pragmas
Tabla A.1: Intel
Apéndice B
R ICC Supported Pragmas
Intel
R Supported Pragmas[intc] son un conjunto desarrollado por fuentes externas que son
Las Intel
mantenidas en estos compiladores por razones de compatibilidad. Muchas de ellas se encuentran en
la documentación de los entornos de programación ofertados por MicrosoftTM . Se puede consultar
una descripción breve de ellas en la Tabla B.1
alloc text
Indica la sección de código donde tienen que estar las
definiciones de función especificada.
auto inline
Aquellas funciones sobre las que se indique el
parámetro off, serán excluidas como candidatas para expandirse automaticamente (inline).
bss seg
Especifica al compilador el segmento dentro del fichero .obj donde las variables no inicializadas tienen
que residir.
check stack
Si se especifica como parámetro on, se habilitará el
check de pila para las funciones inmediatamente a
continuación. En el caso de que sea off, se deshabilitara.
code seg
Especifica una sección de código donde se situarán
las funciones.
comment
Inserta un registro de comentarios en un fichero objeto o ejecutable.
component
Sirve para controlar la generación de información
como pueden ser las dependencias o la denominada browse information, que incluye aspectos como
definiciones, referencias, macros, etc. a partir de los
fuentes de la aplicación.
conform
Especifica el comportamiento en tiempo de ejecución
de la opción de compilación /Zc:forScope (Force
Conformance in FOR Loop Scope) incluida en el entorno de Visual Studio de MicrosoftTM .
109
R ICC SUPPORTED PRAGMAS
APÉNDICE B. INTEL
110
const seg
Especifica el segmento donde se alojarán las funciones el fichero .obj.
data seg
Sección especı́fica para la inicialización de datos.
deprecated
Indica para una función, tipo o cualquier otro identificador, que puede no estar soportado en futuras
versiones o que no deberı́a usarse más.
poison
Sirve para etiquetar los identificadores que se deberı́an eliminar de la aplicación, de manera que cuando se compile, aquellos identificadores marcados con
este pragma provocarán un error de compilación.
float control
Especifica que una función tiene operaciones en punto flotante.
fp contract
Habilita o deshabilita la implementación para fusionar expresiones.
include directory
Incorpora la cadena pasada como argumento, a la
lista de sitios donde buscar por ficheros de inclusión.
init seg
Especifica la sección que contiene inicializaciones en
C++ para las unidades de compilación generadas
tras el preprocesador.
message
Muestra la cadena especificada como parámetro en
la salida estándar.
optimize
Especifica las optimizaciones a realizar sobre las funciones bajo el este pragma o hasta el siguiente pragma del mismo tipo. También se encuentra en la anR con
terior lista de aquellos desarrollados por Intel,
el objetivo de soportar la implementación del de
MicrosoftTM .
options
Pragma compatible con el compilador GCC de MacOS. Configura el alineamiento de los campos en las
estructuras de datos.
pointers to members
Especifica si un puntero a un miembro de una clase
se puede declarar antes de la definición de la clase
en cuestión. También es usado para controlar el tamaño del puntero y el código que se necesitara para
interpretarlo.
pop macro
Configura el valor de una macro en concreto al valor
que se encuentre en la cima de la pila asociada a
dicha macro.
push macro
Salva el valor de una macro en concreto en la cima
de la pila asociada a esta macro.
111
region/endregion
Sirve para especificar un segmento de código en el
MicrosoftTM Visual Studio 2005 Code Editor, que se
despliega o contrae utilizando las caracterı́sticas propias de esta herramienta de trabajo.
section
Crea una sección en un fichero .obj. Una vez que
esta seccion es definida, permanece válida durante el
resto de la compilación.
start map region
Se usa junto con stop map region.
stop map region
Se usa junto con start map region
fenv access
Sirve para informar a una implementación, que un
programa podrı́a testear los flags de estado o ejecutar o ejecutarse bajo un modo distinto al modo por
defecto.
vtordisp
Si el parámetro es on, se habilita la generación de
miembros vtordisp ocultos. Para el caso de off se
deshabilita.
warning
Permite la alteración selectiva del comportamiento
de los mensajes de aviso, warnigs, del compilador.
weak
Sirve para indicar que un sı́mbolo es weak, es decir
que en caso de no encontrarse la definición para ese
sı́mbolo en el tiempo de linkado, no se lanza ningún
error.
R ICC Supported Pragmas
Tabla B.1: Intel
Apéndice C
R Fortran Directives
Intel
R Fortran proporciona algunas directivas de compilación de propósito
El compilador Intel
general para permitir configurar algunas tareas a realizar durante la compilación[intc]. El listado
de la tabla Tabla C.1 presenta el conjunto de directivas disponibles.
ALIAS
Sirve para especificar un nombre alternativo para los
subprogramas externos a los que se haga referencia.
ASSUME ALIGNED
Especifica que una entidad se encuentra alineada en
memoria.
ATTRIBUTES
Permite definir propiedades sobre objetos y procedimientos.
DECLARE and NODECLARE
Activa o desactiva los warnings del compilador en el
caso de que haya variables que se hayan usado pero
que no se hayan definido.
DEFINE and UNDEFINE
Sirve para definir o eliminar la definición de variables
simbólicas cuya existencia o valor pueda ser testeadas
durante compilación condicional.
DISTRIBUTE POINT
Sirve para sugerir al compilador los puntos donde un
bucle DO puede partirse.
FIXEDFORMLINESIZE
Establece la longitud de la lı́nea para el código fuente
de tipo fixed-form. Por ejemplo, que en la columna 1
de cada fila es donde se pone el sı́mbolo *, c ó ! para
indicar que es un comentario. También es el caso de
la columna 6, que sirve para indicar con un sı́mbolo
que la lı́nea anterior continúa en la lı́nea siguiente.
FREEFORM and NOFREEFORM
La primera indica que el código fuente se corresponde con formato free-form. La segunda indica que se
corresponde con formato fixed-form.
IDENT
Especifica una cadena que identifica al un módulo
objeto.
113
R FORTRAN DIRECTIVES
APÉNDICE C. INTEL
114
IF and IF DEFINED
Sirve para especificar aquellas secciones de código
que son de compilación condicional.
INLINE, FORCEINLINE,
NOINLINE
Indica el tipo de inlining que el compilador tiene que
llevar a cabo para rutinas o bucles DO.
INTEGER
Sirve para establecer el número de bytes por defecto
que serán asignados a los enteros.
IVDEP
Indica al optimizador del compilador que asuma que
las dependencias se producen en la misma dirección
que su aparición.
LOOP COUNT
Especifica el número de iteraciones que va a realizar
un bucle DO.
MESSAGE
Indica la cadena que se enviará a la salida estándar
durante la primera pasada del compilador.
NOFUSION
Impide que un bucle se fusione con otros bucles adyacentes en caso de ser posible.
OBJCOMMENT
Especifica la ruta de búsqueda de una librerı́a en el
fichero objeto. Se puede especificar más de una directiva de este tipo para configurar diferentes rutas
a distintas librerı́as en un mismo código fuente.
OPTIMIZE and NOOPTIMIZE
Habilita o deshabilita optimizaciones sobre la unidad
de programa. Una unidad de programa puede ser el
programa principal, una subrutina externa o función.
OPTIONS
Afecta al alineamiento de datos y a los warnings producidos por ello.
PACK
Especifica el alineamiento en memoria de tipos derivados o estructuras tipo struct.
PARALLEL and NOPARALLEL
Facilita la auto-paralelización ayudando al análisis
de dependencias del compilador sobre el bucle DO
siguiente. En el caso de NOPARALLEL, se impide.
PREFETCH and NOPREFETCH
Habilita o deshabilita las pistas para el compilador
de cara a realizar prefetching de datos de memoria.
PSECT
Modifica las caracterı́sticas de un bloque común. Si la
unidad de programa cambia una o más caracterı́sticas de uno de estos bloques, todas las unidades que
lo referencien deben también cambiar.
REAL
Sirve para establecer el número de bytes por defecto
que serán asignados a los reales.
SIMD
Requiere y controla la vectorización SIMD de los bucles.
115
STRICT and NOSTRICT
STRICT habilita caracterı́sticas del lenguaje no encontradas en el estándar especificado en lı́nea de comandos (Fortran 2008, 2003, 95 o 90). Por el contrario, NOSTRICT las deshabilita.
UNROLL and NOUNROLL
La primera indica al optimizador del compilador
cuantas veces hay que desenrollar el bucle DO al que
afecta. La segunda impide el desenrollamiento de ese
bucle.
UNROLL AND JAM and
NOUNROLL AND JAM
Habilitan o deshabilitan, respectivamente, la posibilidad del compilador de desenrollar y fusionar. Solo
se pueden aplicar a bucles DO iterativos.
VECTOR and NOVECTOR
Sobrescriben las heurı́sticas por defecto para la vectorización de bucles DO.
R Fortran Directives
Tabla C.1: Intel
Apéndice D
Mensajes del compilador
Los mensajes del compilador más significativos son los siguientes:
Low trip count
Bucle con un número de iteraciones pequeña.
Vectorization possible but seems inefficient
Bucle demasiado largo con elevado uso de recursos,
ej. registros.
Unsupported loop structure
El compilador no es capaz de determinar el número
de iteraciones, por ejemplo si se usan punteros como
contadores.
Not inner loop
El bucle tratado no es un bucle interno.
Existence of vector dependence
existencia de dependencias entre los punteros del bucle.
Nonstandard loop is not a vectorization
candidate
El bucle contiene más de un punto de salida. También puede ocurrir que haya una llamada en bucle
que se haya pasado como parámetro a la función que
contiene el bucle y que no puede resolver.
Unsupported reduction
no soporta alguna de las reducciones que se estén
realizando dentro del bucle
Conditional assignment to a scalar
El bucle tiene una operación de asignación a una variable escalar, o un campo de una estructura, dentro
de una condición.
Statement cannot be vectorized
El uso de expresiones especı́ficas de Cilk o alguna
herramienta multi-hilo como OpenMP dan lugar a
este mensaje.
Tabla D.1: Mensajes del compilador
117
Descargar