Sunteți pe pagina 1din 28

Grupo 10

Tabla de Smbolos y
Anlisis Semntico de
Java y GCC
200320831 Edwin Alexander Juan Vallejos
2004-12874Julio Enrique Vivas Castillo

Introduccin
La fase de anlisis semntico obtiene su nombre por requerir informacin relativa al
significado del lenguaje, que est fuera del alcance de la representatividad de las
gramticas libres de contexto y los principales algoritmos existentes de anlisis; es por
ello por lo que se dice que captura la parte de la fase de anlisis considerada fuera del
mbito de la sintaxis.
As, la mayora de los compiladores utilizan una gramtica libre de contexto para
describir la sintaxis del lenguaje y una fase de anlisis semntico posterior para
restringir las sentencias que semnticamente no pertenecen al lenguaje.
1.2 Objetivos de la Tabla de Smbolos (TS)
Las Tablas de Smbolos (en adelante TS) son estructuras de datos que
almacenan toda la informacin de los identificadores del lenguaje
fuente.
Las misiones principales de la TS en el proceso de traduccin son:
Colaborar con las comprobaciones semnticas.
Facilitar ayuda a la generacin de cdigo.
La informacin almacenada en la TS depende directamente del tipo
de elementos del
lenguaje especfico a procesar y de las
caractersticas de dicho lenguaje. Habitualmente los elementos del
lenguaje que requieren el uso de la TS son los distintos tipos de
identificadores del lenguaje (nombres de variables, de objetos, de
funciones, de etiquetas, de clases, de mtodos, etc.).
La informacin relativa a un elemento del lenguaje se almacena en
los denominados
atributos de dicho elemento. Estos atributos
tambin varan de un tipo de lenguaje a otro y de un elemento a otro.
As ejemplos de atributos tales como nombre, tipo, direccin relativa
en tiempo de ejecucin, dimensiones de los arrays, nmero y tipo de
los parmetros de procedimientos, funciones y mtodos, tipos de
acceso a los elementos de una clase (public, private, protected),
etc. se recogen y se guardan en la TS.

Los atributos se obtienen unas veces directamente del anlisis del


programa fuente, es decir, estn en forma explcita (por ejemplo en
la seccin de declaraciones del programa fuente) y otras veces los
atributos se obtienen de forma implcita a travs del contexto en el
que aparece el elemento en el programa fuente.
En el proceso de compilacin se accede a la TS en unos determinados
puntos que dependen inicialmente del nmero y la naturaleza de las
pasadas del procesador de lenguaje y del propio lenguaje fuente a
procesar.
En los traductores y compiladores, las TS existen nicamente en
tiempo de compilacin, aunque en depuracin (debug) pueden estar
almacenadas en disco y dar informacin en tiempo de ejecucin para
identificar los smbolos que se deseen inspeccionar.
En los intrpretes contienen informacin en tiempo de ejecucin.
Las palabras reservadas no estn en la TS.
2 Contenidos de la TS
Una TS se puede definir como una estructura de datos organizada en
funcin de los identificadores que aparecen en el programa fuente.
Aunque su nombre parece indicar una estructuracin en una tabla no
es necesariamente sta la nica estructura de datos utilizada,
tambin se emplean rboles, pilas, etc.
Lo que la estructura debe permitir es establecer un homomorfismo
entre los mbitos de utilizacin de los smbolos en el programa
fuente y el modo en que aparecen en las sucesivas bsquedas en la
tabla. Para ello debe manejar diferentes contextos de bsqueda que
imiten los diferentes tipos de bloques del lenguaje fuente que se
compila.
Los smbolos se guardan en la tabla con su nombre y una serie de
atributos opcionales que dependern del lenguaje y de los objetivos
del procesador. Este conjunto de atributos almacenados en la TS para
un smbolo determinado se define como registro de la tabla de
smbolos (symbol-table record).
Una forma de organizacin simple es imaginar la TS como una tabla
con una serie de filas, cada fila contiene una lista de atributos que
estn asociados a un identificador.

Las clases de atributos que aparecen en una TS dependen de la


naturaleza del lenguaje de programacin para el cual est escrito el
compilador. Por ejemplo, un lenguaje de programacin puede no
tener tipos, entonces el atributo tipo no necesita aparecer en la
tabla.
Operaciones con la TS
Las dos operaciones que se llevan a cabo generalmente en las TS son
las insercin y la bsqueda. La forma en que se realizan estas dos
operaciones difiere levemente segn
que las declaraciones del
lenguaje a compilar sean explcitas o implcitas.
Otras operaciones son activar (set) y desactivar (reset) las tablas de
los identificadores locales o automticos.
Lenguajes con declaraciones explcitas obligatorias
Las dos operaciones se realizan en puntos concretos del compilador.
Es obvio que la operacin de insercin se realiza cuando se procesa
una declaracin, ya que una declaracin es un descriptor inicial de
los atributos de un identificador del programa fuente.
Si la TS est ordenada, es decir los nombres de las variables estn
por orden alfabtico, entonces la operacin de insercin llama a un
procedimiento de bsqueda para encontrar el lugar donde colocar los
atributos del identificador a insertar. En tales casos la insercin lleva
tanto tiempo como la bsqueda.
Si la TS no est ordenada, la operacin de insercin se simplifica
mucho, aunque tambin se necesita un procedimiento de colocacin.
Sin embargo la operacin de bsqueda se complica ya que debe
examinar toda la tabla.
La operacin de bsqueda se lleva a cabo en todas las referencias de
los identificadores, excepto en su declaracin. La informacin que se
busca (por ejemplo: tipo, direccin en tiempo de ejecucin, etc.) se
usa en la verificacin semntica y en la generacin de cdigo.
En las operaciones de bsqueda se detectan los identificadores que
no han sido declarados previamente emitindose el mensaje de error
correspondiente. En las operaciones de insercin se detectan los
identificadores que ya han sido previamente
declarados,
emitindose, a su vez, el correspondiente mensaje de error.
3.1.2 Lenguajes con declaraciones implcitas de los
identificadores

Las operaciones insercin y bsqueda estn estrechamente unidas.


Cualquier identificador que aparezca en el texto fuente deber ser
tratado como una referencia inicial, ya que no hay manera de saber a
priori si los atributos del identificador ya han sido almacenados en la
TS.
As, cualquier identificador que aparezca en el texto fuente llama al
procedimiento de bsqueda, que a su vez, llamar al procedimiento
de insercin si el nombre del identificador no se encuentra en la TS.
Todos los atributos asociados con un
identificador declarado
implcitamente se deducen del papel desempeado por el
identificador en el programa.

Representacin OO de smbolos y tipos en compiladores


En los sistemas orientados a objetos aparecen nuevos problemas a la
hora de implementar la tabla de smbolos que se estudian en los
siguientes apartados.
En estos momentos hay dos lenguajes implicados que pueden ser
orientados a objetos:
el lenguaje a compilar y el lenguaje de
implementacin del compilador, en este caso el
lenguaje de
implementacin de la propia tabla de smbolos (ver Figura 4-23).

A continuacin se considera que el lenguaje de implementacin es


orientado a objetos, esto proporciona beneficios de implementacin
de la TS. Todo lo estudiado en este apartado es aplicable para el caso
de lenguajes fuente estructurados (tambin orientados a objetos).
Queda fuera del alcance de este documento las TS de lenguajes OO
implementados en lenguajes estructurados convencionales.
La doble jerarqua smbolo-tipo
Cuando estamos compilando un lenguaje orientado a objetos (OO), el
esquema de tabla de smbolos debe representar en cierto modo el
modelo del lenguaje fuente. En un esquema orientado a objetos
tenemos una doble jerarqua:

Smbolos, que son todos aquellos identificadores que representan


variables locales, atributos, campos de un registro, parmetros de un
mtodo, etc.
Tipos, que son todos los identificadores que representan tipos del
lenguaje, entendidos aqu los tipos en un sentido amplio, esto es
comprendiendo clases
cuando se habla de lenguajes OO. (Ver
[ORTIN04])
No es objetivo definir aqu lo que es un tipo y simplemente nos
quedamos con el concepto de que en OO un tipo determina el tipo de
mensajes que puede admitir un smbolo. Cuando dos smbolos son el
mismo tipo se entiende que admite exactamente
los mismos
mensajes.
As pues el esquema de la tabla de smbolos consta de dos tablas:
Tabla de smbolos, que sirve para guardar una referencia a todos
los smbolos que pueden ser accedidos desde el entorno actual.

Tabla de tipos, que es la que guarda la estructura de tipos


definida en el programa fuente: las clases, las interfaces, los tipos
bsicos, etc.

La jerarqua de smbolos

La jerarqua de smbolos describe todos los posibles smbolos que


pueden aparecer en el cdigo fuente.
A partir de la tabla de smbolos se accede a estos smbolos
(representado aqu por una agregacin, pero que en general ser una
acceso algo ms complejo). Cada smbolo tiene tipo y debe poderse
acceder a dicho tipo a travs del smbolo.
El mtodo de acceso puede ser representado como en este caso por
una agregacin o tambin podra estar representado por una relacin
cualificada por una cadena que identificara el nombre del smbolo.
Este tipo de representacin puede ser implementada por medio de
una tabla de acceso hash, de manera que, como en un diccionario,
se localizase la entrada del smbolo por su nombre.
Este tipo de implementacin est recogida en la mayor parte de las
bibliotecas de contenedores de los lenguajes orientados a objetos,
map, hashmap, hashtable, etc. son
algunos de los nombres
habituales que recibe este tipo de contenedor dependiendo de los
leguajes de implementacin.

SymbolTable
+ insert() : void
+ search() : Symbol
+ set() :
void
+ reset()name
: void

Symbol
- name: string
- address: string

+has

+ getType() :
+ getName() : string
+ getAddress() : string

LocalVar

type

Field

At

+ getType() : Type
+ getName() : string
+ getAddress() : string

+ getType() :
+ getName() : string
+ getAddress()
: string
Parameter
+ getType() : Type
+ getName() : string
+ getAddress()
: string

+ getType(
+ getName
+ getAddre
Property

+ getType() :
+ getName() : string
+ getAddress() : string

Figura 4-26. Jerarqua de smbolos

Obsrvese que en la Figura 4-26 se ha hecho un esquema muy genrico de tabla de smbolos que prev que cada uno de los posibles sm

SymbolTable
+ insert() : void
+ search() : Symbol
+ set() :
void
+ reset()name
: void

GenericSymbol
- name: string
- address: string

+has

Type
type

+ getName() : string
+ getType() : Type
+ getAddress() : string

Figura 4-27. Versin simplificada de la jerarqua de smbolos

La jerarqua de tipos
En paralelo a la jerarqua de smbolos tenemos una jerarqua de tipos
donde pueden estar todos los tipos bsicos del lenguaje y los tipos
definidos por el usuario (con typedef, clases, etc.).
Un esquema bastante general se puede ver en la Figura 4-28.
En este caso se tiene una tabla de tipos (TypeTable) que tiene
guardados todos los tipos que aparecen durante la compilacin del
cdigo fuente.
Al comenzar dicha compilacin estarn nicamente los tipos bsicos
del sistema (los BuiltinType). En el momento en que el cdigo
comienza a describir clases, arrays, mtodos, interfaces, etc. Va
creando los nuevos tipos y metindolos en la tabla.
Hay que hacer notar que la clase BuiltinType representa a cada uno
de los tipos bsicos.

TypeTable

SymbolTable

+ insert() : void
+ search() : Type

name

name

Symbol

+has

Type

type

+field
+atribute

+implements

BuiltInTypeArrayTypeClassTypeInterfaeTypeFunctionTypeRecordType PointerType
+extends *
+extends *

+typename

name

+contains

*
+contains

name

name

position

+type

+contains+method
+contains
+method

Atributes

Atributes

- access: int
- static: boolean

- access: int
- static: boolean

+ getAccess() : int
+ getStatic() : boolean

+ getAccess() : int
+ getStatic() : boolean

+return value
+contains

Figura 4-28. Jerarqua y tabla de tipos

Funcionamiento y acceso a la tabla de smbolos


Sea un lenguaje que tiene los tipos bsicos void, int y double y una
estructura en clases similar al Java sin interfaces y con herencia
simple.
Se trata de hacer un compilador de este tipo de cdigo y, en este
caso, hacer la tabla de smbolos de dicho compilador y demostrar
que es suficiente para todo el proceso de compilacin.
A partir de un cdigo se ir siguiendo toda la formacin de la tabla de
smbolos y se podr comprobar cmo funciona el modelo.
Ejemplo:
Sea el siguiente cdigo de ejemplo de un lenguaje sencillo que slo
permite dos tipos bsicos (int y double) y un cdigo principal en que
se pueden hacer lecturas, escrituras y expresiones:

1: int i;

2:
3:
4:
5:
6:
7:
8:
9:

float f;
main ()
{
int f;
read f;
i = f;
write i + 2;
}

Figura 4-42. AST del cdigo de ejemplo

Figura 4-45. Insertar una declaracin y referenciar un identificador

Figura 4-48. rbol AST decorado resultante de la primera pasada del visitor

Representacin intermedia RTL


La mayor parte del trabajo realizado por el compilador se lleva a cabo
Figura
Estructura AST-TS
resultante unallamada
vez terminada
la visita de
identificacin
sobre
una4-46.
representacin
intermedia
lenguaje
de
transferencia de registros. En este lenguaje, las instrucciones que van
a la salida se describen, una a una, en una forma algebraica que
describe lo que la instruccin realiza.
RTL est inspirado en las listas de Lisp. Este tiene una forma interna,
hecha de estructuras que apuntan a otras estructuras y una forma
textual que es utilizada para la descripcin de la mquina y las
salidas de impresin al momento de depurar un programa. La forma
textual usa parntesis anidados para indicar los apuntadores a la
forma interna.

RTL para expresiones aritmticas


A menos que se especifique lo contrario, todas las expresiones
aritmticas deben ser vlidas para el modo m. Un operando es vlido
para un modo m si tiene un modo m, o si es un const_int o
const_double y m es un modo de la clase MODE_INT.
(plus:m x y)
(ss_plus:m x y)
(us_plus:m x y)
Estas tres expresiones, todas representan la suma de valores
representados por x y y operados en el modo m. Difieren en si
aceptan la operacin con o sin signo.
(minus:m x y)
(ss_minus:m x y)
(us_minus:m x y)
Estas operaciones representan la resta de y a x, operadas en el modo
m. Similar a la adicin anterior.

Manejo de la tabla de smbolos


Cuando cpplib encuentra un identificador, genera un cdigo hash
para ste y lo guarda en la tabla hash. Por identificador nos
referimos a tokens del tipo CPP_NAME; esto incluye identificadores en
el sentido usual de C, adems palabras reservadas, nombres de
directivas, nombre de macros y as sucesivamente. Por ejemplo,
pragma, int, foo y __GNUC__ son todos identificadores y se les calcula
el cdigo hash al momento de ser analizados lxicamente.

Cada nodo en la tabla hash contiene informacin varia sobre el


identificador que representa. Por ejemplo, su longitud y tipo. En
cualquier momento, cada identificador cae en exactamente una de
las tres categoras:

Macros
Estos han sido declarados como macros, ya sea por medio de la lnea
de comandos o por medio de #define. Unos pocos, tales como
__TIME__ son parte del compilador (built-in) e ingresados en la tabla
hash durante la inicializacin. El nodo hash para un macro normal
apunta a una estructura con ms informacin acerca del macro, tal
como, si es de tipo funcin, cuantos argumentos toma, y su
expansin. Los macros que son parte del compilador estn marcados
como especiales, y a cambio contienen una enumeracin indicando
de los macros built-in es.

Aserciones (asserts)
Las Aserciones o afirmaciones se encuentran en un espacio de
nombres separado al de los macros. Para asegurar esto, cpp de hecho
agrega un caracter # antes de hacer el hash y meterlo en la tabla
hash. Un nodo de asercin apunta a una cadena de respuestas a la
asercin.

Void
Cualquier otro identificador cae en esta categora un identificador
que no es un macro, o un macro que desde entonces ha sido
indefinido con #undef. Cuando se preprocesa C++, esta categora
tambin incluye operadores con nombre (named operadors) tal como
xor. En las expresiones estos se comportan como los operadores que
representan, pero en contextos donde la estructura del token es
importante se escriben de forma diferente. Esta distincin en la forma
de escribirlos es relevante cuando son operandos los operadores
macro # y ##. Los nodos hash para los operadores con nombre estn
marcados (flagged), ambos para poder distinguir la forma de escritura
(spelling) y para prevenir que stos sean definidos como macros.

Los identificadores iguales comparten el mismo nodo hash. Desde


que cada token identificador, despus del anlisis lxico, contiene un
puntero a su nodo hash, ste es usado para proveer una consulta
rpida de informacin diversa. Por ejemplo, cuando se analiza
sintcticamente una sentencia #define, CPP marca cada nodo hash

del identificador de argumento con el ndice de ese argumento. Esto


duplica la validacin de argumentos en una operacin O(1) para cada
argumento. De forma similar, para cada identificador en la expansin
del macro, se busca en la estructura para saber si es un argumento,
y que argumento es, tambin es una operacin O(1). Adems, cada
nombre de directiva, tal con endif, tiene una enumeracin de
directiva asociada almacenada en su nodo hash, as que la bsqueda
de la directiva tambin es una operacin O(1).

Examinando la tabla de smbolos


Los ejecutables pueden contener una tabla de smbolos. Esta tabla guarda la localizacin
de funciones y variables por nombre, y puede ser mostrada con la ayuda del comando
nm:
$ nm a.out
08049f20 d _DYNAMIC
08049ff4 d _GLOBAL_OFFSET_TABLE_
080484cc R _IO_stdin_used
w _Jv_RegisterClasses
08049f10 d __CTOR_END__
08049f0c d __CTOR_LIST__
08049f18 D __DTOR_END__
08049f14 d __DTOR_LIST__
080484d8 r __FRAME_END__
08049f1c d __JCR_END__
08049f1c d __JCR_LIST__
0804a014 A __bss_start
0804a00c D __data_start
08048480 t __do_global_ctors_aux
08048340 t __do_global_dtors_aux
0804a010 D __dso_handle
w __gmon_start__
0804847a T __i686.get_pc_thunk.bx
08049f0c d __init_array_end
08049f0c d __init_array_start

08048410 T __libc_csu_fini
08048420 T __libc_csu_init
U __libc_start_main@@GLIBC_2.0
0804a014 A _edata
0804a01c A _end
080484ac T _fini
080484c8 R _fp_hw
08048298 T _init
08048310 T _start
0804a014 b completed.6625
0804a00c W data_start
0804a018 b dtor_idx.6627
080483a0 t frame_dummy
080483c4 T main
U printf@@GLIBC_2.0

Entre los contenidos de la tabla de smbolos, la salida muestra que el


inicio de la funcin main tiene el offset 080483c4. La mayora de
smbolos son para uso interno por el compilador y el sistema
operativo. Una letra T en la segunda columna indica una funcin que
est definida en el archivo objeto, mientras que una U indica que una
funcin est indefinida (y debera ser resuelta por medio de enlace
contra otro archivo objeto.) El uso frecuente para el comando nm es
verificar que una librera contiene la definicin de una funcin
especfica, verificando que aparezca una T en la segunda columna
seguido del nombre de la funcin.

Optimizacin de cdigo con GCC


GCC es un compilador de optimizacin. Provee de un amplio rango de
opciones que intentan incrementar la velocidad, o reducir el tamao,
de los archivos ejecutables que genera.
La optimizacin es un proceso complejo. Para cada comando de alto
nivel en el cdigo fuente generalmente existen muchas
combinaciones de instrucciones de mquina que pueden ser
utilizadas para lograr el resultado final apropiado. El compilador debe

considerar estas posibilidades y escoger entre ellas.


En general, cdigo diferente debe ser generado para diferentes
procesadores, desde que casi cada uno utiliza lenguajes de
ensamblador y mquina incompatibles. Cada tipo de procesador
tambin tiene sus propias caractersticas, por ejemplo, algunos CPU's
proveen un nmero de registros ms grande para almacenar
resultados intermedios de clculos, mientras que otros deben
almacenar y recuperar resultados intermedios de la memoria. El
cdigo apropiado debe ser generado en cada caso.
Adems, diferentes cantidades de tiempo se requieren para diferentes
instrucciones, dependiendo de como sean ordenadas. GCC toma en
cuenta todos estos factores en cuenta y trata de producir el
ejecutable ms rpido para un sistema dado cuando se compila con
optimizacin.

Optimizacin a nivel de cdigo


La primer forma de optimizacin de cdigo usada por GCC ocurre a
nivel de cdigo fuente, y no requiere conocimiento alguno sobre las
instrucciones de mquina. Hay muchas tcnicas sobre optimizacin a
nivel de cdigo fuente pero trataremos las ms comunes: eliminacin
de subexpresiones comunes y funciones en-lnea.

Eliminacin de subexpresiones comunes


Uno de los mtodos de optimizacin a nivel de cdigo que es fcil de
entender envuelve el computo de una expresin en el cdigo fuente
con pocas instrucciones, a modo de reutilizar resultados previamente
calculados. Por ejemplo, la siguiente asignacin:
x = cos(v)*(1+sin(u/2)) + sin(w)*(1-sin(u/2))

puede ser reescrita con una variable t para eliminar una evaluacin
extra el trmino sin(u/2)
t = sin(u/2)
x = cos(v)*(1+t) + sin(w)*(1-t)

Esta forma de reescritura es llamada eliminacin de subexpresiones


comunes (o common subexpression elimination, CSE) y es realizada
automticamente cuando la optimizacin est habilitada. La
eliminacin de subexpresiones comunes es poderosa, porque puede
simultneamente incrementar la velocidad y reducir el tamao del
cdigo.

Funciones en-lnea
Otro tipo de optimizacin a nivel de cdigo es el llamado reemplazo
de funciones en-lnea, el cual incrementa la eficiencia de funciones
llamadas frecuentemente.
Ya sea que una funcin es usada, cierta cantidad de tiempo extra es
requerido para que el CPU pueda realizar la llamada: ste debe
almacenar los argumentos de la funcin en los registros apropiados y
localidades de memoria, saltar al inicio de la funcin, comenzar a
ejecutar el cdigo, en entonces retornar al punto original de ejecucin
cuando la llamada a la funcin est completa. Este trabajo adicional
se conoce como gasto de llamada a funcin (o, function-call
overhead). Este tipo de optimizacin elimina este gasto adicional al
reemplazar llamadas a auna funcin por el cdigo de la funcin en s
(tambin conocido como, colocar el cdigo en-lnea.)
En muchos casos, el costo de llamar a una funcin es una parte
considerable del tiempo total de ejecucin de un programa. Puede
volverse significativo solo cuando hay funciones que contienen un
nmero relativamente bajo de instrucciones, y estas funciones
cuentan por una fraccin sustancial del tiempo de ejecucin, en este
caso el costo se vuelve una gran proporcin del tiempo total de
ejecucin.
Poner en lnea siempre es favorable si hay un solo punto de
invocacin de una funcin. Tambin es incondicionalmente mejor si la
invocacin de una funcin requiere ms instrucciones (memoria) que
mover el cuerpo de la funcin en-lnea. Esta optimizacin tambin
pude facilitar optimizaciones ms precisas, tales como eliminacin de
subexpresiones comunes, por medio de la unin de varias funciones
separadas en una funcin ms grande.
La siguiente funcin sq(x) es un ejemplo tpico de una funcin que
podra beneficiarse de la optimizacin en-lnea. sta computa x^2, el
cuadrado de su argumento x:
double sq (double x)
{
return x * x;
}

Esta funcin es pequea, as que el costo de llamarla es comparable


con el tiempo que toma ejecutar una multiplicacin sencilla calculada
por la misma funcin. Si esta funcin s usada dentro de un bucle, tal
como el que aparece a continuacin, entonces la llamada a funcin
podra volverse substancial:
for (i = 0; i < 1000000; i++)
{
sum += sq (i + 0.5);
}

Utilizando este tipo de optimizacin reemplaza el cuerpo del bucle

con el cuerpo de la funcin, resultando en el siguiente cdigo:


for (i = 0; i < 1000000; i++)
{
double t = (i + 0.5); /* variable temporal */
sum += t * t;
}

Eliminando la llamada a la funcin y realizando la multiplicacin enlnea permite que el bucle se ejecute a la mxima eficiencia.
GCC selecciona funciones para optimizar por este mtodo usando un
nmero pruebas heursticas, tales como examinar que la funcin
candidata sea pequea. Como optimizacin que es, se lleva a cabo
solo dentro de cada archivo objeto. La palabra reservada inline puede
ser usada para pedir que explcitamente una funcin especfica sea
reemplazada por su contenido, incluyendo su uso en otros archivos

Scheduling
ste es el nivel ms bajo de optimizacin, en el cual el compilador
tenermina la mejor forma de ordenar instrucciones individuales. La
mayora de CPUs permiten que una o ms instrucciones nuevas
comiencen a ejecutarse antes que otras hayan finalizado. Muchos
CPUs tambin soportan pipelining, donde mltiples instrucciones se
ejecutan en paralelo en el mismo CPU.
Cuando esta optimizacin est habilitada, las instrucciones deben ser
ordenadas de tal manera que permita que los resultados estn
disponibles para ser utilizados por instrucciones posteriores en el
momento indicado, y para que permitan una ejecucin mxima en
paralelo. sta optimizacin mejora la velocidad de un ejecutable sin
incrementar su tamao, pero requiere de memoria adicional y ms
tiempo en el proceso de compilacin (debido a su complejidad).

Niveles de optimizacin
A modo de controlar el tiempo de compilacin y la utilizacin de
memoria por el compilador, y el costo-beneficio entre velocidad y
espacio para el ejecutable resultante, GCC provee un rango de niveles
de optimizacin general, numerados de 0 a 3, adems de las
opciones individuales para tipos especficos de optimizacin.
-O0 u omitir la opcin -O (default)
En este niel de optimizacin GCC no realiza ninguna optimizacin y
compila el cdigo fuente de la forma ms directa posible. Cada
comando en el cdigo fuente es convertido directamente a las

instrucciones correspondientes en el archivo ejecutable, sin


reordenamiento. Esta es la mejor opcin a usar cuando de depura un
programa y es la default si no se indica nivel de optimizacin alguno.
-O1 o -O
Este nivel habilita la mayora de formas comunes de optimizacin que
no implican operaciones relacionadas con costo-beneficio de
velocidad-espacio. Con esta opcin los ejecutables resultantes
deberan ser ms pequeos y rpidos que con -O0. Las
optimizaciones ms costosas, tales como paralelismo en la ejecucin
de instrucciones no se utilizan en este nivel. Compilar con la opcin
-O1 puede tomar menos tiempo que compilar con -O0, debido a las
cantidades reducidas de datos que necesitan ser procesados despus
de la optimizaciones sencillas realizadas.
-O2
Esta opcin habilita ms optimizaciones, adems de las ya utilizadas
por -O1. Estas optimizaciones adicionales incluyen el paralelismo en
la ejecucin de instrucciones. Solo las optimizaciones que no
requieren clculo de costo-beneficio entre espacio-velocidad son
utilizadas para que el ejecutable no aumente en tamao. El
compilador tomar ms tiempo para compilar programas y requerir
ms memoria que si se utilizara -O1. Esta opcin es generalmente la
mejor eleccin para la implantacin de un programa, porque provee
la optimizacin mxima sin incrementar el tamao del ejecutable. Es
el nivel de optimizacin por default para los lanzamientos de
paquetes GNU.
-O3
Esta opcin habilita optimizaciones ms costosas, tales como
funciones en-lnea, adems de las optimizaciones de los niveles ms
bajos -O2 y -O1. La optimizacin -O3 puede incrementar la velocidad
del ejecutable resultante, pero puede tambin incrementar su
tamao. Bajo algunas circunstancias donde estas optimizaciones no
son favorables, esta opcin podra hacer que el programa sea ms
lento.
-Os
Esta opcin selecciona optimizaciones que reducen el tamao de un
ejecutable. El objetivo de esta opcin es producir el ejecutable ms
pequeo posible, para sistemas restringidos en cuanto a memoria o
espacio en disco. En Algunos casos un ejecutable ms pequeo
tambin se ejecutar ms rpido, debido a un mejor uso de cach.

Es importante recordar que un beneficio de la optimizacin en los


niveles ms altos debe ser evaluada junto con el costo. El costo de
optimizar incluye mayor complejidad en el momento de depurar, y
requerimientos mayores de tiempo y mejora durante la compilacin.
Para la mayora de propsitos est bien si se utiliza -O0 para depurar,
y -O2 para desarrollo e implantacin.

Generacin de cdigo basada en la arquitectura de la mquina


objetivo
Se muestra el cdigo en ensamblador generado al ejecutar gcc con la
opcin -S y la opcin -march seguida del identificador de la
arquitectura respectiva:
gcc -march=i386 -S programa.c
gcc -march=nocona -S programa.c
gcc -march=core2 -S programa.c
Comparacin de los cdigos en ensamblador utilizando Meld:

Ejemplos
El siguiente programa ser utilizado para demostrar los efectos de los
diferentes niveles de optimizacin:

#include <stdio.h>
double
powern (double d, unsigned n)
{
double x = 1.0;
unsigned j;
for (j = 1; j <= n; j++)
x *= d;
return x;
}

int
main (void)
{
double sum = 0.0;
unsigned i;
for (i = 1; i <= 100000000; i++)
{
sum += powern (i, i % 5);
}
printf ("sum = %g\n", sum);
return 0;
}

El programa principal contiene un ciclo que hace llamadas a la


funcin powern. Esta funcin calcula la n-sima potencia de un
numero de coma flotante a traes de multiplicaciones sucesivas, se ha
elegido ste porque tambin es adecuado para mostrar la
optimizacin de las funciones en-lnea. El tiempo de ejecucin del
programa puede ser medido utilizando el comando time en una
terminal.
Se muestran a continuacin los resultados para el programa anterior,
compilado en un procesador Intel Celeron a 566MHz con 16KB L1cache y 128KB L2-cache, usando GCC 3.3.1 en un sistema GNU/Linux:
$ gcc -Wall -O0 test.c -lm
$ time ./a.out
real
0m13.388s
user
0m13.370s
sys
0m0.010s
$ gcc -Wall -O1 test.c -lm
$ time ./a.out
real
0m10.030s
user
0m10.030s
sys
0m0.000s
$ gcc -Wall -O2 test.c -lm
$ time ./a.out
real
0m8.388s
user
0m8.380s
sys
0m0.000s
$ gcc -Wall -O3 test.c -lm
$ time ./a.out
real
0m6.742s
user
0m6.730s
sys
0m0.000s
$ gcc -Wall -O3 -funroll-loops test.c -lm
$ time ./a.out
real
0m5.412s
user
0m5.390s

sys

0m0.000s

El valor relevante de la salida util para comparar la velocidad de los


ejecutables resultantes es el tiempo de usuario (user time), que da el
tiempo invertido por el CPU en correr el proceso. Las otras filas, 'real'
y 'sys', indican el tiempo real para el proceso a correr (incluyendo
tiempos donde otros procesos estuvieron utilizando el CPU) y el
tiempo invertido esperando las llamadas del sistema operativo.
De los resultados podemos ver que al incrementar el nivel de
optimizacin con -O1, -O2, y -O3 produce una mejora en la velocidad
comparado con el cdigo sin optimizar generado con -O0. La opcin
adicional -funroll-loops produce an ms mejoras en la velocidad. La
velocidad del programa se duplica en general, partiendo de un cdigo
no optimizado hacia el nivel ms alto de optimizacin.

La compilacin de un programa escrito en lenguaje C con GCC


Se mostrar con un ejemplo sencillo como GCC trabaja sobre el
programa fuente desde que se preprocesa hasta que se genera el
cdigo binario. Con la opcin -v (verbose) del compilador GCC,
podemos ver lo que ocurre:

Fase 1: Preprocesado
En el cdigo fuente generalmente aparece una serie de directivas del
preprocesador (#define, #include, #ifdef, ) que necesitan ser
interpretadas pra que el cdigo pueda ser compilado.
Por ejemplo, el programa:
#define PI 3.141592
void main(void)
{
float area, radio;
radio = 3;
area = PI * (radio * radio);
printf("\n%f\n", area);
}
tras el preprocesado el programa estar compuesto de sentencias
similares a las del siguiente cdigo fuente:
# 1 "programa.c"

# 1 "<built-in>"
# 1 "<command-line>"
# 1 "programa.c"
void main(void)
{
float area, radio;
radio = 3;
area = 3.141592 * (radio * radio);
printf("\n%f\n", area);
}
El programa anterior fue obtenido utilizando el siguiente comando al
momento de compilar:
gcc -save-temps -v -o programa.o programa.c
y luego consultando el archivo programa.i, o bien, con el siguiente
comando:
gcc -v -E programa.c > programa.preprocesado
y luego leyendo el archivo programa.preprocesado.

Fase 2: Compilacin
En esta fase GCC transforma el cdigo C en cdigo ensamblador,
propio del procesador que utilizamos en nuestra mquina Linux (x86,
MIPS, Sparc, PPC, Alpha, ) Por ello es necesario que en caso de
compilar el GCC se le indique para que arquitectura generar cdigo.
Se puede generar cdigo ensamblador desde el cdigo C
preprocesado con la ayuda del programa cc1, o con un parmetro
especial del GCC:
cc1 programa.preprocesado
o bien,
gcc -S programa.preprocesado
Esto generar el cdigo ensamblador para nuestra mquina en el
archivo de salida, programa.s.

Fase 3: Ensamblado
En esta fase, el compilador transforma el cdigo ensamblador a
cdigo mquina binario que pueda entender nuestro procesador. Esto
se puede realizar con el comando as:
as -o programa.o programa.s
Lo cual generar la salida objeto programa.o

Fase 4: Enlazado
En nuestro cdigo fuente generalmente aparecen llamadas a
funciones como printf, fgets, fopen, etc. Estas funciones ya se
encuentran compiladas y ensambladas en las libreras que se pueden
encontrar en directorios como /lib, /usr/lib, /usr/local/lib. Lo que debe
hacerse es aadir dinmica o estticamente estos binarios a nuestro
cdigo ya compilado y ensamblado. Este proceso se denomina
Enlazado o linkado.
Hay dos tipos principales de enlazado:
Enlazado esttico: Los binarios de las libreras se aaden a
nuestros binarios compilados.
Enlazado dinmico: Esto no aade los binarios de las libreras a
nuestro binario, ms bien, har que nuestro programa cargue en
memoria la librera en el momento que la necesite. Esto permite que
nuestro programa no aumente mucho en tamao.
El enlazado lo hace el programa ld donde a nuestro binario se le
aade las libreras correspondientes a las funciones que llamamos, y
a una serie de rutinas de inicializacin y salida.
Por ejemplo, el siguiente comando enlaza nuestro programa con las
libreras requeridas:
ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o programa.o -z
relro /usr/lib/gcc/i486-linux-gnu/4.3.2/../../../../lib/crt1.o
/usr/lib/gcc/i486-linux-gnu/4.3.2/../../../../lib/crti.o /usr/lib/gcc/i486linux-gnu/4.3.2/crtbegin.o -L/usr/lib/gcc/i486-linux-gnu/4.3.2
-L/usr/lib/gcc/i486-linux-gnu/4.3.2 -L/usr/lib/gcc/i486-linuxgnu/4.3.2/../../../../lib -L/lib/../lib -L/usr/lib/../lib -L/usr/lib/gcc/i486-linuxgnu/4.3.2/../../.. programa.o -lgcc --as-needed -lgcc_s --no-as-needed
-lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/i486-linuxgnu/4.3.2/crtend.o /usr/lib/gcc/i486-linux-gnu/4.3.2/../../../../lib/crtn.o

Aunque todo este proceso est englobado por el GCC, ya que GCC es
un front-end de los programas cpp, cc1, as y ld.
As todo el proceso anterior se puede simplificar en un solo comando:
gcc -o programa programa.c
Si se cuenta con varias libreras que se desean usar, se puede indicar
por medio de la directiva -l. Por ejemplo, para enlazar la librera con
funciones matemticas (libm.so) se indica con -lm.
Si se cuenta con mdulos, es decir, otros archivos fuente que
queremos aadir, primero debern ser compilador todos por separado
como sigue:
gcc -c codigo_principal.c
gcc -c modulo1.c
gcc -c modulo2.c

Obteniendo los archivos codigo_principal.o, modulo1.o, modulo2.o,


etc para el ejemplo anterior.
Y luego sern enlazados creando el archivo ejecutable de la siguiente
manera:
gcc -o programa codigo_principal.o modulo1.o modulo2.o
Obteniendo as el ejecutable programa.

S-ar putea să vă placă și