El efecto de la optimización de expansión de código sobre el diseño de la Cache de instrucciones.
William Y. Chen, Pohua P. Chung, Thomas M. Conte, Member, IEEE, and Wen-mei W. Hwu, Member, IEEE.
 

Tabla de Contenidos
 

1. Introducción
2. Parámetros de la cache de instrucciones.
3. Optimizaciones y transformación de código. 
        3.1 Base de la optimización.
        3.2 Perfil de ejecución.
        3.3 Colocación de Instrucciones.
        3.4 Expansión de funciones en línea.
        3.5 Procesos Superescalares.
4. Experimentos y análisis.
5. Conclusiones.



Volver

1. Introducción.

El compilador juega un rol protagónico en el rendimiento de los procesadores, pues son estos los que traducen el código de alto nivel a lo que podríamos llamar un código de máquina hecho a mano.  Clásicamente los compiladores reducen el número de instrucciones, sin embargo esto limita la eficiencia de la optimización.  Para dar un ejemplo un programa que tenga un cuerpo pequeño y funciones que realizan las tareas nos da una pequeña una pequeña área de acción para realizar optimizaciones, para esto el método de expansión de funciones en línea reemplaza las llamadas a funciones por el código de ésta, con ello tenemos un área de acción más extensa para realizar las optimizaciones. Estas optimizaciones incrementan la cantidad de líneas de código para aumentar la eficiencia.  Por lo tanto vemos claramente que estas optimizaciones deben tener un efecto sobre el rendimiento de la cache.

A continuación se revisan los efectos de las optimizaciones de expansión de código sobre distintos rangos de configuraciones de cache de código.  Los datos experimentales indican que tales optimizaciones reflejan una fuerte y no intuitiva implicancia sobre el diseño de la cache de instrucciones.

Se estudian los efectos tres tipos de optimizaciones de expansión de código : colocación de instrucciones, expansiones de funciones en línea y optimizaciones superescalares. La primera reduce la razón de pérdida en cache pequeñas, la segunda mejora el rendimiento en cache pequeñas, pero decrece en cache de tamaño medio y la tercera incrementa la razón e pérdida en cache pequeña, sin embargo, incrementa la secuencialidad de instrucciones de acceso por lo que un simple esquema de carga avanzada cancela los efectos negativos.  Por último se mostrará que los tres tipos de optimizaciones mejoran el rendimiento de pequeñas cache en cargas consecutivas.


Volver
2. Parámetros de la cache de instrucciones.

Las dimensiones de la cache son expresadas por 3 parámetros :

El tamaño, 2C, de la cache es la cantidad de bytes que pueden estar simultáneamente en la cache.  Esta memoria cache está dividida en  2B bloques, que están agrupados en sets asociativos con 2S bloques por set.  Cuando S = 0, tenemos una cache de mapeo directo y cuando S = 2C-B , tenemos una cache asociativa.
 
Una de las métricas mas utilizadas en los estudios de cache es la razón de pérdida, que es la razón entre el número de referencias a la cache no satisfechas y  el número total de referencias hechas a la cache.  Esta medida es independiente del tiempo de acceso a los elementos, por lo que esta métrica sirve para determinar el ancho de banda de un sistema de memoria.


Volver
3. Optimizaciones y transformación de código.

3.1 Base de la optimización.

El compilador utilizado es un compilador de C.  Éste utiliza en set de optimizaciones estándar disponibles en muchos compiladores.  El objetivo de las optimizaciones es reducir una aplicación e su tiempo de ejecución.  Las optimizaciones locales son realizadas dentro de un bloque de instrucciones, mientras que una optimización global es realizada a través de los diferentes bloques básicos del programa.

3.2 Perfil de ejecución.

El perfil de ejecución es medido en su rendimiento a través de mediciones de Benchmarks.  Los programas son traducidos a C para ser compilados y ejecutados.  Los perfiles de información son recogidos usando un programa con un promedio de 20 entradas por benchmark.  Cada entrada adicional es utilizada para medir el rendimiento de la cache.

3.3 Colocación de Instrucciones.

Reordena el programa para mejorar el rendimiento de la memoria.  Este método mejora el rendimento de la cache realizando un orden jerárquico de las instrucciones.  El algoritmo trata de eliminar los conflictos de mapeo a la cache e incrementa el uso de la memoria en forma secuencial.

Para una función dada se toman muchos pasos para guardar la secuencia de instrucciones.  Para cada función los bloques básicos, que tienden a ser ejecutados en forma secuencial, son agrupados en trazos.  Los trazos son la unidad básica utilizada para la colocación de instrucciones.  Se reordenan los trazos, insertando los trazos que menos han sido ejecutados.

Esto puede incrementar la necesidad de agrandar el tamaño de los bloques de la cache.  Para el mismo tamaño de cache, un incremento en el tamaño del bloque decrementa la cantidad de TAG’s a guardar.

3.4 Expansión de funciones en línea.

Consiste en reemplazar las llamadas a funciones que más se utilizan por el código de este.  Esto es bueno para agrandar el área de acción para la optimización.
Esta transformación aumenta el número de referencias únicas, por lo  resultan más pérdidas.  Sin embargo la tasa de pérdidas decrece, pues sin la expansión existe un potencial de reemplazo de llamada en las funciones alojadas en la cache de instrucciones.

3.5 Procesos Superescalares.

El proceso superescalar se utiliza en computadores con procesadores multiescalares y consta de 4 partes :

Formación de superbloques : Un superbloque es una secuencia de instrucciones que pueden ser alcanzadas sólo desde la parte de arriba y puede contener míltiples ramificaciones con más instrucciones.  Cuando la ejecución alcanza un superbloque es como si todos los bloques básicos dentro del superbloque fueran ejecutados.  Un trazo puede ser convertido en un superbloque creando una copia del trazo y redireccionando todas las transferencias de control en el medio del trazo de la copia.

Loop desenrollado : El cuerpo de un superbloque con loop es duplicado para incrementar la cantidad de instrucciones dentro del superbloque.  Al desenrollar un loop N veces, el tamaño del cuerpo del loop se ve incrementado N-1 veces.

Loop Peeling : Hay veces en que los loop iteran muy pocas veces, por lo que el Loop desenrollado no son muy efectivos.  Por lo tanto lo que se realiza es tomar el código como una línea recta sin necesidad de interpretrarlo como un loop.

Expansion de Branch Target : La colocación de instrucciones y la formación de superbloques introduce ramificación de código. El  branch target ayuda a eliminar el número de ramificaciones tomadas por la copia de traget de bloques básicos.


Volver
4. Experimentos y análisis.

Se realizó in benchmark con programas de las siguientes características:
 
Programa
Descripción
Tamaño del código (bytes)
Referencia a las instrucciones
cccp
Preprocedador de C GNU
20400
2.89 x 107
eqntoot
Generador de tablas de verdad
15256
1.47 x 108
espresso
minimizaciones boleanas
61264
5.48 x 107
mpla
pla layout
138808
1.07 x 108
tbl
format table for troff
24804
3.08 x 107
xlisp
interprete de lisp
31920
1.46 x 108
yacc
programa generador de parsing
21320
3.47 x 107
 

Los tamaños de cache considerados para las pruebas fueron de 1 a 256 K bytes y el tamaño de los bloques fue de 12, 32, 64 y 128 bytes.

En esta parte de considera la siguiente notación :

no : sin optimización.
pl : colocación de código.
in : función en línea.
su : soptimización superescalar.

Los aumentos de código con los diferentes métodos se muestran en la siguiente tabla
 
Programa
no(%)
pl (%)
in (%)
su(%)
cccp
-
2
36
54
eqntoot
-
1
2
7
espresso
-
1
10
60
mpla
-
1
13
41
tbl
-
3
22
67
xlisp
-
1
18
69
yacc
-
4
21
110
promedio
-
2
17
55
 
 
Le número de referencias dinámicas se muestra en la siguiente tabla :
 
Programa
% de cambio
Base sin optimización
pl (%)
pl (%)
pl (%)
cccp
2.89 x 107
-0.27
-2.01
-3.17
eqntoot
1.47 x 108
-0.42
-0.43
-0.45
espresso
5.48 x 107
+0.18
-1.23
-3.33
mpla
1.07 x 108
-0.62
-6.18
-10.1
tbl
3.08 x 107
+0.21
-12.3
-16.2
xlisp
1.46 x 108
-1.84
-14.6
-16.7
yacc
3.47 x 107
-1.00
+0.13
+6.53
 
 
 
Figura 1
 

La figura 1 muestra el efecto promedio de la colocación de instrucciones sobre el porcentaje de pérdida de la cache.  Por un lado se reduce la razón de pérdida en cache pequeña 1 a 2 K, se puede comparar que una cache de 1K con colocación de instrucciones se puede comparar a una cache de 2K sin ella.  Pero en cache grande el efecto es poco notorio (8 a 16K).

 
Figura 2
 

La figura 2 muestra cache con razones de pérdidad muy altas a las cuales se les aplica la colocación de código.  Como se ve claramente el beneficio de la colocación de instrucciones es más notorio aún.
 
 
 
 
Figura 3
La figura 3 muestra el efecto promedio de la función en línea sobre la razón de pérdida de la cache.  La razón de pérdida es relativamente alta en cache pequeña y la función en línea decrece esta razón.  Para cache media el efecto no es muy notorio, pero para cache grande es despreciable.
 
 


Volver
5. Conclusiones.

Se analizaron los efectos de las optimizaciones de expansión de código en tiempo de compilación sobre el diseño de la cache de instrucciones.  Se mostró en primera instancia que tanto el método de instrucción en el lugar, la funciones en línea y las expansiones superescalares incrementaban de manera significativa la cantidad de líneas de código de un programa, por lo que el rendimiento de la cache se vería afectado, así como también el tamaño de esta debería ser significativo.  Luego se mostró el efecto de cada método sobre la cache.

Para cache pequeña, donde la razón de pedida es considerable, la colocación de instrucciones reduce el número de referencias perdidas, pero aumenta el uso de bytes de transferencia por cada pérdida de la cache. Para cache grande el efecto no es notorio.

El método de función en línea afecta el rendimiento de la cache. Para cache pequeña ayuda a mejorar la secuencialidad, reduciendo la razón de pérdida.  Para cache grande el efecto de reducir la razón de pérdida es pequeño.

El método superescalar también reduce la razón de pérdida.  Incrementa la secuencialidad de instrucciones de acceso.

Los tres métodos mejoran el rendimiento en cache pequeña.