• No se han encontrado resultados

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

C object # cycles

6.2.3.3 Anotación con análisis de código fuente

La técnica de anotación con análisis de código fuente es una técnica más rápida que la de sobrecarga de operadores. Para conseguir una baja sobrecarga en simulación es necesario realizar parte del análisis del tiempo del segmento en estático, antes de compilar el código a simular. Por ello la técnica de estimación comienza con el preprocesado del código mediante un analizador estático. De esta forma se puede obtener el coste de cada bloque básico de código. Posteriormente, durante la ejecución de cada bloque básico se toma su coste y se añade al del resto del segmento.

Al igual que en la técnica de sobrecarga de operadores, la técnica de estimación se basa en asignar un coste temporal a cada operador C y estimar el coste total de cada segmento sumando el de los operadores ejecutados en el segmento. El coste de cada operador se calcula de la misma forma que se mostró en la sección previa. Además, como antes, los efectos de las optimizaciones del compilador son difíciles de estimar desde el análisis del código fuente. Por ello también se mantiene la aplicación de un factor de ajuste para considerar la mejora introducida en el código por las optimizaciones.

Una vez obtenido el tiempo del segmento puede ser utilizado de la misma forma que con la técnica anterior para imbuir el comportamiento temporal a la simulación de SW. Por tanto, ambas técnicas pueden ser fácilmente intercambiables, obteniendo diferentes relaciones de precisión / velocidad, manteniendo el resto del sistema de modelado intacto.

Esta técnica explota la cualidad del código SW de que dentro de un mismo bloque básico se ejecutarán siempre todas sus instrucciones. Esto significa que podemos añadir por cada bloque básico el tiempo correspondiente a todas sus instrucciones internas, sin preocuparnos de cómo se ejecutará cada una. Así se modifica la granularidad del análisis, disminuyendo el detalle y aumentando la velocidad.

No obstante, este aumento de granularidad produce un aumento del error menor de lo que se podría pensar. Esto se debe a la propia definición de segmento dada anteriormente. Hemos de tener en cuenta que la técnica propuesta se basa en ejecutar un segmento de código íntegro, estimarlo, y por último introducir el tiempo correspondiente en la simulación. Por ello, el aumento de granularidad de operación a bloque básico no afecta a la integración de los tiempos de ejecución estimados en el modelado del sistema.

6.2.3.3.1Proceso de análisis

Para realizar la estimación basada en análisis de código fuente se han de realizar una serie de tareas (Figura 6-9). La secuencia completa es:

1. Pre-procesar el código

2. Anotar el código añadiendo la información por bloque básico 3. Compilar el código anotado

4. Ejecutar Código SW anotado Información de la plataforma Tarea SW Tarea SW Tarea SW Simulación Código SW de aplicación Preprocesador SystemC Modelo de OS … // Código if(flag){ tiempo+=t_bloque; … // Código void sem_open(){ wait(tiempo); os_sem_open(); } Anotación

El pre-procesado debe ser realizado en primer lugar para permitir la anotación del código. Esta anotación se realiza a través de un análisis gramatical del código. Para que este análisis sea posible y considere todos los elementos del código, es necesario trabajar con el código completo. Para ello, todas las cláusulas de pre-procesador, especialmente los “define”, deben ser aplicados al código, a fin de tener un código fuente gramaticalmente completo.

Una vez tenemos el código pre-procesado se realiza el análisis y se añade la información de tiempo por cada bloque básico. Posteriormente, el código puede ser compilado y ejecutado para obtener un modelo temporal del SW.

Para realizar la anotación del código debemos tener en cuenta el código que recibimos como entrada. Dividiremos este código en tres categorías:

 Cabeceras auxiliares y de sistema

 Código SW para modelar

 Código SW que no debe ser modelado.

Al pre-procesar el código antes de realizar el análisis, no solo recibimos el código fuente original. Además, el código contiene todas las cabeceras del sistema que hayan sido directa o indirectamente incluidas. En consecuencia, el código anotado preprocesado puede tener un tamaño muy superior al original. Este incremento de líneas de código conlleva un aumento considerable del tiempo requerido para realizar el análisis gramatical de forma innecesaria. Estas cabeceras no contienen código ejecutable, sino únicamente declaraciones de funciones y variables, por lo que no han de ser analizadas ni anotadas.

Además, no todo el código SW ha de ser modelado necesariamente. En un modelo de sistema podemos tener un código SW que no queremos que influya en el análisis del sistema, ya sea para facilitar el análisis o porque dicho componente no va a estar presente en el sistema real. Además, en determinadas ocasiones nos podemos encontrar con códigos que ya han sido estimados y anotados externamente, de modo que no deben ser re-anotados.

Para identificar qué parte del código ha de ser re-anotado se han creado dos cláusulas adicionales:

“#pragma START_ANALYSIS” y “#pragma END_ANALYSIS”

Con estas cláusulas, el análisis se limita al código delimitado por dichas cláusulas, haciendo más eficiente el análisis. El uso de “#pragma” hace que puedan ser definidas en el código original, mantenidas por el pre-procesador e ignoradas por el compilador. De esta forma solo afectan a la anotación del código.

6.2.3.3.2Estimación del tiempo de bloque básico

Para realizar la anotación se ha partido de una gramática C++. Dicha gramática está descrita para ser utilizada aplicando “flex” [Flex] y “byson” [Bison], para los análisis léxicos

y gramaticales. La gramática de la que se ha partido en este trabajo es la desarrollada por Ed Willink [Willink] como parte de su tesis. Esta gramática ha demostrado ser muy completa. Salvo alguna extensión requerida para dar soporte a elementos del tipo “__atribute__ ()” de C++, los ejemplos a los que se ha aplicado han demostrado que la gramática soporta completamente un código C++.

Aunque un análisis completo requeriría una gestión completa de los elementos de la gramática, en esta tesis nuestro objetivo es simplemente demostrar la viabilidad de la utilización de esta técnica mixta como técnica de estimación SW para exploración rápida de sistemas. Por ello, solo se ha utilizado una parte de la capacidad de la gramática. En concreto, nos hemos limitado a utilizar la gramática para detectar los bloques básicos, las operaciones de cómputo y las instrucciones de control.

El objetivo es realizar un análisis estático de cada bloque básico antes de la compilación. En este análisis se obtiene el coste estimado para cada bloque básico (Figura 6- 10). Una vez analizado el código, se reconstruye, introduciendo en el propio código unas instrucciones adicionales que se encargan de añadir el tiempo del bloque al tiempo total del segmento. Código original Análisis gramatical Identificación de bloques básicos Estimación de coste temporal Reconstrucción y anotación Código anotado Coste de operadores en plataforma destino Identificación de operadores

Figura 6-10: Estimación con análisis de código fuente

Dado que la técnica se basa en añadir instrucciones a cada bloque básico, lo primero que se ha añadido en la gramática es la capacidad de reconstruir el código de tal forma que todo bloque básico está encerrado entre llaves, aunque el original careciera de ellas.

A continuación, en toda regla gramatical de operación de cómputo se ha añadido una función que incrementa el coste del bloque en curso en el coste correspondiente a dicha operación. La modificación implementada no tiene en cuenta el tipo de los datos involucrados en la operación. Esta limitación puede ser resuelta mediante la generación de una tabla de variables. No obstante, para comprobar las posibilidades de la técnica de anotación con análisis de código fuente esta extensión no es estrictamente necesaria.

De la misma forma, en cada regla gramatical de control se ejecuta un incremento de la variable de coste del bloque básico. Las reglas gramaticales de control además indican el comienzo y el final de los bloques básicos, junto con las llaves de delimitación de bloque. El coste de las reglas se ha de considerar o bien en el bloque anterior, en el bloque siguiente o en ambos. En nuestro caso, las operaciones de control condicional han sido consideradas en el bloque siguiente, mientras que las de salto incondicional se consideran en el bloque anterior.

Esto se debe a que después de una operación de salto incondicional no existe un nuevo bloque. Cuando se ejecuta un “return”, “break” o “continue”, se salta a un bloque en una posición más o menos distante de dicha instrucción, de forma que no es posible anotar el coste del salto en el bloque destino.

6.2.3.3.3Anotación del código fuente

Con el tiempo de bloque básico estimado, y teniendo delimitado dicho bloque debemos introducir la información temporal en el código. En este punto debemos tener en cuenta la sobrecarga temporal que implican las operaciones que requiere la técnica de modelado.

En ejecuciones grandes, la sobrecarga introducida por el análisis estático no es excesivamente costosa. Esto se debe a que cada bloque básico se analiza una sola vez mientras que durante la simulación puede ejecutarse muchas veces. Por ello es crucial limitar la sobrecarga en simulación. Cualquier código adicional que añadamos para realizar la anotación supondrá un aumento del tiempo de simulación. Esto es especialmente importante si consideramos que habitualmente los bloques que más se repiten en un código suelen ser los que se encuentran dentro de varios lazos anidados, normalmente bloques pequeños. Al Debido a su pequeño tamaño, cualquier código adicional tiene gran repercusión en la ralentización de la simulación.

Para minimizar este problema la anotación de cada bloque básico se realiza añadiendo el coste del bloque básico a una variable global que se indica se mantenga en un registro del procesador. De esta forma la sobrecarga es realmente mínima. Esto se realiza añadiendo al buffer de regeneración del código anotado la información siguiente:

sprintf(buffer, “uc_segment_consumed_time+=%d;”, block_time);