• No se han encontrado resultados

Modelado de caches con la técnica de sobrecarga

6 Modelado del SW de aplicación 6.1 Modelado del subsistema SW

C object # cycles

6.3 Modelado de caches

6.3.1.1 Modelado de caches con la técnica de sobrecarga

La técnica de simulación de sobrecarga de operadores es una técnica de estimación completamente dinámica. Por ello, el modelado de cache debe realizarse utilizando únicamente información disponible durante la simulación. Para ello se ha implementado una técnica basada en un cálculo probabilístico del cambio de línea de cache en función de las operaciones ejecutadas. En primer lugar se evalúa si se produce un cambio de línea en cada operador ejecutado. A continuación, se calcula la probabilidad de que la nueva línea esté o no en cache.

El procedimiento aplicado en cada operación es el siguiente:

 Estimar la posición actual de la instrucción en la línea de cache.

 Estimar una vez ejecutada la instrucción si se ha alcanzado una nueva línea, y

la posición en dicha línea.

 En caso de alcanzar una nueva línea, identificar si la nueva línea está en cache o no.

La posición actual en la línea se estima la primera vez que se ejecute la línea, a partir de la posición previa y la instrucción ejecutada. Una vez ejecutada, esta información se guarda con la información de línea. Esto es especialmente útil para vueltas atrás en lazos, ya que al empezar sabemos en que posición estamos si sabemos la de la instrucción anterior. Sin embargo, al volver hacia atrás no tenemos ninguna información, ya que no sabemos el tamaño del salto.

Conociendo la posición en la línea, se estima que se alcanza una nueva línea en función del tipo de operación ejecutada:

 Durante la ejecución de instrucciones consecutivas, alcanzaremos una nueva línea cuando hayamos ejecutado tantas instrucciones como el taño de línea.

 Si cambia el proceso en ejecución, se alcanzará una nueva línea, ya que se cambiará la zona de memoria

 Si se produce un salto, habrá una nueva línea con una probabilidad “p”, que se

Para instrucciones consecutivas, la nueva instrucción está en la posición de la instrucción anterior más el tamaño de la instrucción. Si la posición final está fuera de la línea actual significa que se han producido uno o varios cambios de línea. Por tanto, se accede a la cache a comprobar si estos han producido un fallo de cache o no. Esto se aplica para:

 Operaciones de cómputo

 “if”, “for”, “while” con condición verdadera

Para saltos hacia delante, primero sumaremos el tamaño de la instrucción correspondiente a la posición anterior, como si fuera una instrucción consecutiva. A continuación, dado que durante la ejecución dinámica no sabemos cual es el tamaño del salto no podemos saber exactamente cual es la dirección de destino. Así, se calcula de forma probabilística, sabiendo que tenemos la misma probabilidad de ir a cualquier posición dentro de una línea. Además, se supone siempre que hemos saltado a otra línea, lo que implica una nueva comprobación en la cache. Esto se aplica para:

 “if”, “for”, “while” con condición falsa

 Saltos a función, retorno de función

 “break”, “return”

Por último, para identificar si la nueva línea produce un fallo de cache, utilizaremos un modelo de cache. En este modelo se almacena la información de las líneas presentes en la cache en cada momento. El modelo contiene una lista de líneas cuyo tamaño es función del tamaño de la cache a modelar. Por cada línea se guarda una estructura de datos que contiene un identificador de línea de información de su último uso, que es utilizada por el mecanismo de reemplazo.

Cada línea de cache se identifica mediante una cadena de caracteres. En las operaciones de control, está cadena se obtiene directamente del código fuente. En el resto de operadores, la cadena está compuesta por la de la operación de control anterior más un identificador del número de líneas ejecutadas desde dicha instrucción de control.

En cada operación de control se obtiene el identificador de línea de cache en función del número de línea en el archivo C. Para ello se utilizan las macros presentadas en la sección 6.1.5. Por ejemplo, la macro correspondiente a la claúsula “if” es:

#define if(cond) if(add_branch_time(__LINE__,__FILE__, basic_type(cond)))

Con la información de línea y archivo (“__LINE__” y “__FILE__”), la instrucción de control es identificada. La línea correspondiente de cache se llamara “archivoX_lineaY”. De esta forma, cuando volvamos a ejecutar la instrucción y, bastará comprobar si la línea “archivoX_lineaY” está en la cache o no. Las líneas consecutivas se identifican mediante un prefijo que indica el valor de la condición y el número de líneas desde la instrucción de

control. De esta forma una línea se podría llamar “archivoX_lineaY_F_5” si es la quinta línea ejecutada y la condición fue falsa.

Con esta información, cada vez que se cambia de línea se chequea el modelo de cache. Si la línea está en la lista de la cache, continuamos, y si no, se modela un fallo de cache. En el modelado del fallo se carga la nueva línea en la cache. Para ello se comprueba si hay hueco en cache, expulsando una línea de la cache, según un mecanismo de reemplazo LRU. Además, se realiza un acceso al bus del tamaño de la línea, acceso durante el cual la ejecución de código se paraliza.

6.3.2 Modelado de caches con técnicas de anotación por

preprocesado

El modelado de la cache de instrucciones implementado para técnicas de anotación estática es más sencillo que su equivalente para sobrecarga de operadores. Esto se debe que las técnicas de anotación se basan e una combinación de anotación estática y modelado dinámico. El procedimiento de anotación estática permite conocer a priori el tamaño de cada bloque básico. Esto significa que es posible asociarle a cada uno una posición de memoria en tiempo de pre-procesado. Por tanto no es necesario obtener información del camino de ejecución para estimar si se ha pasado por un bloque o no. La simplicidad del modelado reduce la sobrecarga en el tiempo de simulación sin reducir la precisión del modelo.

La técnica de modelado de caches para anotación estática está dividida en dos partes: una realizada mediante un análisis estático y otra ejecutada durante la simulación. Durante el análisis estático se estima el tamaño de cada bloque básico y por tanto el número de líneas de cache que requiere. Durante la simulación se dispone de un modelo de cache que emula el comportamiento de una cache real. Este modelo ha sido codificado por Juan Castillo.

La técnica implementada ha sido diseñada como una evolución a los modelos utilizados en simuladores de instrucciones (ISS). La sobrecarga de estos modelos en una simulación nativa no es asumible. Por cada instrucción nativa ejecutada deberíamos realizar una búsqueda en cache, y provocar un fallo de cache y una expulsión en los casos necesarios. Para evitar esta sobrecarga, la técnica implementada modifica la técnica de los ISSs, aumentando la velocidad a cambio de una relativa reducción de la precisión.

En primer lugar, en vez de modelar cada línea de cache por separado se agrupan todas las líneas de cada bloque básico. Esto evita que se realice una gestión de cache por línea y lo reduce a una por bloque básico. Como consecuencia cada bloque se corresponde con una entrada de cache, que tiene su dirección y su tamaño en lugar de “n” entradas, una por línea. Esto implica que cada vez que se necesita expulsar una línea de la cache, en realidad se expulsan todas las líneas del bloque juntas. Hemos de considerar que normalmente cada salto no es automáticamente seguido de otro salto, viene seguido de unas cuantas instrucciones secuenciales antes del próximo salto. Por tanto la probabilidad de que si se expulsa una línea de cache también se vaya a expulsar a las siguientes es bastante alta. Esto limita el error introducido por la agrupación de las líneas del bloque.

En segundo lugar es crucial eliminar la búsqueda para saber si las líneas del bloque están en cache. Para ello se ha creado una estructura que contiene toda la información de cache del bloque: el tamaño, la posición y si está o no en cache. Aprovechando que cada bloque de código rodeado por llaves genera un nuevo ámbito de visibilidad, se crea una instancia de dicha clase por bloque básico. Dicha instancia se declara como estática, de forma que se crea durante la primera ejecución del bloque y se mantiene durante las demás ejecuciones. De esta forma no es necesario ir a la cache a comprobar si la línea está cargada o no. Es suficiente con comprobar la variable de la estructura que indica si la información está en cache o no. Cuando no está en cache se ejecuta el código de modelado de un fallo de cache. Este código carga la estructura en el modelo de cache y produce una transferencia por el bus. Cuando un bloque ha de ser expulsado de la cache basta con quitarlo de la lista de la cache y cambiar su variable para indicar que ya no está en la cache.

El código que introduce la anotación para el modelado de la cache es el siguiente:

fprintf(file,"static struct ic_cache_block_t *block_%d=init_block(%d,%d);",

parsing_struct.block_id, parsing_struct.instr_count, parsing_struct.init_line);

fprintf(file,"if(!block_%d->m_in_cache){icache_insert_block(block_%d);}", parsing_struct.block_id, parsing_struct.block_id,

parsing_struct.current_set);

De esta forma, la sobrecarga en caso de que la línea ya esté en cache se reduce a un “if” con condición falsa. En caso de que haya que modelar el miss la sobrecarga es mayor, pero dado que la sobrecarga produce una transferencia por el bus, no es preocupante. Una transmisión por el bus implica una sobrecarga bastante considerable, ya que implica una simulación del comportamiento de varios componentes HW del sistema.

Para reducir esta última sobrecarga se permite agrupar los fallos de cache de forma que en lugar de modelar una transferencia de bus por miss se realice una por cada varios mises, con el tamaño correspondiente a todos ellos.

6.3.3 Modelado de la cache de datos