vectores de fortran em c - fenix.tecnico.ulisboa.pt · partilhar a minha experiencia com alguˆ em...
TRANSCRIPT
Vectores de Fortran em C
Joao Carlos Murtinheira
Dissertacao para obtencao do Grau de Mestre em
Engenharia Informatica e de Computadores
Orientador: Prof. Pedro Manuel Guerra e Silva Reis dos Santos
Juri
Presidente: Prof. Daniel Jorge Viegas GoncalvesOrientador: Prof. Pedro Manuel Guerra e Silva Reis dos Santos
Vogal: Prof. David Manuel Martins de Matos
Outubro 2015
Agradecimentos
Em primeiro lugar, expresso a minha profunda e sincera gratidao ao meu professor orientador de
dissertacao, Prof. Pedro Reis Santos, cuja dedicacao foi indispensavel para levar a cabo este pro-
jecto. Agradeco por toda a disponibilidade, empenho, paciencia e enorme conhecimento sobre o tema,
factores sem os quais a redaccao desta dissertacao nao seria possıvel.
Agradeco tambem a todos os colegas e amigos que, de uma forma ou de outra, me deram o enco-
rajamento e apoio de que precisei para levar a cabo este trabalho: Alberto Carvalho, Beatriz Morgado,
Bernardo Santos, Claudia Grilo, Eduardo Martins, Mafalda Cardeira, Margarida Alberto, Miguel Coe-
lho, Nuno Duarte, Joao Neves, Jorge Pereira, Jose Cavalheiro, Rita Domingos, Soraia Alarcao e Vania
Mendonca. Em especial, quero agradecer aos meus abnegados “mentores”, Bernardo Santos, Jorge
Pereira, Mafalda Cardeira, Soraia Alarcao e Vania Mendonca, por essencialmente me ensinarem a
redigir uma dissertacao de mestrado a luz das suas proprias experiencias.
Muito obrigado a minha companheira assıdua de noitadas, Rita Domingos, pela compreensao, pe-
las gargalhadas, por toda a motivacao para seguir em frente, e acima de tudo, pela oportunidade de
partilhar a minha experiencia com alguem que, em troca, partilhou a sua.
Ao meu grande amigo Miguel Coelho, alem da disponibilizacao de hardware que tornou possıvel a
avaliacao do trabalho, quero agradecer pela enorme cumplicidade e amizade, que me mantiveram de
pe ao longo do ultimo ano.
Obrigado ainda ao insubstituıvel Jorge Pereira, que apesar da distancia fısica, conseguiu estar sem-
pre presente para me dar as palavras de animo e forca quando precisei delas. Muito obrigado pela
eterna amizade.
Finalmente, aos meus pais e familiares que sempre me apoiaram nas mais diversas vertentes, quero
agradecer por tornarem possıvel nao so este projecto, como tambem toda a minha formacao ate hoje.
Abstract
The programming languages C and Fortran allow the use of arrays as a way to organize sets of same-
type entities in memory. Although their purpose and functionality are similar, each language uses a
different method of addressing values stored in an array, since each one possesses a distinct strategy
of organizing the memory allocated for these structures. Depending on the conditions under which a
program is run, such as Central Processing Unit (CPU) architecture, each different strategy may cause
different performance degrees when running a program containing array operations. This project con-
sisted in combining both memory organization and access strategies into an extended version of the
C programming language. The advantages and disadvantages of using each strategy are also explo-
red, based on experimental performance measurements of several implemented algorithms running on
different CPU architectures.
Keywords
Array, C Language, Compiler, Fortran Language, Memory, Performance.
iii
Resumo
As linguagens de programacao C e Fortran permitem a utilizacao de vectores como forma de orga-
nizar em memoria conjuntos de entidades do mesmo tipo. Ainda que com objectivo e funcionalidade
semelhante, cada linguagem utiliza um metodo diferente de carregamento dos valores guardados em
vectores, pois cada uma possui uma estrategia propria de organizacao da memoria reservada para
estas estruturas. Dependendo das condicoes de execucao de um programa, como a arquitectura do
processador utilizado, cada estrategia podera originar diferentes nıveis de desempenho de um pro-
grama que faca uso de vectores. Neste projecto, desenvolveu-se uma forma de combinar ambas as
estrategias de organizacao e acesso a memoria numa versao estendida da linguagem C. Exploram-se
tambem as vantagens e desvantagens do uso de cada estrategia, com base em medicoes experimen-
tais do desempenho de varios algoritmos implementados e executados sobre diferentes arquitecturas
de processador.
Palavras Chave
Compilador, Desempenho, Linguagem C, Linguagem Fortran, Memoria, Vector.
v
Conteudo
1 Introducao 1
1.1 Motivacao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2 Objectivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3 Estrutura do Documento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2 Enquadramento 7
2.1 Vectores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.2 Ponteiros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.3 Linguagem C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.4 Linguagem Fortran . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.5 Linguagens MATLAB e GNU Octave . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.6 Compiladores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.6.1 Analise Lexical . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.6.2 Analise Sintactica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.6.3 Analise Semantica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.6.4 Geracao de Codigo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.7 Analise Comparativa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.8 Sumario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
3 Solucao Proposta 17
3.1 Visao Global . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3.2 Descricao da Arquitectura . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
3.2.1 Extensoes Sintacticas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
3.2.2 Verificacoes Semanticas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
3.2.3 Geracao de Codigo C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
3.2.4 Exemplos de Utilizacao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3.3 Metodologia de Avaliacao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
3.4 Sumario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
vii
4 Realizacao 31
4.1 Funcionalidade da Solucao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
4.2 Flex: Fast Lexical Analyzer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
4.3 GNU Bison . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
4.4 Descricao Gramatical . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
4.5 Semantica e Geracao de Codigo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
4.6 Processo de Desenvolvimento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
4.6.1 Tipos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
4.6.2 Identificadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
4.6.3 Comentarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
4.6.4 Declaracoes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
4.6.5 Indexacoes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
4.6.6 Funcoes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
4.7 Problemas Enfrentados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
5 Avaliacao da Solucao 47
5.1 Modelos de Teste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
5.1.1 Soma de Vectores Tridimensionais . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
5.1.2 Produto de Matrizes Quadradas . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
5.1.3 Determinante: Formula de Leibniz . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
5.2 Discussao dos Resultados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
6 Conclusao 55
6.1 Conclusoes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
6.2 Limitacoes e Trabalho Futuro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
A Codigo de Avaliacao 61
A.1 Soma de Vectores Tridimensionais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
A.2 Produto de Matrizes Quadradas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
A.3 Determinante: Formula de Leibniz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
B Tempos de Execucao 67
viii
Lista de Figuras
2.1 Vector bidimensional em C: Ponteiros para outros vectores. . . . . . . . . . . . . . . . . . 11
2.2 Vector bidimensional em Fortran: Bloco contıguo em memoria. . . . . . . . . . . . . . . . 12
2.3 Componentes de um compilador tıpico e fases da compilacao. . . . . . . . . . . . . . . . 13
3.1 Visao Global da solucao. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
ix
x
Lista de Tabelas
5.1 Desempenho da soma de vectores num processador Intel x86-64 a 1.80 GHz. . . . . . . 50
5.2 Desempenho da soma de vectores num processador Intel x86-64 a 2.00 GHz. . . . . . . 50
5.3 Desempenho da soma de vectores num processador ARMv6 a 700 MHz. . . . . . . . . . 51
5.4 Desempenho do produto de matrizes num processador Intel x86-64 a 1.80 GHz. . . . . . 51
5.5 Desempenho do produto de matrizes num processador Intel x86-64 a 2.00 GHz. . . . . . 52
5.6 Desempenho do produto de matrizes num processador ARMv6 a 700 MHz. . . . . . . . . 52
5.7 Desempenho da formula de Leibniz num processador Intel x86-64 a 1.80 GHz. . . . . . . 53
5.8 Desempenho da formula de Leibniz num processador Intel x86-64 a 2.00 GHz. . . . . . . 53
5.9 Desempenho da formula de Leibniz num processador ARMv6 a 700 MHz. . . . . . . . . . 54
B.1 Execucao da soma de vectores num processador Intel x86-64 a 1.80 GHz. . . . . . . . . 68
B.2 Execucao da soma de vectores num processador Intel x86-64 a 2.00 GHz. . . . . . . . . 68
B.3 Execucao da soma de vectores num processador ARMv6 a 700 MHz. . . . . . . . . . . . 68
B.4 Execucao do produto de matrizes num processador Intel x86-64 a 1.80 GHz. . . . . . . . 69
B.5 Execucao do produto de matrizes num processador Intel x86-64 a 2.00 GHz. . . . . . . . 69
B.6 Execucao do produto de matrizes num processador ARMv6 a 700 MHz. . . . . . . . . . . 69
B.7 Execucao da formula de Leibniz num processador Intel x86-64 a 1.80 GHz. . . . . . . . . 70
B.8 Execucao da formula de Leibniz num processador Intel x86-64 a 2.00 GHz. . . . . . . . . 70
B.9 Execucao da formula de Leibniz num processador ARMv6 a 700 MHz. . . . . . . . . . . . 70
xi
xii
Lista de Algoritmos
4.1 Geracao de codigo de uma declaracao. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
4.2 Geracao de codigo de uma indexacao. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
xiii
xiv
Listagens
3.1 Somatorio dos valores de uma matriz (linguagem C tradicional). . . . . . . . . . . . . . . 20
3.2 Somatorio dos valores de uma matriz (linguagem C estendida). . . . . . . . . . . . . . . . 20
3.3 Soma dos elementos de duas matrizes (linguagem C estendida). . . . . . . . . . . . . . . 24
3.4 Soma dos elementos de duas matrizes (linguagem C tradicional). . . . . . . . . . . . . . 25
3.5 Alteracao do tipo do vector retornado pela funcao de reserva. . . . . . . . . . . . . . . . . 26
3.6 Acessos a vectores de ambos os tipos (linguagem C). . . . . . . . . . . . . . . . . . . . . 27
3.7 Acessos a vectores de ambos os tipos (linguagem assembly). . . . . . . . . . . . . . . . 28
3.8 Carregamento de um valor num vector com 6 dimensoes. . . . . . . . . . . . . . . . . . . 29
4.1 Exemplo de entrada do compilador. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
4.2 Exemplo de saıda do compilador. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
A.1 Soma de dois vectores tridimensionais. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
A.2 Produto de duas matrizes quadradas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
A.3 Formula de Leibniz para o determinante de uma matriz. . . . . . . . . . . . . . . . . . . . 65
xv
xvi
Acronimos
ARM Acorn RISC Machine
CPU Central Processing Unit
GCC GNU Compiler Collection
GPL GNU General Public License
LALR Look-Ahead Left-to-Right
xvii
xviii
1Introducao
Conteudo
1.1 Motivacao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2 Objectivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3 Estrutura do Documento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1
2
A linguagem C, originaria de cerca de 1972 [1], surgiu com o intuito de fornecer ao programador um
elevado nıvel de controlo sobre a quantidade de memoria utilizada, e sobre a forma como a mesma se
organiza no espaco de enderecamento de um programa [2]. Por este motivo, esta e uma linguagem
indicada ao desenvolvimento de programas que tenham fortes requisitos de gestao dos recursos de
memoria utilizados [1].
Desde a sua origem, a linguagem C continua a ser utilizada como standard no desenvolvimento de
aplicacoes informaticas, e principalmente de sistemas operativos [3], mantendo a especificacao original
da autoria de Brian Kernighan e Dennis Ritchie [2], no que toca ao funcionamento das operacoes de
reserva e acesso a memoria.
A linguagem Fortran, mais antiga (meados da decada de 50), teve como objectivo maximizar o
desempenho da realizacao de operacoes vectoriais, divergindo da linguagem C em relacao ao controlo
sobre a memoria reservada que e fornecido ao programador [4], bem como no que diz respeito ao modo
como as operacoes de acesso sao realizadas [5,6].
1.1 Motivacao
Este projecto pretende abordar, como se descreve em maior detalhe no Capıtulo 2, a necessidade
de combinar pelo menos duas linguagens de programacao, neste caso C e Fortran, quando se pre-
tende operar de modos distintos sobre a memoria reservada para os vectores multidimensionais de um
programa [2,6].
Por outro lado, se o programador pretender utilizar apenas uma unica linguagem, ve-se obrigado
a optar por uma mais flexıvel no que toca a organizacao da memoria dos vectores no seu espaco de
enderecamento, mas que se pode revelar menos eficiente no acesso ao seu conteudo, como e o caso
da linguagem C, por oposicao a uma linguagem mais restritiva, mas que podera ser mais eficiente nos
acessos, como a linguagem Fortran [4,7].
Com este trabalho, pretendeu-se concretizar uma solucao para este problema, de modo a permi-
tir ao programador o uso de vectores multidimensionais na linguagem C, com operacoes de acesso
potencialmente mais eficientes do que aquelas que a linguagem fornece, e que estao presentes na
linguagem Fortran [4].
O trabalho teve como principal motivacao a extraccao de conclusoes sobre o desempenho compa-
rativo de cada tipo de vectores, avaliando cada um em diferentes condicoes, como se descreve mais
adiante.
3
1.2 Objectivos
Para solucionar o problema referido na seccao anterior, propos-se desenvolver uma versao mo-
dificada da linguagem C, estendendo-a de modo a acrescentar um tipo novo de vectores, os quais
funcionam de modo identico aos da linguagem Fortran. Pretendeu-se tambem que as operacoes com
vectores da linguagem C tradicional fossem inteiramente preservadas, de modo a permitir a utilizacao
de ambos os tipos de vectores num so programa, implementado numa unica linguagem.
Este tipo de abordagem, utilizada em projectos de diferentes naturezas e finalidades [8, 9], permite
que qualquer programa desenvolvido segundo a especificacao tradicional da linguagem C seja inteira-
mente compatıvel com a versao modificada [9], uma vez que apenas consiste em acrescentar regras
sintacticas e correspondentes verificacoes semanticas [10,11] a especificacao existente, sem a alterar
de qualquer outro modo.
De modo a concretizar a solucao proposta, este trabalho teve como objectivo o desenvolvimento de
um compilador, ou seja, um programa de traducao de codigo [10]. Este compilador recebe, como en-
trada, programas escritos na versao modificada da linguagem C, produzindo programas equivalentes,
escritos na linguagem C tradicional. A saıda desta ferramenta pode entao ser processada por compi-
ladores da linguagem C ja existentes, de modo a que possam ser aproveitadas as optimizacoes neles
incluıdas como consequencia do seu proprio desenvolvimento [8].
1.3 Estrutura do Documento
No presente documento descrevem-se, em primeiro lugar, os conceitos que contextualizam o pro-
blema a solucionar. Esta informacao encontra-se no Capıtulo 2, no qual se define o conceito de vector,
ou array, no ambito de uma linguagem de programacao, explicando qual o seu proposito, bem como
o modo de utilizacao e funcionamento destas estruturas nas linguagens de programacao envolvidas
na solucao desenvolvida: a linguagem C e a linguagem Fortran. Referem-se ainda breves exemplos
de outras linguagens, devido a relacao que apresentam com estas, ou as particularidades que as tor-
nam relevantes para o contexto deste trabalho. Finalmente, descreve-se o processo de compilacao
do codigo de um programa, detalhando todas as fases que o constituem e os componentes que um
compilador (ou tradutor) deve possuir para as realizar.
No Capıtulo 3 sao descritos os componentes arquitecturais da solucao. Visto tratar-se de uma
extensao de uma linguagem de programacao, este capıtulo descreve os mecanismos de analise lexical,
sintactica e semantica, referindo quais as regras a acrescentar a gramatica da linguagem existente, e
qual o codigo a gerar para as mesmas. E tambem neste capıtulo que se descreve em pormenor qual o
mecanismo de funcionamento dos vectores que se procurou implementar. Por fim, fornecem-se alguns
exemplos de correcta utilizacao da linguagem estendida.
4
O Capıtulo 4 descreve a implementacao do trabalho no que diz respeito a programacao realizada
e as ferramentas utilizadas para o desenvolvimento do compilador que constitui a solucao. E comple-
mentada com os algoritmos de maior relevancia, com vista a melhor documentar o trabalho realizado.
O Capıtulo 5 avalia a solucao desenvolvida, atraves de medicoes e tratamento estatıstico do tempo
de execucao de programas elaborados com recurso a extensao da linguagem, por oposicao a progra-
mas equivalentes, sem recurso a mesma. No final deste capıtulo, sera possıvel concluir acerca das
vantagens e desvantagens do uso da extensao da linguagem, avaliando a solucao produzida na sua
totalidade.
Por ultimo, o Capıtulo 6 apresenta em resumo o conteudo do documento, referindo os aspectos
principais de cada capıtulo, e aludindo as limitacoes e possibilidades de melhoria do trabalho desenvol-
vido.
5
6
2Enquadramento
Conteudo
2.1 Vectores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.2 Ponteiros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.3 Linguagem C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.4 Linguagem Fortran . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.5 Linguagens MATLAB e GNU Octave . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.6 Compiladores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.7 Analise Comparativa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.8 Sumario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
7
8
Neste capıtulo exploram-se os conceitos fundamentais ao entendimento do projecto desenvolvido,
bem como os aspectos mais relevantes das linguagens de programacao envolvidas no contexto deste
projecto. Concretamente, sao referidas as vantagens e desvantagens da utilizacao de cada linguagem,
os conceitos de programacao relacionados com o tema, e ainda a constituicao tıpica de uma ferramenta
de compilacao.
2.1 Vectores
Em programacao, o conceito de vector (ou array ) e tipicamente definido como um conjunto de
multiplos valores do mesmo tipo guardados contiguamente no espaco de enderecamento de um pro-
grama [2,6,12]. O acesso a cada elemento do vector e realizado atraves de uma operacao de indexacao,
a qual consiste na soma aritmetica de um valor de deslocamento ao endereco de memoria da posicao
inicial do vector [2, 4, 6]. Para facilitar a programacao, um vector pode ter um numero de dimensoes
superior a 1, o que faz com que seja necessario realizar tambem a indexacao em cada uma dessas
dimensoes [2,6].
A tıtulo de exemplo, considere-se a implementacao de um progama de desenho, em que o utilizador
dispoe de uma tela rectangular e faz uso da interface grafica para alterar a cor de cada ponto individual
da tela. Neste contexto, o programador pode optar por organizar esses pontos como uma matriz de
duas dimensoes, ficando cada uma associada a um eixo coordenado da tela. Desta forma, cada ponto
e acedido e modificado pelo programa atraves do fornecimento de um par de coordenadas (x, y), pro-
venientes da interface de utilizador, o que corresponde a obter o endereco do elemento do vector de
pontos correspondente, atraves da indexacao do vector com esses mesmos valores x e y.
Apesar desta definicao generica do conceito de vector, cada linguagem de programacao pode es-
pecifica-la de formas distintas [2, 4], factor do qual dependera a eficiencia dos programas que os seus
compiladores produzem, como se descreve nas seccoes seguintes deste capıtulo.
2.2 Ponteiros
O conceito de ponteiro e normalmente definido como uma entidade que guarda o valor de um
endereco de memoria com algum significado relevante para o programa [1, 2, 12]. Apesar de poder
nao ter um valor valido definido, um ponteiro esta normalmente associado a uma variavel no espaco
de enderecamento, indicando o endereco de memoria que a mesma ocupa, sendo tambem frequente
a sua utilizacao na linguagem C, para indicar o endereco da posicao inicial de um vector [2].
Na linguagem Fortran, um ponteiro nao pode ser utilizado para obter directamente o endereco da
entidade a qual esta associado; apenas permite aceder ao valor guardado nesse endereco, entre outras
9
particularidades [4, 6]. Na linguagem C, por oposicao, um ponteiro nao e mais do que um valor do tipo
inteiro sem sinal, que contem o valor do endereco para o qual aponta [1]. Esta simplicidade permite
ao programador realizar operacoes aritmeticas (como somas ou subtraccoes) envolvendo ponteiros e
ındices de deslocamento. Destas operacoes resultam novos enderecos de memoria, os quais podem
ser usados para aceder aos valores guardados nesses enderecos, desde que nao seja transposto o
espaco de enderecamento do programa [1,2,4].
As operacoes de aritmetica de ponteiros sao uteis no processamento de vectores na linguagem
C, pois estas estruturas sao constituıdas por blocos de memoria contıgua que armazenam multiplos
valores [1,2], como se descreve na proxima seccao.
2.3 Linguagem C
A linguagem C proporciona ao programador um grande nıvel de controlo sobre a organizacao das
estruturas de dados de um programa no seu espaco de enderecamento [1], bem como o acesso as
mesmas.
Esta linguagem fornece tres tipos de vectores, cuja especificacao difere, dependendo do modo como
e realizada a reserva de memoria, apesar de serem semelhantes em termos de utilizacao. Os vectores
obtidos atraves de reserva estatica de memoria sao blocos contıguos com tamanho fixo, igual ao pro-
duto do numero de bytes de cada elemento do vector pelos valores especificados na sua declaracao.
Para obter um vector deste tipo, o programador precisa apenas de declara-lo, especificando os tama-
nhos das suas dimensoes atraves de expressoes numericas constantes [2].
Os vectores obtidos atraves de reserva local (com recurso a funcao alloca, definida na biblioteca
padrao da linguagem) sao semelhantes aos vectores estaticos, pois nao e possıvel alterar o seu ta-
manho. No entanto, permitem fazer uso de valores guardados em variaveis para especifica-lo, e nao
apenas expressoes constantes [1]. O valor retornado por esta funcao e um ponteiro para um bloco de
memoria contıguo, com tamanho igual ao numero de bytes passado como argumento.
A reserva dinamica de memoria, por outro lado, tambem realizada com recurso a funcoes da biblio-
teca padrao da linguagem C (stdlib), torna possıvel alterar o tamanho do vector resultante, o que nao
e permitido com reserva estatica ou reserva local [1,2].
Independentemente do tipo de reserva, a operacao de indexacao de vectores na linguagem C con-
siste, numa primeira fase, em obter o endereco guardado no ponteiro resultante da operacao de reserva,
o qual e utilizado para calcular o endereco pretendido, atraves da soma do deslocamento correspon-
dente. So apos este calculo e possıvel realizar o acesso ao valor pretendido [1,2].
Para possibilitar o uso de vectores com mais do que uma dimensao, e necessario elaborar uma hi-
erarquia de vectores de ponteiros, cada um dos quais pode ter tamanho variavel, com tantos nıveis
10
hierarquicos quanto o numero de dimensoes pretendidas para o vector [2]. A Figura 2.1 ilustra a
organizacao em memoria deste tipo de vectores. Na reserva dinamica, este e um processo mais com-
plexo para o programador, pois envolve sucessivas reservas de memoria, de modo a preencher devi-
damente todos os nıveis da hierarquia com vectores de ponteiros para os nıveis inferiores. A operacao
de indexacao multidimensional envolve tambem tantas sequencias de calculos de deslocamentos e
carregamentos de valores quanto o numero de dimensoes do vector [2].
Figura 2.1: Vector bidimensional em C: Ponteiros para outros vectores.
A reserva dinamica de memoria introduz ainda a ocorrencia de fragmentacao espacial dos blocos
obtidos, no que diz respeito a organizacao dos vectores [1].
A tıtulo de exemplo, a obtencao de um vector dinamico de duas dimensoes, ou seja, uma matriz
de X linhas por Y colunas, exige que seja invocada a funcao de reserva de memoria para obter,
numa primeira fase, um ponteiro para um vector de outros ponteiros, os quais resultarao, em fases de
execucao posteriores, de Y chamadas subsequentes a mesma funcao1. Este processo, por um lado,
permite poupar memoria, pois cada vector e obtido individualmente e pode ter uma dimensao diferente
dos restantes. Origina, no entanto, uma inevitavel dispersao dos varios vectores, podendo cada um ficar
separado dos restantes por grandes intervalos de memoria, devido ao mecanismo de aleatoriedade do
espaco de enderecamento, presente em muitos sistemas operativos. A isto acrescenta-se a ocorrencia
de fragmentacao externa [1], provocada pela libertacao da memoria destes vectores. Neste caso,
surgem diversos lotes de memoria livre, os quais podem nunca ser reaproveitados, caso o seu tamanho
seja demasiado pequeno. Trata-se, portanto, de um compromisso entre a flexibilidade das operacoes
de reserva de memoria da linguagem e a eficiencia do acesso a mesma.
1Neste exemplo, considera-se que o algoritmo exige a reserva de uma coluna por cada ındice das linhas da matriz
11
2.4 Linguagem Fortran
Surgida na decada de 50, e mais antiga do que a linguagem C, a linguagem Fortran2 apresentava,
ja desde as suas especificacoes iniciais, uma forma eficiente de programacao e calculo de operacoes
aritmeticas com vectores de multiplas dimensoes [5]. Esta propriedade da linguagem prende-se com o
facto de ter sido criada com o objectivo de facilitar a introducao de formulas matematicas e o processa-
mento intensivo de calculos numericos necessario em areas como a Fısica, a Quımica ou a Engenharia.
Ainda assim, no que diz respeito a constituicao dos vectores em memoria, bem como a capacidade
de manipulacao da mesma pelo programador, trata-se de uma linguagem mais restritiva do que a lin-
guagem C. De facto, contrariamente a esta, um elemento de um vector na linguagem Fortran e sempre
acedido atraves de um deslocamento aplicado ao ponteiro para a sua posicao inicial, independente-
mente do numero de dimensoes que possa ter [4–6]. Este deslocamento e entao utlizado para aceder
directamente a posicao desejada, realizando-se apenas uma unica operacao de carregamento para o
efeito [4].
Consequentemente, como ilustrado na Figura 2.2, um vector multidimensional em Fortran esta sem-
pre restrito a condicao de contiguidade em memoria dos seus elementos, e consequentemente, nao e
permitida a diferenca de tamanho entre vectores parciais do mesmo nıvel hierarquico, contrariamente a
linguagem C.
Figura 2.2: Vector bidimensional em Fortran: Bloco contıguo em memoria.
Exemplificando, para obter o elemento contido na posicao (i, j, k) de um vector tridimensional com
ındices variando entre (0, 0, 0) e (x, y, z), o deslocamento a somar a primeira posicao em memoria do
mesmo e dado, de forma imediata, por [4]:
δ = i+ jx+ kxy
Desta forma, em comparacao com a linguagem C, e apesar das restricoes na organizacao dos vec-
tores em memoria, a linguagem Fortran podera ser mais eficiente no acesso a mesma, especialmente
para vectores com numeros elevados de dimensoes.
2O nome Fortran e um acronimo que significa FORmula TRANslation.
12
2.5 Linguagens MATLAB e GNU Octave
Dado o enfase em calculo matricial da linguagem MATLAB, nao e de estranhar que a sua compo-
nente vectorial seja maioritariamente desenvolvida em Fortran. De facto, caso o programador o deseje,
pode aceder e interagir com rotinas de Fortran durante execucao dos seus programas em MATLAB, ou
inversamente, invocar funcoes de MATLAB em programas na linguagem Fortran [13]. A literatura revela
tambem que e possıvel o desenvolvimento de ferramentas de traducao de codigo MATLAB para codigo
Fortran [14].
Ainda assim, visto ser uma linguagem interpretada de alto nıvel, constituıda segundo uma arquitec-
tura em camadas implementadas em linguagens diferentes3, e devido ao elevado numero de reservas
de memoria que um programa em MATLAB necessita frequentemente de realizar, acaba por se sacrifi-
car consideravelmente o desempenho desta linguagem [14].
Outras linguagens semelhantes seguem o mesmo tipo de abordagem, como a linguagem GNU
Octave, cujo objectivo e ser tao compatıvel com a linguagem MATLAB quanto possıvel, pelo que faz
uso das mesmas linguagens e possibilita tambem a interaccao com programas externos [15,16]. Possui,
portanto, as mesmas desvantagens de desempenho referidas.
2.6 Compiladores
Como ja foi referido, pretendeu-se estender a especificacao da linguagem C existente, pelo que
foi necessario desenvolver um compilador que processe codigo escrito na versao modificada da lin-
guagem, traduzindo esse codigo para uma versao intermedia, sob a forma de codigo C tradicional, de
modo a que, no final de todo o processo de compilacao, seja possıvel obter o codigo-maquina cor-
respondente [10], ou seja, o codigo binario que e efectivamente reconhecido e processado por uma
CPU. Nesta seccao, descrevem-se os principais componentes do pipeline que constitui um compilador
tradicional, os quais se ilustram na Figura 2.3.
Figura 2.3: Componentes de um compilador tıpico e fases da compilacao.
3Alem das rotinas de Fortran, a interface grafica de utilizador e desenvolvida em Java, sendo a sua camada principal imple-mentada em C++.
13
2.6.1 Analise Lexical
A primeira fase do processo de compilacao consiste na analise lexical do ficheiro de codigo fonte,
que e a entrada do compilador. Esta analise reconhece os elementos da linguagem (ou tokens) pre-
sentes no codigo [10,11], identificando univocamente cada um, bem como a posicao no codigo de cada
elemento relativamente aos restantes. Esta informacao e passada para o nıvel seguinte do compilador,
o analisador sintactico. Caso sejam detectados no codigo elementos lexicais que nao pertencam a
linguagem, devera ser assinalado um erro pelo compilador [10].
Dependendo da especificacao da linguagem, pode tambem nesta fase ser realizado algum proces-
samento previo, como reconhecimento de tipos de literais ou eliminacao de comentarios [11].
2.6.2 Analise Sintactica
A segunda fase da compilacao consiste na construcao de uma arvore sintactica, de acordo com a
ordem pela qual sao identificados os tokens ao nıvel do analisador lexical [10,11]. Esta arvore e cons-
truıda segundo as regras gramaticais da linguagem, as quais indicam qual a combinacao de elementos
da linguagem que constitui cada operacao. Deste modo, as operacoes estruturalmente mais complexas
sao colocadas em nıveis superiores da arvore, tendo como nos filhos as operacoes mais simples que
as constituem. Adicionalmente, cada literal deve ficar associado ao seu valor, na base da arvore, de
modo a que possa ser processado directamente pelo analisador semantico, que desempenha a fase
seguinte da compilacao [10].
E ainda na fase de analise sintactica que o compilador deve realizar e reportar a deteccao de erros
sintacticos presentes no codigo. Estes sao erros que ocorrem quando o programador desrespeita as
regras gramaticais da linguagem, ao escrever combinacoes de elementos lexicais que nao possuam
uma estrutura sintactica correspondente na gramatica da linguagem [10].
2.6.3 Analise Semantica
Esta fase da compilacao tem como entrada a arvore sintactica gerada na fase anterior, e consiste
em percorrer e aumentar a mesma, visitando todos os seus nos e propagando os valores de cada no
inferior, comecando nas folhas, para os nos superiores, de acordo com a operacao associada a cada
um, ate ser atingida a raiz da arvore. Durante a analise semantica, o compilador devera reportar erros
de semantica presentes no codigo do programa, ou seja, combinacoes de nos da arvore sintactica que
contenham significados ou valores incompatıveis entre si [10].
E tambem nesta fase que e verificada a validade de atribuicoes, correspondencias entre nomes de
variaveis ou funcoes e as suas definicoes, bem como correspondencias de tipos (type checking) [10].
14
2.6.4 Geracao de Codigo
A ultima fase da compilacao consiste na seleccao de instrucoes correspondentes aos nos da arvore
semantica proveniente da fase anterior, compreendendo tambem a reserva de registos e a optimizacao
do codigo resultante. Cada no da arvore equivale a um conjunto de instrucoes na linguagem de saıda
do compilador4. Deste modo, o produto final da compilacao e obtido atraves da combinacao das varias
instrucoes que constituem, na linguagem pretendida, o programa equivalente a sequencia de entrada
do compilador [10].
2.7 Analise Comparativa
No que diz respeito a flexibilidade de gestao da memoria dos vectores que a linguagem C fornece
ao programador, esta permite optimizar a quantidade de memoria reservada em cada momento de
execucao, o que por sua vez podera permitir minimizar os requisitos de hardware sobre o qual o pro-
grama executa. Porem, tem como consequencia uma menor eficiencia no acesso a memoria em vec-
tores multidimensionais, quando estes sao organizados como uma hierarquia de vectores de ponteiros.
A causa desta ineficiencia prende-se com a quantidade de operacoes de carregamento de enderecos
que este tipo de organizacao implica realizar quando se pretende aceder a um dado valor.
De facto, para carregar um valor guardado num vector de n dimensoes organizado desta forma, o
processador necessita de realizar n− 1 carregamentos adicionais, de modo a obter primeiro o valor de
cada ponteiro em cada nıvel da hierarquia, e so depois o valor efectivamente pretendido.
Na linguagem Fortran, por outro lado, visto que a indexacao de um vector consiste sempre na soma
aritmetica de um deslocamento ao endereco da sua posicao inicial, como se ilustrou na Seccao 2.4,
apenas e necessario efectuar o calculo desse deslocamento, obtendo-se o valor pretendido atraves de
um unico carregamento.
E de notar, no entanto, que o referido calculo consiste, em parte, na realizacao de multiplicacoes dos
ındices que o programador indica, as quais sao tao mais numerosas quanto o numero de dimensoes do
vector. Por este motivo, no caso particular de CPUs que nao fornecam a operacao de multiplicacao ao
nıvel de hardware, como a maioria dos actuais processadores Acorn RISC Machine (ARM), e introdu-
zido um custo computacional significativo para numeros elevados de dimensoes, devido a necessidade
de realizar as multiplicacoes ao nıvel do software. Esta desvantagem pode nao justificar a optimizacao
da linguagem Fortran face aos multiplos carregamentos da linguagem C neste tipo de arquitectura.
Ainda assim, dependendo do codigo fonte a compilar, podera ser possıvel a substituicao das opera-
coes de multiplicacao por operacoes de deslocamento a esquerda e de soma5, dada a equivalencia
4Exemplos comuns sao linguagens assembly ou codigo binario.5Optimizacao conhecida como strength reduction, pois consiste em substituir operacoes computacionalmente pesadas (ditas
“fortes”) por operacoes mais leves (ou “fracas”).
15
existente entre uma multiplicacao por uma potencia de 2 e um deslocamento a esquerda dos bits
de um registo. Nos casos em que esta optimizacao e possıvel, podera, mesmo numa arquitectura
ARM, ter menor custo computacional o uso dos vectores da linguagem Fortran, em comparacao aos da
linguagem C.
2.8 Sumario
A reserva de memoria na linguagem C permite uma grande flexibilidade na organizacao da mesma,
sendo possıvel minimizar a quantidade de memoria reservada. No entanto, as operacoes de indexacao
de vectores exigem sucessivas computacoes de calculo de deslocamentos e obtencao de valores ate
se obter efectivamente o valor pretendido.
A linguagem Fortran nao permite tanta flexibilidade, mas tem como vantagem a linearidade em
memoria dos vectores multidimensionais, o que permite aceder directamente aos seus elementos com
a simples aplicacao de um deslocamento ao ponteiro para a posicao inicial do vector.
A linguagem MATLAB, entre outras, faz uso desta propriedade da linguagem Fortran para optimizar
os tempos de execucao dos seus programas. No entanto, visto que os componentes do ambiente
MATLAB sao implementados em linguagens diferentes, que necessitam de comunicar constantemente
entre si, a sua eficiencia fica prejudicada.
16
3Solucao Proposta
Conteudo
3.1 Visao Global . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3.2 Descricao da Arquitectura . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
3.3 Metodologia de Avaliacao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
3.4 Sumario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
17
18
Neste capıtulo e descrita a abordagem seguida para a implementacao da solucao realizada. Em
primeiro lugar, descreve-se a extensao da linguagem desenvolvida com uma perspectiva global. Segui-
damente, descreve-se a arquitectura da solucao, a qual e complementada com exemplos de utilizacao
dos novos vectores, bem como de codigo gerado em diferentes fases do processo de compilacao.
3.1 Visao Global
Como ja foi referido, no decurso deste trabalho, concretizou-se uma extensao da linguagem C, re-
plicando o modo como a memoria e organizada em vectores da linguagem Fortran. Para o efeito,
desenvolveu-se um compilador que processa codigo escrito na versao estendida da linguagem, produ-
zindo codigo na linguagem C tradicional. Este pode, por sua vez, ser processado por um compilador ja
existente dessa mesma linguagem.
Figura 3.1: Visao Global da solucao.
O desenvolvimento de um compilador implicou a implementacao das fases do processo de compila-
cao, como descrito no capıtulo anterior. Para a implementacao desta extensao, optou-se por nao definir
novos elementos da linguagem (tokens), pois os que ja existem sao suficientes para distinguir os novos
vectores dos tradicionais. Para tal, apenas e necessaria a utilizacao de combinacoes diferentes dos
mesmos. Deste modo, a fase de analise lexical da compilacao nao exigiu uma quantidade significativa
trabalho de programacao, tendo a maior parte do esforco sido dedicada as fases posteriores.
O analisador sintactico foi desenvolvido com recurso a ferramenta GNU Bison [11]. Esta ferramenta
permite gerar a arvore sintactica de um programa a partir da especificacao da sua gramatica formal. Foi
necessaria a criacao de regras para processar declaracoes e indexacoes (acessos) dos novos vectores,
segundo a sintaxe escolhida para os mesmos, a qual se descreve em maior detalhe na seccao seguinte.
Foram tambem acrescentadas regras para realizar a passagem destes vectores como argumentos de
funcoes.
Para exemplificar a sintaxe referida, tome-se como exemplo o problema de somar iterativamente to-
dos os elementos de um vector de inteiros bidimensional, com 20 linhas por 30 colunas. Este problema
pode ser resolvido, na linguagem C tradicional, atraves de 2 ciclos encadeados, como se exemplifica
na funcao da Listagem 3.1.
19
Para implementar a mesma operacao com recurso aos novos vectores, utiliza-se a sintaxe da funcao
definida na Listagem 3.2, a qual equivale a Listagem 3.1.
Listagem 3.1: Somatorio dos valores de uma matriz (linguagem C tradicional).
1 int somatorio(int v[20][30]) {
2 int i, j, val = 0;
3 for(i = 0; i < 20; ++i) {
4 for(j = 0; j < 30; ++j) {
5 val += v[i][j];
6 }
7 }
8 return val;
9 }
Listagem 3.2: Somatorio dos valores de uma matriz (linguagem C estendida).
1 int somatorio(int v[20;30]) {
2 int i, j, val = 0;
3 for(i = 0; i < 20; ++i) {
4 for(j = 0; j < 30; ++j) {
5 val += v[i;j];
6 }
7 }
8 return val;
9 }
O componente de analise semantica necessita de criar entradas na tabela de sımbolos do pro-
grama para cada vector declarado, de modo semelhante as restantes variaveis e funcoes. Por fim, o
componente gerador de codigo produz declaracoes, de modo a reservar para cada vector o numero de
bytes adequado, bem como operacoes de indexacao, entre outras. Estas sao efectuadas recorrendo
a aritmetica de ponteiros, atraves do calculo do deslocamento pretendido. Todas estas operacoes sao
descritas em maior detalhe nas proximas subseccoes.
3.2 Descricao da Arquitectura
Para o desenvolvimento do compilador, foi necessario elaborar as regras gramaticais que constituem
a analise sintactica da extensao da linguagem, definir a estrutura da tabela de sımbolos da analise
20
semantica e definir tambem a seleccao de instrucoes da fase de geracao de codigo, bem como as
operacoes aritmeticas associadas a reserva de memoria e indexacao dos novos vectores, abordadas
na Subseccao 3.2.2.
3.2.1 Extensoes Sintacticas
Para os novos vectores da extensao, utiliza-se uma sintaxe semelhante a ja existente para os vec-
tores tradicionais da linguagem C. A declaracao de um vector deste tipo segue as regras gramaticais
seguintes:
D → T N [ E ; S ]
S → E ; S | E | ε
Nas regras acima, sao sımbolos terminais os parentesis rectos e o ponto e vırgula, designando as
letras maiusculas os sımbolos nao terminais. Os sımbolos T , N e E designam, respectivamente, um
tipo, um nome e uma expressao numerica constante, de acordo com a gramatica da linguagem C ja
existente. De acordo com estas regras, para o programador declarar um destes vectores, devera faze-lo
indicando os valores maximos para cada dimensao, separados por ponto e vırgula. Estas regras apenas
identificam vectores com mais do que uma dimensao, pois a extensao da linguagem desenvolvida
apenas diz respeito a esse caso.
De modo semelhante, seja N o nome do vector, presente na tabela de sımbolos do programa, e X
uma expressao numerica (constante ou nao), a operacao de indexacao, identificada por I, realiza-se
de acordo com as regras seguintes:
I → N [ X ; P ]
P → X ; P | X
Por fim, para definir uma funcao F , que receba um destes vectores como argumento, seja C um
bloco de codigo que constitui o corpo da funcao, utilizam-se as seguintes regras:
21
F → T N ( A ) { C }
A → T M | T M , A | ε
M → N | N [ J ]
J → E ; K
K → J | E | ε
No calculo dos deslocamentos relativos a operacao de indexacao, optou-se por processar as di-
mensoes dos novos vectores no sentido da esquerda para a direita. Por este motivo, e permitido omitir
a ultima dimensao, como se justifica na proxima subseccao. Para as restantes operacoes da linguagem
estendida, mantem-se as propriedades da linguagem existente.
3.2.2 Verificacoes Semanticas
As regras gramaticais referidas foi necessario associar o comportamento correspondente do compi-
lador da extensao desenvolvida. A operacao de declaracao de um vector consiste em criar uma entrada
na tabela de sımbolos do compilador, associando cada nome a uma estrutura que contem o numero de
dimensoes do vector, n, e os tamanhos t1 . . . tn de cada dimensao. Deste modo, e possıvel calcular o
numero total de bytes a reservar para o vector, o qual, seja b o numero de bytes de cada elemento, e
dado por:
V = b
n∏i=1
ti (3.1)
De modo a reproduzir o comportamento das indexacoes de vectores na linguagem Fortran, descrito
na Seccao 2.4, a operacao de indexacao faz uso da referida estrutura de dados para efectuar o calculo
do endereco de memoria pretendido. Esse calculo consiste na aplicacao de um deslocamento ao
ponteiro para a posicao inicial do vector. Concretamente, para aceder a posicao [x1 . . . xn] de um vector
V com n dimensoes, de tamanhos t1 . . . tn, para n > 1, e inıcio na posicao de memoria p, sera calculado
o valor dado pela expressao:
V [x1 . . . xn] =
n∑i=1
xi i−1∏j=1
tj
b+ p (3.2)
Os vectores de dimensao 1, como ja referido, sao tratados do mesmo modo que na linguagem C
tradicional.
22
3.2.3 Geracao de Codigo C
A seleccao de instrucoes da linguagem C substitui declaracoes dos novos vectores por operacoes de
reserva estatica da quantidade de memoria correspondente. O valor a reservar sera o valor calculado
para V , segundo a Expressao (3.1), referida na subseccao anterior.
A indexacao corresponde ao calculo do deslocamento ja mencionado, o qual e aplicado ao ponteiro
para a posicao inicial do vector, reproduzindo o comportamento da linguagem Fortran. Matematica-
mente, conforme a Expressao (3.2), esta operacao corresponde a soma do valor desse ponteiro ao
somatorio dos ındices de cada dimensao, multiplicando cada um, em cada iteracao do calculo do so-
matorio, pelos tamanhos de todas as dimensoes processadas nas iteracoes anteriores. Apos se obter
o endereco resultante da aplicacao do deslocamento, pode ser realizada a leitura ou escrita do valor
guardado nesse mesmo endereco.
A passagem de um vector como argumento de uma funcao implica que sejam explicitados, no codigo
da sua definicao (bem como nas ocorrencias do seu prototipo, caso existam), os valores das dimensoes
do vector, de modo a que possam ser utilizados no calculo dos deslocamentos no contexto da funcao
chamada.
Exemplifica-se, na Listagem 3.3, o codigo C gerado para as operacoes de declaracao e passagem
como argumento dos novos vectores, de acordo com a extensao desenvolvida. O exemplo define uma
funcao que realiza a soma de todos os elementos de duas matrizes de 50 linhas por 60 colunas. Apos
ser processado pelo compilador desenvolvido, o codigo resultante sera como se ilustra na Listagem 3.4.
E de notar que, com esta implementacao, se torna desnecessario especificar a ultima dimensao de
cada vector, quer na sua declaracao, quer na passagem como argumento de uma funcao, pois a mesma
nunca e utilizada no calculo dos deslocamentos. Pode observar-se que o calculo do deslocamento para
o acesso a matriz mat2, no corpo da funcao total apenas inclui o tamanho da primeira dimensao.
Por este motivo, deixa-se ao criterio do programador a omissao dessa dimensao no uso deste tipo de
vectores. (De modo semelhante, os vectores tradicionais da linguagem C tambem permitem a omissao
de uma das dimensoes.)
23
Listagem 3.3: Soma dos elementos de duas matrizes (linguagem C estendida).
1 #include <stdio.h>
2
3 int v[50][60];
4 int u[50;60];
5
6 int total(int mat1[50][60], int mat2[50;60]) {
7 int i, j, res = 0;
8 for(i = 0; i < 50; ++i) {
9 for(j = 0; j < 60; ++j) {
10 res += mat1[i][j] + mat2[i;j];
11 }
12 }
13 return res;
14 }
15
16 int main() {
17
18 /* Inicializacao das matrizes
19 ...
20
21 */
22
23 printf("%d\n", total(v, u));
24 return 0;
25 }
As declaracoes dos vectores desta extensao originam declaracoes de vectores unidimensionais
do mesmo tipo. O seu tamanho e calculado a partir da aplicacao da Expressao (3.1), referida na
Subseccao 3.2.2, aos tamanhos de cada dimensao, especificados na declaracao. Deste modo, a
semelhanca das declaracoes de vectores na linguagem C tradicional, o programador apenas pode
utilizar expressoes constantes para especificar o tamanho de cada dimensao do vector. Os vectores
gerados terao, por regra, a forma de vectores unidimensionais da linguagem C tradicional, conforme se
ilustra na Listagem 3.4.
24
Listagem 3.4: Soma dos elementos de duas matrizes (linguagem C tradicional).
1 #include <stdio.h>
2
3 int v[50][60];
4 int u[(50) * (60)];
5
6 int total(int mat1[50][60], int mat2[]) {
7 int i, j, res = 0;
8 for(i = 0; i < 50; ++i) {
9 for(j = 0; j < 60; ++j) {
10 res += mat1[i][j] + mat2[(i) + (j) * (50)];
11 }
12 }
13 return res;
14 }
15
16 int main() {
17
18 /* Inicializacao das matrizes
19 ...
20
21 */
22
23 printf("%d\n", total(v, u));
24 return 0;
25 }
Se o programador pretender utilizar reserva dinamica de memoria, deve para isso invocar a funcao
de reserva que pretender, atribuindo o valor retornado por essa funcao a uma variavel do seu pro-
grama cujo tipo seja um vector da extensao da linguagem. Dada a diferenca de tipos entre o retorno
das funcoes de reserva e estes vectores, devera ser utilizada uma operacao de type cast do valor
retornado1, como se ilustra na Listagem 3.5.
1Esta operacao e tipicamente utilizada apos uma chamada a uma funcao de reserva de memoria, para ser possıvel tratar oconteudo do bloco reservado com o tipo de dados que o mesmo se destina a guardar.
25
Listagem 3.5: Alteracao do tipo do vector retornado pela funcao de reserva.
1 #include <stdlib.h>
2
3 int main() {
4 int u[;];
5 void *ptr = malloc(50*60*sizeof(int));
6
7 /* Cast aplicado ao valor da variavel 'ptr' */
8 u = (int[50;60]) ptr;
9
10 /* Utilizacao do vector
11 ...
12
13 */
14
15 free((void *) u);
16 return 0;
17 }
Ao realizar esta operacao de type cast, o programador tera a garantia de que o bloco de memoria
retornado pela funcao de reserva que invocou sera processado segundo as regras dos vectores da
extensao da linguagem. No entanto, fica a sua responsabilidade a gestao dessa mesma memoria, bem
como a sua libertacao. De acordo com as regras de geracao de codigo referidas, e devido a incompa-
tibilidade existente entre vectores estaticos e ponteiros na linguagem C, o programador devera, neste
caso, declarar o vector sem especificar os tamanhos das suas dimensoes. Este tipo de declaracao em
particular ira originar um ponteiro no codigo tradicional gerado, ao inves de um vector unidimensional. A
especificacao das dimensoes devera realizar-se apenas aquando da operacao de type cast, conforme
ilustrado.
3.2.4 Exemplos de Utilizacao
Como ja foi referido, quando a arquitectura do sistema fornece multiplicacoes ao nıvel de hard-
ware, e quando o seu custo computacional e inferior ao custo das operacoes de carregamento de
enderecos, e esperado que, quanto maior o numero de dimensoes dos vectores utilizados, melhor se
revele o seu desempenho, quando comparados aos vectores existentes na linguagem C tradicional.
Quando esta operacao necessita de ser realizada por software, torna-se necessario estudar o numero
de multiplicacoes de ındices realizadas pelos novos vectores face ao numero de carregamentos de
enderecos realizados pelos vectores tradicionais.
26
Considere-se agora o excerto de codigo da Listagem 3.6, o qual realiza acessos a dois vectores
bidimensionais. De acordo com as regras de geracao de codigo referidas, a chamada a funcao printf
resultara na seguinte linha de codigo, a qual acede a mesma posicao de cada vector de modo diferente:
printf("%d\n%d\n", v[50][60], u[(50) + (60) * (100)];
Considerando esta linha, gerou-se o codigo assembly correspondente, com recurso a ferramenta
GNU Compiler Collection (GCC) numa arquitectura Intel x86. Atraves da observacao do codigo resul-
tante, apresentado na Listagem 3.7. Atraves da sua observacao, e possıvel constatar que o carrega-
mento de um valor do vector v, o qual e um vector da linguagem C tradicional, implica a realizacao de
dois carregamentos de enderecos, realizados pelas duas ocorrencias da instrucao: movl (%eax), %eax.
Estes carregamentos correspondem a aplicacao sucessiva dos deslocamentos a cada ponteiro carre-
gado.
Listagem 3.6: Acessos a vectores de ambos os tipos (linguagem C).
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int **v;
5 int u[100;100];
6
7 int main() {
8 /* Reserva de memoria para 'v'
9 ...
10
11 */
12
13 printf("%d\n%d\n", v[50][60], u[50;60]);
14 return 0;
15 }
27
Listagem 3.7: Acessos a vectores de ambos os tipos (linguagem assembly).
1 movl u, %eax ; Ponteiro para o inicio de 'u'
2 addl $24200, %eax ; Soma do deslocamento a 'u'
3 ; d = (50+60*100)*4 bytes
4 movl (%eax), %edx ; Carregamento do endereco
5
6 movl v, %eax ; Ponteiro para o inicio de 'v'
7 addl $200, %eax ; Soma do deslocamento a 'v'
8 ; d1 = 50*4 bytes
9 movl (%eax), %eax ; Carregamento do endereco
10 addl $240, %eax ; Soma do deslocamento a 'v[50]'
11 ; d2 = 60*4 bytes
12 movl (%eax), %eax ; Carregamento do endereco
13
14 movl %edx, 8(%esp) ; Colocacao na pilha dos
15 movl %eax, 4(%esp) ; argumentos da funcao 'printf'
16 movl $.LC0, (%esp)
17 call printf
Por oposicao, o codigo assembly gerado pela operacao de indexacao do vector u, atraves da
aplicacao de um unico deslocamento ao ponteiro para a posicao inicial, revela-se mais curto, contendo
apenas um carregamento de endereco, realizado pela instrucao: movl (%eax), %edx.
Observa-se que a diferenca em termos de peso computacional de ambas as alternativas se prende
com a quantidade de operacoes de carregamento de enderecos geradas pelas operacoes com vecto-
res tradicionais, comparativamente as operacoes com os novos vectores. Estes carregamentos, iden-
tificaveis pela presenca de parentesis em torno de um dos operandos, ocorrem no codigo assembly
tantas vezes quanto o numero de dimensoes de um vector tradicional, mas apenas uma vez no codigo
resultante da extensao da linguagem.
Apresenta-se, na Listagem 3.8, um exemplo de codigo assembly gerado para uma operacao de
acesso a um vector tradicional com 6 dimensoes. Como esperado, neste caso, sao realizados 6 carre-
gamentos.
28
Listagem 3.8: Carregamento de um valor num vector com 6 dimensoes.
1 movl v, %eax
2
3 addl $200, %eax
4 movl (%eax), %eax
5
6 addl $240, %eax
7 movl (%eax), %eax
8
9 addl $280, %eax
10 movl (%eax), %eax
11
12 addl $320, %eax
13 movl (%eax), %eax
14
15 addl $360, %eax
16 movl (%eax), %eax
17
18 addl $400, %eax
19 movl (%eax), %eax ; Carregamento de
20 ; v[50][60][70][80][90][100]
Outra vantagem desta abordagem, a qual se observa na operacao de soma do deslocamento de
24200 bytes ao ponteiro para u, e a possibilidade de pre-processamento e optimizacao (pelo compila-
dor da linguagem C tradicional) das operacoes aritmeticas associadas as somas e multiplicacoes que
constituem o calculo desse mesmo deslocamento. Neste caso, visto que o acesso e realizado apenas
atraves de literais, todo o calculo pode ser realizado em tempo de compilacao, minimizando o custo
computacional em tempo de execucao. O mesmo e valido para qualquer tipo de expressao constante.
Alem desta optimizacao, como ja foi referido anteriormente, e de considerar a equivalencia existente
entre uma operacao de multiplicacao por uma potencia da forma 2n, para n > 0, e uma sequencia de n
operacoes de deslocamento a esquerda dos bits de um registo do processador. O custo computacional
deste tipo de operacoes e normalmente muito inferior ao de uma multiplicacao, pelo que, quando o
calculo de um deslocamento envolve multiplicacoes com operandos multiplos de 2, torna-se possıvel
decompo-la em deslocamentos a esquerda e multiplicacoes mais simples ou somas. Isto possibilita
diminuir o custo total do calculo em arquitecturas que nao fornecam a operacao de multiplicacao ao
nıvel de hardware. Aos exemplos ilustrados podem juntar-se problemas da area de calculo matricial,
como o calculo de produtos de matrizes, inversas de matrizes, determinantes, entre outros.
29
3.3 Metodologia de Avaliacao
Nesta seccao, apresentam-se os requisitos a que se sujeitou o processo de avaliacao da solucao
desenvolvida.
A correcta avaliacao da solucao exige medir os tempos medios de execucao de programas que
utilizem somente vectores tradicionais da linguagem C, comparando-os com os tempos medios de
execucao de programas identicos, em que apenas se substituam os vectores tradicionais por vectores
da extensao da linguagem.
Estas medicoes devem ser realizadas em sistemas com arquitecturas de CPU distintas, nomeada-
mente ARM e Intel, e com diferentes velocidades de relogio. Deste modo, torna-se possıvel concluir
sobre a influencia destes factores no desempenho da solucao. Idealmente, deve ser minimizado o
numero de processos em execucao concorrente nos sistemas de teste, de modo a que as medicoes de
desempenho sejam tao fiaveis quanto possıvel.
Os programas de teste devem incluir vectores com numeros de dimensoes iguais ou superiores a 2,
e com tamanhos tao elevados quanto possıvel, dependendo da quantidade de memoria disponıvel em
cada arquitectura.
Com esta metodologia, pretende-se obter e concluir sobre os racios de desempenho dos vectores da
extensao da linguagem face ao desempenho dos vectores tradicionais, em cada arquitectura referida.
3.4 Sumario
A solucao desenvolvida consiste num compilador que processa codigo escrito numa versao esten-
dida da linguagem C, a qual inclui vectores que replicam o comportamento dos mesmos na linguagem
Fortran. O codigo produzido por este compilador e codigo C tradicional, o que permite que o mesmo
seja processado por um compilador da linguagem ja existente.
A operacao de indexacao dos novos vectores e sempre realizada atraves do calculo de um deslo-
camento aplicado ao endereco da posicao inicial de cada vector, de modo a obter-se o comportamento
pretendido. Toda a restante funcionalidade da linguagem C mantem-se inalterada, de modo a possibili-
tar ao programador a escolha dos vectores que pretende utilizar em cada situacao. Essa decisao devera
depender das especificacoes do hardware disponıvel para a execucao dos programas desenvolvidos,
tendo em conta o desempenho comparativo de cada tipo de vectores nessas condicoes.
A sintaxe associada aos novos vectores e semelhante a ja existente para os vectores da linguagem
C. Dependendo das condicoes de hardware disponıveis, o programador podera alternar entre ambos
os tipos de vectores, de modo a maximizar a eficiencia dos seus programas.
30
4Realizacao
Conteudo
4.1 Funcionalidade da Solucao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
4.2 Flex: Fast Lexical Analyzer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
4.3 GNU Bison . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
4.4 Descricao Gramatical . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
4.5 Semantica e Geracao de Codigo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
4.6 Processo de Desenvolvimento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
4.7 Problemas Enfrentados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
31
32
Neste capıtulo e descrito o processo de desenvolvimento da solucao proposta, referindo-se as tec-
nologias e ferramentas utilizadas na concepcao da solucao.
Descrevem-se ainda as principais dificuldades e problemas encontrados ao longo deste processo.
4.1 Funcionalidade da Solucao
A ferramenta desenvolvida consiste numa aplicacao que recebe uma sequencia de entrada atraves
do canal standard input, produzindo uma sequencia de saıda no canal standard output. Como ja foi
referido, a entrada da aplicacao devera ser codigo escrito na versao estendida da linguagem C. A
saıda produzida pode entao ser visualizada na interface de linha de comandos utilizada para executar
a aplicacao, ou redireccionada para um ficheiro de codigo fonte da linguagem C tradicional.
O codigo gerado pela ferramenta desenvolvida consiste numa copia integral do codigo recebido
como entrada, com excepcao das operacoes referentes aos vectores que constituem a extensao da
linguagem. O codigo em comentario nao e processado, sendo tambem integralmente copiado para o
canal de saıda da aplicacao.
O compilador produzido suporta mudancas de ambiente de nomes, sendo possıvel a declaracao de
vectores da extensao da linguagem num determinado ambiente, realizando a indexacao num ambiente
de nıvel inferior. A indexacao em ambientes de nıvel superior nao e possıvel. A criacao de um ambiente
de nomes da-se quando o programador inicia um bloco de codigo, constituıdo por um conjunto de
instrucoes entre chavetas. Um bloco de instrucoes pode ou nao corresponder ao corpo de uma funcao
ou instrucao de controlo de fluxo, como um ciclo ou instrucao condicional. Caso existam, em ambientes
de nomes diferentes, duas ou mais variaveis com o mesmo nome, a indexacao referenciara a variavel
que se encontre no ambiente de nomes de nıvel mais baixo. A semelhanca dos vectores tradicionais, as
operacoes de indexacao podem ser encadeadas, utilizando o valor retornado por uma destas operacoes
como ındice de outra.
E suportada a definicao de tipos de dados em tempo de compilacao, com recurso a instrucao
typedef. Deste modo, e possıvel a declaracao, indexacao e passagem como argumento de vecto-
res com tipos definidos pelo programador. Estes poderao ser tipos primitivos da linguagem, ponteiros,
estruturas ou unioes.
A definicao de funcoes pode incluir, na sua lista de argumentos, vectores da extensao da linguagem
com qualquer numero de dimensoes. Quando tal acontece, e necessaria a especificacao do tamanho
de cada dimensao, com excepcao do ultimo. De modo semelhante, e possıvel a definicao de funcoes
cujos argumentos sejam ponteiros para outras funcoes, as quais podem, por sua vez, receber vectores
da extensao desenvolvida como argumentos. Tambem e possıvel a definicao de funcoes cujo valor de
retorno seja um ponteiro para uma funcao com vectores da extensao na lista de argumentos.
33
Por ultimo, como ja foi referido e exemplificado, e ainda possıvel a utilizacao e tratamento de pon-
teiros como vectores da extensao da linguagem. Deste modo, blocos de memoria reservada atraves
de funcoes de reserva da linguagem C podem ser convertidos em vectores da extensao. A conversao
realiza-se atraves de uma operacao de type cast aplicada a um ponteiro. No entanto, o resultado de
uma operacao deste tipo apenas pode ser atribuıdo a um vector da extensao que tenha sido decla-
rado sem qualquer especificacao de tamanhos das suas dimensoes. O tipo de dados especificado na
operacao de type cast devera ser igual ao tipo desse vector.
A Listagem 4.1 constitui uma possıvel sequencia de entrada da aplicacao desenvolvida, ilustrando
aspectos da funcionalidade descrita. A sequencia de saıda resultante do processamento apresenta-se
na Listagem 4.2.
Listagem 4.1: Exemplo de entrada do compilador.
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int ext array[6; 10; 20]; // Variavel global
5
6 int foo(int arg1, int *arg2[30; 50;], double arg3) {
7
8 /* Declaracoes */
9 int *ptr;
10 int ext array[3; 5; 10]; // Nome igual ao da variavel global
11 int trad array[4][6][11];
12 int cast array[;];
13
14 /* Indexacao dos vectores declarados localmente */
15 printf("%d\n", ext array[2; 4; 9] + trad array[3][5][10]);
16
17 /* Indexacao de um argumento; tamanho da ultima dimensao desnecessario */
18 ptr = arg2[29; 49; 99];
19
20 /* Encadeamento de indexacoes */
21 trad array[3 + ext array[3; ext array[0; 0; 0]; 10]][5][10];
22
23 /* Utilizacao de reserva dinamica de memoria */
24 ptr = (int *) malloc(45*sizeof(int));
25 cast array = (int [9; 5]) ptr; // Operacao de type cast
34
26 printf("%d\n", cast array[8; 4]);
27 free(cast array);
28
29 return 0;
30 }
31
32 int main(int argc, char **argv) {
33 printf("%d\n", ext array[5; 9; 19]); // Indexacao da variavel global
34 int *arg array[30; 50; 100];
35 return foo(1, arg array, 3.14);
36 }
Listagem 4.2: Exemplo de saıda do compilador.
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int ext array[(6) * (10) * (20)]; // Variavel global
5
6 int foo(int arg1, int *arg2[], double arg3) {
7
8 /* Declaracoes */
9 int *ptr;
10 int ext array[(3) * (5) * (10)]; // Nome igual ao da variavel global
11 int trad array[4][6][11];
12 int *cast array;
13
14 /* Indexacao dos vectores declarados localmente */
15 printf("%d\n", ext array[(2) + (4) * (3) + (9) * (3) * (5)]
16 + trad array[3][5][10]);
17
18 /* Indexacao de um argumento; tamanho da ultima dimensao desnecessario */
19 ptr = arg2[(29) + (49) * (30) + (99) * (30) * (50)];
20
21 /* Encadeamento de indexacoes */
22 trad array[3 + ext array[(3) +
23 (ext array[(0) + (0) * (3) + (0) * (3) * (5)]) * (3) +
24 (10) * (3) * (5)]][5][10];
35
25
26 /* Utilizacao de reserva dinamica de memoria */
27 ptr = (int *) malloc(45*sizeof(int));
28 cast array = (int *) ptr; // Operacao de type cast
29 printf("%d\n", cast array[(8) + (4) * (9)]);
30 free(cast array);
31
32 return 0;
33 }
34
35 int main(int argc, char **argv) {
36 printf("%d\n",
37 ext array[(5) + (9) * (6) +
38 (19) * (6) * (10)]); // Indexacao da variavel global
39 int *arg array[(30) * (50) * (100)];
40 return foo(1, arg array, 3.14);
41 }
4.2 Flex: Fast Lexical Analyzer
A ferramenta escolhida para a realizacao da analise lexical da solucao foi a ferramenta Flex. Esta
ferramenta e de utilizacao livre, de acordo com a GNU General Public License (GPL), pelo que nao sao
impostas quaisquer restricoes ou condicoes de uso a este projecto [11].
A ferramenta Flex permite gerar codigo-fonte nas linguagens C ou C++. Este, quando compilado,
produz um programa analisador de sequencias de entrada. O comportamento do analisador gerado e
definido por uma descricao contida num ficheiro fornecido a esta ferramenta. A descricao deve mapear
expressoes regulares em blocos de codigo C ou C++, dependendo da linguagem escolhida, os quais
sao executados pelo analisador lexical quando este identifica correspondencias entre a sequencia de
entrada e as expressoes regulares contidas no ficheiro de descricao [11]. Concretamente, cada bloco
de codigo consiste em converter num lexema (ou token) cada correspondencia entre a sequencia de
entrada e as expressoes regulares do ficheiro de descricao do analisador lexical. Os lexemas sao pro-
cessados pelo analisador sintactico pela mesma ordem em que sao reconhecidos ao nıvel do analisador
lexical, e podem ou nao ter um valor adicional associado, caso este seja necessario ao processamento
do lexema ao nıvel da analise sintactica [11]. O uso desta ferramenta permite complementar o compor-
tamento do analisador lexical produzido, atraves da ferramenta escolhida para a realizacao da restante
funcionalidade da solucao, que se descreve na seccao seguinte.
36
4.3 GNU Bison
Como ja foi referido na Seccao 3.1, para desenvolver o analisador sintactico, optou-se pela utilizacao
da ferramenta GNU Bison. Esta ferramenta, devido a sua natureza open source [11], permitiu testar a
correcta execucao da solucao em diferentes sistemas operativos.
A ferramenta Bison permite gerar programas de analise sintactica, nomeadamente analisadores
do tipo Look-Ahead Left-to-Right (LALR), complementando a funcionalidade da ja referida ferramenta
Flex [11, 17]. O comportamento dos analisadores sintacticos gerados provem de uma gramatica livre
de contexto, que devera ser fornecida como entrada da ferramenta Bison. O analisador sintactico e
responsavel pelo processamento sequencial de cada lexema proveniente do analisador lexical, identifi-
cando correspondencias entre os lexemas recebidos e as producoes das regras da gramatica fornecida
a ferramenta Bison [11].
Para este efeito, o analisador sintactico constroi um automato finito determinista que depende da
gramatica fornecida [17], e utiliza uma estrutura de dados com comportamento “last in, first out” (uma
pilha) para guardar os lexemas reconhecidos pelo analisador lexical [11]. Cada estado do automato
corresponde a um conjunto de lexemas reconhecidos e guardados na pilha do analisador sintactico
num dado momento, e cada transicao corresponde a uma operacao que deve ser realizada quando
o lexema seguinte e recebido. As operacoes que o automato realiza podem ser de deslocamento ou
de reducao. Uma operacao de deslocamento consiste na decisao de armazenar na pilha o lexema
recebido, transitando para um novo estado que acrescenta esse lexema ao conjunto de lexemas reco-
nhecidos [11,17]. Uma operacao de reducao ocorre quando e verificada uma correspondencia entre o
conjunto de lexemas reconhecidos e uma producao da gramatica que originou o automato. Neste caso,
da-se tambem uma transicao no automato, mas sao retirados da pilha os lexemas que constituem a
producao, reduzindo-se o tamanho da pilha [11,17].
Ao elaborar uma gramatica, e possıvel a existencia de regras que introduzam ambiguidade no
automato do analisador sintactico, ou seja, e possıvel a ocorrencia de conflitos no automato, que
possibilitem mais do que uma opcao de transicao de estado quando e reconhecido um mesmo le-
xema. Concretamente, e possıvel a ocorrencia de dois tipos de conflitos: deslocamento/reducao e
reducao/reducao [11].
Os conflitos do tipo deslocamento/reducao ocorrem quando um mesmo lexema pode, simultanea-
mente, provocar uma operacao de reducao, de acordo com uma producao presente na gramatica, ou
uma operacao de deslocamento, para mais tarde realizar uma operacao de reducao diferente, de acordo
com outra producao [11]. Um conflito de reducao/reducao ocorre quando um mesmo lexema permite
realizar duas operacoes de reducao diferentes, cada uma de acordo com uma producao diferente da
gramatica [11].
Os analisadores gerados pela ferramenta Bison classificam-se como analisadores LALR(1) [11,17].
37
Tratam-se de analisadores que percorrem a sequencia de lexemas no sentido da esquerda para a
direita, utilizando, em cada estado do automato, o lexema seguinte da sequencia em analise como
informacao auxiliar a resolucao de conflitos. Esta informacao, designada por lookahead, e util na
resolucao de conflitos do tipo deslocamento/reducao, pois pode permitir ao analisador determinar se a
operacao mais adequada numa situacao de conflito, dado um lexema de lookahead, e um deslocamento
ou uma reducao [11, 17]. Quanto maior a sequencia de lookahead, mais informacao e disponibilizada
ao analisador, e consequentemente, maior e tambem a sua capacidade de resolucao de conflitos deste
tipo1. Porem, uma sequencia de lookahead com mais do que um lexema provoca um aumento expo-
nencial no numero de estados do automato de um analisador, o que prejudica a sua viabilidade. Este
compromisso constitui a base da decisao de implementacao da ferramenta Bison como gerador de
analisadores sintacticos LALR(1) [17].
A gramatica fornecida a ferramenta Bison devera mapear as producoes das regras sintacticas em
blocos de instrucoes, os quais sao executados quando e realizada uma operacao de reducao. No caso
de a gramatica fornecida originar conflitos de deslocamento/reducao, o automato transita, por omissao,
para o estado correspondente a realizacao da operacao de deslocamento [11]. Quando ocorre um con-
flito de reducao/reducao, a transicao correspondera a regra da gramatica que ocorre em primeiro lugar
no ficheiro que a descreve [11]. Apesar de a sua ocorrencia nao impedir a compilacao do programa,
deve ser evitada a existencia de conflitos na gramatica, pois o automato resultante podera provocar
comportamentos indesejados por parte do analisador sintactico [11].
4.4 Descricao Gramatical
O ficheiro de descricao gramatical elaborado possui regras que, a medida que ocorrem operacoes
de reducao do analisador sintactico, produzem o codigo C tradicional a ser gerado. Este codigo e
armazenado em memoria sob a forma de cadeias de caracteres sucessivamente mais longas. Estas
cadeias de caracteres sao uma copia da entrada do compilador desenvolvido, com excepcao do codigo
respeitante aos vectores da extensao da linguagem C, o qual e modificado de acordo com as regras
sintacticas escolhidas para a solucao, mencionadas anteriormente, e com o seu significado semantico.
Da regra principal da gramatica elaborada, a qual especifica as primeiras ramificacoes da arvore
sintactica da solucao, fazem parte quatro producoes de maior destaque que as restantes, pois dizem
respeito as operacoes essenciais dos vectores da extensao desenvolvida. Estas operacoes, referidas
anteriormente, sao nomeadamente a declaracao, indexacao, passagem como argumento de funcao e
type cast de vectores da extensao da linguagem C.
A indexacao de um vector e sintacticamente semelhante a uma operacao de declaracao, sendo
1De forma generica, um analisador LALR(k) utiliza k lexemas de lookahead.
38
constituıda pelo nome atribuıdo ao vector, ao qual se seguem os ındices indicados pelo programador,
entre parentesis rectos e separados pelo sımbolo ponto e vırgula. A sintaxe da operacao de declaracao
inclui a sintaxe da indexacao, precedendo-a apenas pelo tipo de dados escolhido para o vector que se
pretende declarar. Estas operacoes recorrem a uma estrutura de dados, do tipo struct symbol, a qual
associa o nome dado a um determinado vector da extensao aos tamanhos das dimensoes que lhe sao
atribuıdas na sua declaracao.
De um modo geral, procurou-se reproduzir as regras sintacticas descritas na Subseccao 3.2.1, de
forma tao fiel quanto possıvel. Porem, a elaboracao da descricao gramatical revelou-se mais complexa
do que inicialmente esperado, devido ao requisito de nao introduzir incompatibilidades com a sintaxe
da linguagem C tradicional, como se vera na Seccao 4.7.
4.5 Semantica e Geracao de Codigo
As estruturas do tipo struct symbol sao organizadas em memoria sob a forma de uma lista ligada,
a qual constitui a tabela de sımbolos para um determinado ambiente (scope) do programa. Cada tabela
de sımbolos constitui, por sua vez, um elemento do tipo struct scope. Este tipo de dados organiza-se
em memoria sob a forma de uma pilha, sendo criada uma nova entrada de cada vez que e reconhecida
a existencia de um novo ambiente local. Quando o codigo do novo ambiente termina, e removida
a entrada da pilha criada mais recentemente. Esta pilha possui sempre, no mınimo, uma entrada,
correspondente ao ambiente global do programa.
A traducao de uma operacao de declaracao em codigo C tradicional corresponde a declaracao de
um vector tradicional unidimensional, cujo tamanho e o produto de todos os tamanhos especificados
para cada dimensao do vector da extensao. O reconhecimento de uma operacao deste tipo tem como
resultado o acrescimo de uma nova entrada na tabela de sımbolos do ambiente no topo da pilha de
elementos do tipo struct symbol.
Algoritmo 4.1: Geracao de codigo de uma declaracao.begin
tamanhos←− entrada.tamanhoscodigo←− εAcrescentaSimbolo(entrada, tabela)
while TemElementos(tamanhos) do
Concatena(codigo, '(' +indices.elementoActual+ ')' )tamanhos←− tamanhos.proximoElemento
if TemElementos(tamanhos) thenConcatena(codigo, '*' )
39
O reconhecimento da ocorrencia da definicao de uma funcao que receba como argumento um vector
da extensao desenvolvida introduz tambem alteracoes na tabela de sımbolos do programa. Porem,
neste caso, os sımbolos de cada vector da lista de argumentos da funcao sao salvaguardados num
novo ambiente, que corresponde ao ambiente local da funcao.
A indexacao de um vector depende da presenca do seu nome na tabela de sımbolos, pois apenas e
possıvel gerar o codigo correspondente a esta operacao com recurso a informacao sobre os tamanhos
das dimensoes do vector, obtida atraves do acesso a entrada correspondente da tabela de sımbolos
que a contem. A procura de um nome para obtencao da informacao necessaria a geracao do codigo da
operacao de indexacao da-se a partir do topo da pilha de ambientes do programa. E percorrida cada
tabela de sımbolos ate se obter a primeira correspondencia, momento apos o qual a procura termina e
e realizada a geracao do codigo, de acordo com o Algoritmo 4.2. A informacao necessaria a geracao
do codigo da operacao encontra-se presente na lista de ındices introduzidos pelo programador, bem
como na lista de dimensoes do vector a indexar, contida na estrutura de dados resultante da procura.
Algoritmo 4.2: Geracao de codigo de uma indexacao.begin
simbolo←− ProcuraSimbolo(entrada.nome, tabela)tamanhos←− simbolo.tamanhosindices←− entrada.indicescodigo←− εcontadorIndices←− 0limiteIndices←− 0
while TemElementos(tamanhos) and TemElementos(indices) do
if limiteIndices > 0 thenConcatena(codigo, '+' )
Concatena(codigo, '(' +indices.elementoActual+ ')' )
while contadorIndices < limiteIndices doConcatena(codigo, '*' + '(' +tamanhos.elementoActual+ ')' )tamanhos←− tamanhos.proximoElementocontadorIndices←− contadorIndices+ 1
tamanhos←− tamanhos.primeiroElementoindices←− indices.proximoElementocontadorIndices←− 0limiteIndices←− limiteIndices+ 1
Quando e realizada uma tentativa de indexacao de um vector que nao tenha sido declarado no am-
biente em que a mesma se realiza, ou seja, quando a procura de um sımbolo nas tabelas de sımbolos
do programa nao tem sucesso, e indicado um erro semantico ao programador, e o processamento e
abortado.
Por fim, a realizacao de uma operacao de type cast de um ponteiro so e considerada valida se o
40
mesmo tiver sido declarado sem especificacao dos tamanhos das suas dimensoes, conforme ilustrado
no exemplo da Listagem 3.5 da Subseccao 3.2.3. Este tipo de declaracao origina uma operacao de
declaracao de um ponteiro no codigo gerado, ao inves de um vector unidimensional. O ponteiro gerado
tem o mesmo tipo do vector declarado, e a tabela de sımbolos e, neste caso, actualizada apenas com
informacao sobre o nome da variavel declarada.
A operacao de type cast em si, tal como no exemplo referido, e realizada atraves do uso, entre
parentesis, do tipo de vector pretendido, juntamente com a especificacao dos tamanhos das dimensoes
do vector. O reconhecimento desta operacao origina uma procura nas tabelas de sımbolos semelhante
a que e realizada para gerar o codigo de uma operacao de indexacao. Porem, esta procura serve
para acrescentar a informacao acerca das dimensoes do vector a entrada correspondente, possibili-
tando a partir daı a realizacao de indexacoes. Caso haja uma tentativa de indexacao de um vector
cujas dimensoes nao tenham sido especificadas, o processamento termina com a indicacao de um erro
semantico.
4.6 Processo de Desenvolvimento
O processo de desenvolvimento comecou com a observacao e analise de exemplos de ficheiros de
descricao lexical e gramatical, respectivamente destinados as ferramentas Flex e Bison, para a lingua-
gem C tradicional. Isto permitiu a familiarizacao e aprendizagem de regras sintacticas da linguagem
C passıveis de interferir com as regras da solucao desenvolvida. De especial destaque sao as regras
relativas ao reconhecimento de operacoes com vectores tradicionais, as quais tiveram de ser replica-
das no ficheiro de descricao gramatical da solucao, de modo a evitar erros sintacticos provenientes da
utilizacao de vectores tradicionais pelo programador.
Para desenvolver o codigo da solucao, atraves das ferramentas Flex e Bison, optou-se pela lingua-
gem C. Essa mesma linguagem foi utilizada tambem para a criacao de uma biblioteca de funcoes de
auxılio ao processamento da entrada da solucao, realizacao de validacoes semanticas e construcao das
sequencias de saıda. As funcoes da biblioteca em questao sao invocadas quer no codigo da descricao
lexical da ferramenta Flex, quer no codigo da descricao gramatical da ferramenta Bison.
4.6.1 Tipos
Concretamente, no que diz respeito a descricao lexical, revelou-se importante o reconhecimento de
algumas palavras reservadas da linguagem C, nomeadamente os sımbolos que designam os tipos da
linguagem. Estes sımbolos, como se vera mais adiante, sao importantes para distinguir sintacticamente
as operacoes de declaracao de vectores das operacoes de indexacao. Nao so os sımbolos que desig-
nam tipos, como int, char ou double, mas tambem os sımbolos struct, union e typedef, pela relacao
41
que tem com as anteriores, se revelaram importantes na analise sintactica. Numa primeira abordagem,
optou-se por distinguir com um lexema diferente cada sımbolo referente a um tipo da linguagem. Na
versao final, porem, visto que essa distincao se revelou desnecessaria, apenas os sımbolos struct,
union e typedef possuem lexemas unicos. Todos os outros, ao serem reconhecidos, produzem um
lexema que designa genericamente um tipo, sem o especificar.
4.6.2 Identificadores
Revelou-se igualmente importante o reconhecimento de sımbolos alfanumericos, com o acrescimo
do caracter underscore, para realizar o reconhecimento de identificadores de variaveis. Estes sımbolos
constituem possıveis nomes para os vectores a processar pelo compilador desenvolvido, e sao tambem
identificados com um lexema proprio. Para o processamento das definicoes de funcoes, foi necessario
reconhecer, alem destes, os sımbolos: vırgula, parentesis curvos, chavetas e reticencias. Por fim, o
processamento dos vectores em si, de acordo com a sintaxe escolhida para os mesmos, exigiu ainda o
reconhecimento dos sımbolos: parentesis rectos e ponto e vırgula. Cada lexema produzido e comuni-
cado ao analisador sintactico faz-se sempre acompanhar pelo texto efectivamente reconhecido, sob a
forma de um atributo, uma vez que a geracao de codigo produzira, na sua maioria, texto semelhante,
ou mesmo igual a sequencia de entrada.
4.6.3 Comentarios
Optou-se por nao processar texto em comentario no codigo-fonte da linguagem estendida, pelo que
se realiza tambem o reconhecimento dos sımbolos da linguagem C que indicam o inıcio e final de co-
mentarios. Estes lexemas em particular provocam uma alteracao de estado do analisador lexical. Neste
estado, sao ignoradas todas as regras definidas no ficheiro de descricao lexical, produzindo-se sempre
o mesmo lexema, independentemente do texto recebido como entrada. Este lexema indica ao anali-
sador sintactico que deve ser copiado o texto recebido, integralmente e sem qualquer processamento.
As excepcoes a este comportamento sao os sımbolos da linguagem C que indicam o final do texto em
comentario, fazendo com que o analisador lexical recupere o seu comportamento regular.
4.6.4 Declaracoes
Na analise sintactica, como ja foi referido, sao utilizados os lexemas provenientes da analise lexi-
cal para emparelhar sequencias dos mesmos, recebidas como entrada, com as regras definidas na
descricao gramatical. As regras de declaracao de vectores tem por base as regras de indexacao,
sendo apenas acrescentada uma lista de tipos que a antecede, e que permite distinguir entre as duas
operacoes. Quando o analisador sintactico realiza uma operacao de reducao de acordo com a regra
42
de declaracao de um vector, o codigo gerado corresponde a declaracao de um vector tradicional do
mesmo tipo, com uma unica dimensao, e cujo tamanho corresponde ao produto de todas as dimensoes
indicadas pelo programador. A estrutura do tipo struct symbol correspondente e entao criada, arma-
zenando uma cadeia de caracteres, que contem o nome da variavel declarada, bem como uma lista
ligada de outras cadeias de caracteres. Estas outras cadeias contem o texto que o programador utilizou
para especificar os tamanhos das dimensoes do vector declarado. A estrutura de dados produzida e
entao guardada de imediato na estrutura de dados que constitui a tabela de sımbolos do programa,
tambem ela uma lista ligada, que armazena, em cada entrada, uma entidade do tipo struct symbol.
Optou-se por nao realizar qualquer reconhecimento de expressoes nem validacoes semanticas para
determinar o valor das dimensoes especificadas numa operacao de declaracao, pois o codigo C tradici-
onal correspondente as operacoes de indexacao destes vectores pode ser gerado independentemente
do valor semantico dos sımbolos introduzidos pelo programador, sejam eles literais numericos, ex-
pressoes, variaveis, chamadas de funcoes, constantes ou outros sımbolos. Cabe, portanto, ao compila-
dor da linguagem C tradicional a validacao semantica dos sımbolos introduzidos como dimensoes dos
vectores da extensao da linguagem.
4.6.5 Indexacoes
Quando uma operacao de reducao se da de acordo com a regra de indexacao de um vector, e
realizada uma procura pela primeira ocorrencia de uma entidade do tipo struct symbol cujo nome
corresponda a variavel indexada. Como ja foi referido, a procura tem inıcio no ambiente em que a
indexacao se realiza, percorrendo-se a pilha de ambientes sucessivamente, ate ser atingido o ambi-
ente global. Quando uma procura nao obtem nenhum resultado, a indexacao nao e possıvel, pelo que
e indicado um erro semantico e a geracao de codigo e interrompida. Quando e encontrada uma cor-
respondencia, obtem-se a lista de dimensoes indicadas na declaracao da variavel, que se encontra
guardada na entidade do tipo struct symbol correspondente. Procede-se entao a geracao do codigo
C tradicional para calcular o ındice pretendido. O codigo e gerado de acordo com a Expressao (3.2),
mencionada na Subseccao 3.2.2, e com o Algoritmo 4.2.
4.6.6 Funcoes
No que diz respeito a definicao de funcoes, uma reducao de acordo com a regra correspondente da
gramatica realiza o equivalente a uma operacao de declaracao por cada vector da extensao passado
como argumento da funcao definida. No entanto, ao inves de especificar o seu tamanho como o pro-
duto de todas as dimensoes do vector, o codigo C tradicional gerado nao inclui qualquer informacao
a esse respeito, visto que os vectores tradicionais originados sao unidimensionais. Como ja se referiu
43
anteriormente, a linguagem C permite a omissao do tamanho da primeira dimensao de vectores pas-
sados como argumento de funcoes, pelo que, neste caso, essa informacao e inteiramente ignorada. A
definicao de uma funcao e tambem responsavel pela criacao de um novo ambiente de nomes, sob a
forma de uma nova estrutura do tipo struct scope, a qual e acrescentada ao topo da pilha de ambi-
entes de nomes. Os nomes dos vectores passados como argumentos sao entao incluıdos neste novo
ambiente, ficando assim disponıveis para operacoes de indexacao no corpo da funcao.
De modo semelhante as definicoes de funcoes, tambem qualquer bloco de instrucoes delimitado por
chavetas origina a criacao de um novo ambiente de nomes. Por associacao, este caso inclui ocorrencias
de instrucoes de controlo de fluxo, como ciclos e instrucoes condicionais, quando as mesmas sao
sucedidas por blocos de instrucoes.
4.7 Problemas Enfrentados
O desenvolvimento do analisador sintactico da solucao suscitou obstaculos a programacao, nome-
adamente a ocorrencia de conflitos na gramatica produzida, entre outros problemas, que a seguir se
descrevem.
A linguagem C tradicional permite a utilizacao de sequencias arbitrarias de caracteres de mudanca
de linha, espacos em branco ou tabulacoes, sem que a sua presenca tenha qualquer influencia na
analise sintactica da linguagem. Dada a natureza da solucao desenvolvida, de modo a preservar a
legibilidade do codigo gerado a partir do codigo da extensao da linguagem, tornou-se requisito da
solucao a replicacao dessas mesmas sequencias de caracteres de forma tao fiel quanto possıvel. Para
o efeito, optou-se inicialmente por realizar o reconhecimento destes caracteres ao nıvel da analise le-
xical, processando-os como lexemas integrantes nas regras gramaticais da analise sintactica. Esta
abordagem originou a ocorrencia de numerosos conflitos na gramatica produzida, pois requeria a in-
clusao destes lexemas em todas as regras gramaticais. Isto levou a introducao de conflitos na gramatica
resultante, o que tornou inviavel o compromisso entre permissividade destes caracteres e correcta fun-
cionalidade da solucao.
Numa segunda abordagem, optou-se pela utilizacao de um vector de caracteres, partilhado pelos
analisadores lexical e sintactico, utilizado para acumular os referidos caracteres a medida que sao reco-
nhecidos na fase de analise lexical. Ao ser reconhecido um lexema, a sua comunicacao ao analisador
sintactico faz-se acompanhar pela concatenacao dos caracteres guardados no vector com o texto que
constitui o lexema. Esta estrategia torna possıvel a geracao de codigo com indentacao semelhante ao
codigo recebido como entrada da solucao, satisfazendo o requisito de preservacao da legibilidade do
mesmo. O uso desta abordagem obrigou, no entanto, a cuidados especiais na procura de sımbolos
nas tabelas de sımbolos, pois e essencial para isso considerar apenas os caracteres alfanumericos e
44
underscore, descartando os restantes.
Uma outra dificuldade encontrada consistiu na incorrecta identificacao de lexemas que constituem
tipos de dados definidos em tempo de compilacao, pois os sımbolos que lhes dao origem nao sao
palavras reservadas da linguagem C tradicional. Por este motivo, ao nıvel da analise lexical, os mes-
mos produziam lexemas correspondentes a identificadores de variaveis, que provocavam a deteccao
de erros sintacticos em sequencias de entrada que deveriam ser consideradas validas pelo analisa-
dor sintactico. Para resolver este problema, tornou-se essencial o processamento de operacoes de
definicao de tipos, acrescentando regras a gramatica para o processamento do uso da palavra reser-
vada typedef. Optou-se pela criacao de uma lista ligada de cadeias de caracteres, partilhada por
ambos os analisadores. Esta lista e preenchida pelo analisador sintactico quando este realiza uma
operacao de reducao de acordo com a regra correspondente a uma definicao de tipo, guardando-se o
nome do tipo definido. Ao reconhecer um sımbolo candidato a producao de um lexema identificador
de variavel, o analisador lexical pode entao aceder a esta lista, para determinar se o mesmo foi previ-
amente definido como tipo. A decisao sobre o lexema a produzir da-se conforme o texto reconhecido
esteja ou nao presente na lista de tipos definidos pelo programador.
Ainda no que diz respeito ao reconhecimento de tipos de dados, surgiram dificuldades na distincao
entre analise sintactica e semantica das listas de tipos introduzidas nas operacoes de declaracao de
vectores. Inicialmente, optou-se por distinguir sintacticamente os sımbolos referentes a tipos, como
int, char ou float, e modificadores de tipos, como long, short ou unsigned. Esta distincao a nıvel
sintactico nao se revelou possıvel, pois exigia a validacao de todas as permutacoes possıveis de tipos
e modificadores, atraves da criacao de uma regra na gramatica para cada caso. Era necessario permi-
tir tambem o uso de apenas modificadores, sem tipo. Esta particularidade foi responsavel pela vasta
introducao de conflitos de deslocamento/reducao na gramatica. Na realidade, este tipo de verificacoes
deve ser feito a nıvel da analise semantica, testando apenas nessa fase a validade da sequencia in-
troduzida como lista de tipos que qualificam uma variavel. Por este motivo, optou-se por considerar
sintacticamente valida qualquer sequencia de lexemas de tipo, o que tornou a sua distincao desne-
cessaria tambem a nıvel lexical. A validacao semantica dessas sequencias e entao delegada para o
compilador da linguagem C tradicional utilizado juntamente com a solucao desenvolvida.
Inicialmente, surgiram tambem conflitos na gramatica como consequencia do processamento de
vectores unidimensionais, quer tradicionais, quer da extensao desenvolvida. As regras gramaticais
referentes ao processamento de ambos os tipos de vectores incluıam o caso em que o numero de
dimensoes e igual a um, apesar da sua igualdade sintactica e semantica. Esta sobreposicao originou
conflitos de reducao/reducao, os quais foram resolvidos atraves da remocao deste caso particular no
processamento de vectores da extensao da linguagem.
45
46
5Avaliacao da Solucao
Conteudo
5.1 Modelos de Teste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
5.2 Discussao dos Resultados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
47
48
Para testar a solucao desenvolvida, criaram-se alguns exemplos de algoritmos que realizam opera-
coes com matrizes. Os mesmos foram implementados em duas versoes: a primeira versao utiliza
apenas vectores da linguagem C tradicional, enquanto a segunda versao substitui todos esses vectores
por vectores da extensao da linguagem.
Os programas que implementam os referidos algoritmos, que em seguida se descrevem, foram
compilados e executados em tres arquitecturas diferentes: um processador Intel x86-64, com frequencia
de 1.80 GHz; um outro processador Intel x86-64, com frequencia de 2.00 GHz, com tecnologia Intel
Turbo Boost [18], e um processador ARMv6, com frequencia de 700 MHz. Calcularam-se os racios
de desempenho entre ambas as versoes de cada algoritmo, de modo a concluir sobre as vantagens
e desvantagens do uso da solucao implementada. Seja n o numero de testes realizados sob cada
conjunto de condicoes, e sejam si e tj dois valores medidos para os tempos de execucao de um
programa, para a versao com e sem recurso aos vectores da extensao da linguagem, respectivamente,
o racio de desempenho entre versoes de cada programa e dado por:
R% = 100
1−
n∑i=1
si
n∑j=1
tj
(5.1)
5.1 Modelos de Teste
Os programas desenvolvidos realizam uma serie de operacoes matematicas sobre vectores multi-
dimensionais e matrizes, nomeadamente o calculo de somas de vectores e produtos de matrizes, bem
como o calculo do determinante de uma matriz quadrada. Cada algoritmo utiliza diferentes estrategias
de acesso as posicoes de cada vector ou matriz, como se vera mais adiante. O Apendice A contem
as listagens do codigo destes programas, implementados na versao estendida da linguagem C. Estas
listagens servem tambem como exemplos de utilizacao da solucao desenvolvida.
5.1.1 Soma de Vectores Tridimensionais
O primeiro algoritmo implementado realiza o calculo da soma de dois vectores tridimensionais.
Implementou-se este algoritmo com recurso a acessos sequenciais as posicoes dos vectores cuja
soma se pretende obter. O codigo elaborado pode ser consultado no Apendice A.1. O programa
realiza acessos sequenciais a posicoes consecutivas dos vectores que se pretendem somar, de modo
a conseguir-se o vector que constitui o resultado da operacao.
Aplicou-se este algoritmo a vectores tridimensionais, preenchidos com valores aleatorios do tipo
int, e com dimensoes de 25x50x75, 40x60x80 e 100x90x80 posicoes. O programa foi elaborado
em duas versoes. A primeira versao, que constitui a Listagem A.1, recorre apenas a vectores da
49
Tabela 5.1: Desempenho da soma de vectores num processador Intel x86-64 a 1.80 GHz.
Dimensoes 20x50x75 40x60x80 100x90x80Versao Ext. Trad. Ext. Trad. Ext. Trad.
Media (s) 0,042 0,022 0,103 0,041 0,408 0,149D. Padrao 0,000 0,000 0,000 0,000 0,002 0,000Racio (%) -90,000 -151,220 -173,691
Tabela 5.2: Desempenho da soma de vectores num processador Intel x86-64 a 2.00 GHz.
Dimensoes 20x50x75 40x60x80 100x90x80Versao Ext. Trad. Ext. Trad. Ext. Trad.
Media (s) 0,013 0,010 0,026 0,018 0,081 0,050D. Padrao 0,002 0,003 0,004 0,005 0,013 0,010Racio (%) -33,673 -43,716 -61,800
extensao desenvolvida, recorrendo a outra versao apenas a vectores tradicionais da linguagem C, em
substituicao dos mesmos. Realizaram-se dez medicoes dos tempos de execucao, em segundos, de
cada versao do programa, quando executado sobre cada uma das arquitecturas de processador ja
referidas. Posteriormente, calcularam-se as medias dos tempos medidos e o racio de desempenho em
cada arquitectura.
Os resultados obtidos apresentam-se nas Tabelas 5.1, 5.2 e 5.3. Os tempos medidos nao con-
templam a execucao do codigo em comentario na Listagem A.1, o qual apenas e responsavel por
apresentar o resultado da operacao, como saıda do programa.
5.1.2 Produto de Matrizes Quadradas
O segundo algoritmo implementado consiste no calculo do produto de duas matrizes quadradas.
Esta operacao envolve acessos sequenciais as posicoes de cada linha da primeira matriz, e de cada
coluna da segunda matriz. O codigo de implementacao deste algoritmo pode ser consultado no Apendi-
ce A.2.
O algoritmo foi aplicado a tres pares de matrizes quadradas, de dimensoes 100x100, 250x250 e
500x500. Aplicou-se um tratamento estatıstico semelhante ao processo descrito na subseccao anterior.
O programa foi elaborado em duas versoes, uma das quais apenas utiliza vectores da extensao da
linguagem, recorrendo a outra versao apenas a vectores tradicionais. Executou-se entao cada versao
do programa sobre cada uma das arquitecturas de processador ja referidas.
Realizaram-se dez medicoes de tempos de execucao por cada conjunto de condicoes de teste,
apos as quais se calcularam as medias de tempos de execucao e racios de desempenho entre versoes
correspondentes do programa.
Tambem neste caso se optou por colocar em comentario o codigo responsavel pela apresentacao do
resultado da operacao. Por este motivo, a sua execucao nao e contemplada nos tempos de execucao
50
Tabela 5.3: Desempenho da soma de vectores num processador ARMv6 a 700 MHz.
Dimensoes 20x50x75 40x60x80 100x90x80Versao Ext. Trad. Ext. Trad. Ext. Trad.
Media (s) 0,170 0,077 0,339 0,138 1,223 0,513D. Padrao 0,001 0,001 0,001 0,000 0,005 0,001Racio (%) -121,802 -144,902 -138,262
Tabela 5.4: Desempenho do produto de matrizes num processador Intel x86-64 a 1.80 GHz.
Dimensoes 100x100 250x250 500x500Versao Ext. Trad. Ext. Trad. Ext. Trad.
Media (s) 0,043 0,046 0,660 0,932 6,784 8,700D. Padrao 0,000 0,000 0,008 0,002 0,003 0,005Racio (%) 6,725 29,198 22,023
medidos, que podem ser consultados no Apendice B. Os valores resultantes apresentam-se nas Tabe-
las 5.4, 5.5 e 5.6.
5.1.3 Determinante: Formula de Leibniz
Implementou-se o calculo do determinante de uma matriz atraves da aplicacao da Formula de Leib-
niz. O codigo do programa em questao, com recurso a vectores da extensao da linguagem, bem como
uma breve explicacao do algoritmo, podem ser consultados no Apendice A.3. Este programa, em cada
iteracao a excepcao da primeira, realiza acessos a posicoes nao contıguas da matriz cujo determinante
se pretende calcular.
Aplicou-se o algoritmo a matrizes quadradas de valores aleatorios do tipo double, de dimensoes
9x9, 10x10, 11x11 e 12x12. De forma semelhante aos algoritmos anteriormente referidos, alem da
versao listada no Apendice A, foi tambem elaborada uma versao que substitui os vectores da extensao
por vectores tradicionais da linguagem C.
O programa foi compilado e executado sobre as arquitecturas de processador ja referidas. Realizou-
se cada teste um total de dez vezes, recolhendo-se os tempos de execucao de cada teste, em segun-
dos, que se apresentam no Apendice B. Excepcionalmente, no caso da arquitectura ARMv6, nao se
realizou o tratamento estatıstico para o calculo do determinante da matriz de 12x12 posicoes. Neste
caso, verificou-se que cada execucao requeria mais de 10 minutos para concluir, o que inviabilizou a
realizacao de medicoes.
Apos os testes, calculou-se a media de cada conjunto de dez medicoes e, por fim, o racio entre a
media dos tempos de execucao do programa que utiliza os vectores da extensao e a media dos tempos
de execucao do programa correspondente, que apenas utiliza vectores tradicionais. Os resultados dos
calculos referidos, para cada arquitectura de processador, apresentam-se nas Tabelas 5.7, 5.8 e 5.9.
51
Tabela 5.5: Desempenho do produto de matrizes num processador Intel x86-64 a 2.00 GHz.
Dimensoes 100x100 250x250 500x500Versao Ext. Trad. Ext. Trad. Ext. Trad.
Media (s) 0,017 0,020 0,189 0,174 1,023 1,115D. Padrao 0,002 0,004 0,014 0,032 0,019 0,035Racio (%) 15,764 -8,913 8,293
Tabela 5.6: Desempenho do produto de matrizes num processador ARMv6 a 700 MHz.
Dimensoes 100x100 250x250 500x500Versao Ext. Trad. Ext. Trad. Ext. Trad.
Media (s) 0,150 0,136 2,511 2,324 28,988 28,498D. Padrao 0,001 0,001 0,007 0,008 0,024 0,197Racio (%) -10,677 -8,041 -1,720
5.2 Discussao dos Resultados
A observacao dos resultados obtidos torna evidente que, alem da diferenca de arquitecturas de pro-
cessador, como referido na Seccao 3.3, tambem o funcionamento de cada algoritmo implementado tem
influencia sobre o desempenho dos vectores da extensao desenvolvida, face aos vectores tradicionais.
Observando, em primeiro lugar, os resultados presentes nas Tabelas 5.1 e 5.2, verifica-se uma
drastica quebra de desempenho quando se utilizam os vectores da extensao da linguagem. Este
fenomeno podera ser explicado pela implementacao das instrucoes de carregamento de valores, nos
processadores em teste, de acordo com o princıpio da localidade espacial [19]. Segundo este princıpio,
apos um acesso a uma posicao de memoria no espaco de enderecamento de um programa, a proba-
bilidade de a ele se seguirem acessos a posicoes proximas da mesma e elevada. Por este motivo, a
realizacao de uma operacao de carregamento de um endereco na memoria principal de um sistema,
e consequente colocacao do valor carregado na memoria cache do processador, consiste realmente
no carregamento de uma gama de enderecos, em que se inclui o endereco pretendido. Esta pratica
permite minimizar o numero de falhas de cache ao longo da execucao de um programa, tornando mais
rapidas as operacoes de carregamento de enderecos consecutivos [19].
A optimizacao referida tem um notavel efeito no desempenho dos vectores tradicionais da lingua-
gem C, quando aplicados ao algoritmo de soma de vectores. Neste caso, o desempenho dos vecto-
res da extensao da linguagem e prejudicado pela latencia introduzida pelas operacoes de calculo de
cada deslocamento utilizado para indexar os vectores. O impacto desta latencia, como seria esperado,
revela-se ainda maior quando o algoritmo e executado sobre a arquitectura ARMv6, como se observa
na Tabela 5.3. Como ja foi referido, a latencia introduzida pelas operacoes de multiplicacao, neste tipo
de arquitectura, e superior a latencia das mesmas em arquitecturas Intel, o que estara na origem da
diferenca entre os racios de desempenho presentes na tabela em questao, face as anteriores.
As Tabelas 5.4, 5.5 e 5.6, referentes ao algoritmo de calculo do produto de matrizes, revelam racios
52
Tabela 5.7: Desempenho da formula de Leibniz num processador Intel x86-64 a 1.80 GHz.
Dimensoes 9x9 10x10 11x11 12x12Versao Ext. Trad. Ext. Trad. Ext. Trad. Ext. Trad.
Media (s) 0,109 0,113 1,186 1,226 14,021 14,495 174,244 180,330D. Padrao 0,000 0,000 0,001 0,002 0,003 0,008 0,034 0,022Racio (%) 3,540 3,279 3,270 3,375
Tabela 5.8: Desempenho da formula de Leibniz num processador Intel x86-64 a 2.00 GHz.
Dimensoes 9x9 10x10 11x11 12x12Versao Ext. Trad. Ext. Trad. Ext. Trad. Ext. Trad.
Media (s) 0,036 0,051 0,400 0,427 4,045 4,294 50,090 51,712D. Padrao 0,008 0,012 0,023 0,037 0,058 0,079 0,637 0,267Racio (%) 30,469 6,367 5,803 3,135
de desempenho com valores mais heterogeneos que os anteriores, e com maiores oscilacoes em torno
de zero. Estas oscilacoes sao particularmente visıveis na Tabela 5.5. Porem, e tambem nesta tabela
que se verificam os maiores valores de desvio padrao, relativamente a media de tempos obtida. Pode
notar-se que, quer no teste com matrizes de 100x100 posicoes, quer no teste com 250x250 posicoes, o
valor do desvio padrao corresponde a cerca de 10% da media obtida, no caso dos vectores da extensao
da linguagem, e cerca de 20%, no caso dos vectores tradicionais. Tais oscilacoes terao sido provocadas
pela presenca da tecnologia Intel Turbo Boost nesta arquitectura de CPU em particular. Esta tecnologia
aumenta e reduz dinamicamente a velocidade de relogio do processador, consoante a carga a que o
mesmo esta sujeito em cada momento [18, 20]. Por este motivo, os dados obtidos a partir dos testes
realizados sobre esta arquitectura terao sido influenciados por variacoes dinamicas da velocidade de
relogio do processador, uma vez que esta tecnologia podera ou nao ter sido activada durante algumas
das medicoes. A semelhanca da Tabela 5.5, tambem nas Tabelas 5.2 e 5.8 se podem notar elevados
valores de desvio padrao, ocorrencia que torna esta arquitectura menos fiavel que as restantes para a
extraccao de conclusoes.
Comparando, no entanto, as Tabelas 5.4 e 5.6, verifica-se que, no que toca ao algoritmo de multiplica-
cao de matrizes, a necessidade de carregamento de valores da memoria principal e suficiente para que,
na arquitectura Intel, seja mais vantajosa a alternativa de calculo de deslocamentos. Contrariamente a
soma de vectores, este algoritmo realiza sempre indexacoes de posicoes nao consecutivas de uma das
matrizes, provocando um numero superior de falhas de cache em relacao ao algoritmo de soma. Na
arquitectura ARMv6, por outro lado, a latencia introduzida pelos calculos de deslocamentos tem ainda
um impacto demasiado elevado nos tempos de execucao.
Por fim, a observacao das Tabelas 5.7, 5.8 e 5.9 revela que, contrariamente aos outros algoritmos
testados, o calculo do determinante de uma matriz segundo a formula de Leibniz apresenta apenas
racios de desempenho positivos. O elevado numero de falhas de cache originado pela execucao deste
53
Tabela 5.9: Desempenho da formula de Leibniz num processador ARMv6 a 700 MHz.
Dimensoes 9x9 10x10 11x11Versao Ext. Trad. Ext. Trad. Ext. Trad.
Media (s) 0,455 0,470 4,812 4,858 56,653 57,289D. Padrao 0,001 0,028 0,001 0,001 0,003 0,008Racio (%) 3,151 0,955 1,111
algoritmo revela mais vantajosa a utilizacao dos vectores da extensao desenvolvida, ainda que os racios
de desempenho, na sua maioria, nao sejam muito superiores a 3%. Como seria esperado, a arquitec-
tura ARMv6 e a que apresenta valores mais baixos, devido a latencia introduzida pelos calculos dos
deslocamentos dos vectores da extensao da linguagem.
54
6Conclusao
Conteudo
6.1 Conclusoes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
6.2 Limitacoes e Trabalho Futuro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
55
56
6.1 Conclusoes
Foi proposta uma solucao para a necessidade de combinar mais do que uma linguagem de progra-
macao quando se pretende, num mesmo programa, utilizar vectores com diferentes modalidades de
acesso e diferentes organizacoes em memoria. Foram analisadas as vantagens e desvantagens do uso
de vectores nas linguagens C e Fortran, e descreveu-se o funcionamento tradicional de uma ferramenta
de compilacao, essencial ao desenvolvimento da solucao proposta: uma extensao da linguagem C com
integracao de vectores com o comportamento daqueles que a linguagem Fortran fornece.
Descreveu-se a arquitectura da solucao proposta, referindo-se quais as regras gramaticais propos-
tas para a extensao da linguagem C, quais as principais verificacoes semanticas que sera necessario
realizar pelo compilador, bem como quais as instrucoes da linguagem C tradicional que deverao estar
associadas as novas instrucoes da linguagem estendida.
De seguida, exemplificou-se a utilizacao da solucao, referindo-se as principais diferencas observa-
veis no codigo assembly gerado para as operacoes de indexacao de cada tipo de vectores. Com
base nessas diferencas, fundamentou-se a dependencia do desempenho da solucao nas condicoes de
hardware disponıveis para a execucao dos programas desenvolvidos.
A realizacao da solucao consistiu na construcao de cadeias de caracteres que constituem o codigo
a gerar como saıda da solucao desenvolvida. O texto construıdo e identico ao texto de entrada, com
excepcao das alteracoes resultantes do processamento de operacoes com vectores que utilizem a
sintaxe acrescentada a gramatica da linguagem linguagem. Entre as operacoes a destacar incluem-se
a declaracao, indexacao e passagem de vectores como argumentos de funcoes. Tambem e permitido
o uso da operacao de type cast sobre um ponteiro, para que o conteudo do endereco de memoria
indicado pelo mesmo seja tratado como um vector da extensao desenvolvida.
A avaliacao comparativa do desempenho dos dois tipos de vectores revelou que a decisao acerca
de qual deve ser utilizado no desenvolvimento de um programa nao deve depender apenas do tipo
de arquitectura de processador utilizado, mas tambem do funcionamento do algoritmo implementado.
Quanto menor a latencia introduzida pelas operacoes de multiplicacao na arquitectura responsavel
pela execucao do programa, e quanto maior o numero de falhas de cache que a execucao provoca,
melhor sera o desempenho dos vectores da extensao desenvolvida, face aos vectores tradicionais da
linguagem C.
6.2 Limitacoes e Trabalho Futuro
Apesar da funcionalidade desenvolvida no contexto deste trabalho, a solucao nao se encontra com-
pleta, pois existem operacoes sintactica e semanticamente validas na modalidade tradicional de vec-
tores que nao sao suportadas pelos vectores da extensao desenvolvida. Operacoes como a definicao
57
de estruturas de dados contendo vectores da extensao como membros, ou a declaracao e indexacao
de vectores de ponteiros para funcoes, sao exemplos de funcionalidade que permanece por realizar.
Tambem nao e suportado o processamento de ficheiros de cabecalho e bibliotecas da linguagem for-
necidas pelo compilador de codigo C tradicional.
Com vista a melhorar a usabilidade da solucao, a mesma podera ainda ser incorporada numa ferra-
menta unica, que combine a execucao da aplicacao desenvolvida com a execucao de um compilador
da linguagem C tradicional. Esta ferramenta podera tambem permitir a activacao ou desactivacao do
uso da semantica dos vectores da extensao da linguagem, visto ter-se comprovado que a mesma nem
sempre e vantajosa. Para o efeito, poder-se-a utilizar um parametro (flag) de execucao do compilador,
que possibilite, em tempo de compilacao, a conversao de todos vectores da extensao da linguagem em
vectores tradicionais. Desta forma, e possıvel ignorar por completo a sintaxe e semantica da extensao
da linguagem, sem a necessidade de alteracao do codigo dos programas a compilar. A conversao in-
versa, porem, nao e possıvel, devido as limitacoes ja descritas nesta seccao, bem como as restricoes
impostas pela nova semantica de vectores que nao podem ser replicadas com os vectores tradicionais,
como a operacao de type cast.
Relativamente a possibilidades de expansao do estudo realizado, ficou por explorar o impacto de
diversas optimizacoes de compilacao de programas na linguagem C, das quais sao exemplos a reducao
de forca de operacoes (strength reduction), referida na Seccao 2.7, ou a regra de strict aliasing, que
possibilita ganhos de desempenho num programa ao impedir a sobreposicao de ponteiros de tipos
diferentes.
No que toca a trabalhos futuros de investigacao, podera ser pertinente o estudo da viabilidade e
desenvolvimento de uma extensao semelhante a que se desenvolveu, aplicada a outras linguagens de
programacao, como a linguagem C++.
58
Bibliografia
[1] P. V. D. Linden, Expert C Programming: Deep C Secrets. Prentice Hall Press, 1994.
[2] B. W. Kernighan and D. M. Ritchie, The C Programming Language, 2nd ed. Upper Saddle River,
NJ, USA: Prentice Hall Press, 1988, vol. 78.
[3] A. S. Tanenbaum, Modern Operating Systems, 2nd ed. Upper Saddle River, NJ, USA: Prentice
Hall Press, 2001.
[4] D. Graves and C. Hogue, Fortran 77 Language Reference Manual, 1994.
[5] J. W. Backus and W. P. Heising, “Fortran,” IEEE Transactions on Electronic Computers, vol. EC-13,
1964.
[6] ANSI, “USA Standard FORTRAN (USAS X3.9-1966),” 1966.
[7] J. C. Adams, W. S. Brainerd, R. a. Hendrickson, R. E. Maine, and J. T. Martin, The Fortran 2003
Handbook, 2008.
[8] P. Kleinrubatscher, A. Kriegshaber, R. Zochling, and R. Gluck, “Fortran Program Specialization,”
ACM SIGPLAN Notices, vol. 30, 1995.
[9] R. W. Numrich and J. Reid, “Co-array Fortran for Parallel Programming,” ACM SIGPLAN Fortran
Forum, vol. 17, no. 2, 1998.
[10] A. V. Aho, M. S. Lam, R. Sethi, and J. D. Ullman, Compilers: Principles, Techniques, and Tools,
2nd ed., 2006.
[11] J. Levine, T. Mason, and D. Brown, Lex & Yacc, 2nd ed. O’Reilly, 1992.
[12] S. McConnell, Code Complete, 2nd ed. Redmond, WA, USA: Dreamtech Press, 2004.
[13] T. A. Davis and K. Sigmon, MATLAB Primer, 7th ed. Boca Raton, FL, USA: CRC Press, Inc., 2005.
[14] L. de Rose and D. Padua, “Techniques for the translation of MATLAB programs into Fortran 90,”
ACM Transactions on Programming Languages and Systems, vol. 21, pp. 286–323, 1999.
59
[15] J. W. Eaton, GNU Octave, 2007, vol. 103.
[16] J. S. Hansen, GNU Octave Beginner’s Guide, 2011.
[17] T. J. Pennello and F. DeRemer, “Efficient Computation of LALR(1) Look-ahead Sets,” SIGPLAN
Not., vol. 39, no. 4, pp. 14–27, 2004.
[18] J. Casazza, “Intel® Turbo Boost Technology in Intel® Core™ Microarchitecture (Nehalem) Based
Processors,” White paper, Intel Corp, no. November, 2008.
[19] D. A. Patterson and J. L. Hennessy, Computer Organization and Design: The Hardware/Software
Interface, 2009, vol. 4th, no. 0.
[20] J. Charles, P. Jassi, A. Narayan, A. Sadat, and A. Fedorova, “Evaluation of the Intel Core i7 Turbo
Boost Feature,” Proceedings of the 2009 IEEE International Symposium on Workload Characteri-
zation, IISWC 2009, pp. 188–197, 2009.
60
ACodigo de Avaliacao
Apresentam-se em seguida as listagens de codigo dos programas utilizados para testar a solucao
desenvolvida. Cada listagem e acompanhada por uma breve descricao do funcionamento do algoritmo.
A.1 Soma de Vectores Tridimensionais
A Listagem A.1 apresenta o codigo do programa implementado para o calculo da soma de dois
vectores de tres dimensoes. Os vectores sao declarados nas linhas 20 e 21, e preenchidos com valores
aleatorios nas linhas 24 a 31. A funcao sum, definida nas linhas 10 a 16, indexa sequencialmente cada
posicao dos vectores A e B, recebidos como argumentos. Em cada posicao do vector C, guarda-se o
resultado da soma dos valores guardados nas correspondentes posicoes dos vectores A e B.
61
Listagem A.1: Soma de dois vectores tridimensionais.
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 #define X 25
5 #define Y 50
6 #define Z 75
7
8 int C[X; Y; Z];
9
10 void sum(int A[X; Y; Z], int B[X; Y; Z]) {
11 int i, j, k;
12 for(i = 0; i < X; ++i)
13 for(j = 0; j < Y; ++j)
14 for(k = 0; k < Z; ++k)
15 C[i; j; k] = A[i; j; k] + B[i; j; k];
16 }
17
18 int main() {
19 int i, j, k;
20 int A[X; Y; Z];
21 int B[X; Y; Z];
22
23 srand(64777);
24 for(i = 0; i < X; ++i) {
25 for(j = 0; j < Y; ++j) {
26 for(k = 0; k < Z; ++k) {
27 A[i; j; k] = rand();
28 B[i; j; k] = rand();
29 }
30 }
31 }
32
33 sum(A, B);
34 /*
35 printf("==========\n");
36 for(i = 0; i < X; ++i) {
37 for(j = 0; j < Y; ++j) {
62
38 printf("|\t");
39 for(k = 0; k < Z; ++k)
40 printf("%d\t", C[i; j; k]);
41 printf("|\n");
42 }
43 printf("\n==========\n\n");
44 }
45 */
46 return 0;
47 }
A.2 Produto de Matrizes Quadradas
O codigo do programa de calculo do produto de duas matrizes apresenta-se na Listagem A.2. Este
programa declara e inicializa, com valores aleatorios, duas matrizes quadradas, A e B, cada uma com
o mesmo numero de linhas e colunas. O resultado do produto e guardado numa terceira matriz, C.
Para determinar o valor de cada posicao da matriz C, a funcao multiply, definida nas linhas 8 a 14,
multiplica todos os valores da linha correspondente na matriz A, incrementando o ındice da coluna, pelos
respectivos valores da coluna correspondente na matriz B, incrementando ındice da linha. Cada um
destes produtos constitui uma parcela do valor que devera ocupar a posicao da matriz C correspondente
aos ındices fixados nas matrizes A e B.
Listagem A.2: Produto de duas matrizes quadradas.
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 #define N 100
5
6 int C[N; N];
7
8 void multiply(int A[N;], int B[N;]) {
9 int i, j, k;
10 for(i = 0; i < N; ++i)
11 for(j = 0; j < N; ++j)
12 for(k = 0; k < N; ++k)
13 C[i; j] += A[i; k] * B[k; j];
63
14 }
15
16 int main() {
17 int i, j;
18 int A[N; N];
19 int B[N; N];
20
21 srand(64777);
22 for(i = 0; i < N; ++i) {
23 for(j = 0; j < N; ++j) {
24 A[i; j] = rand();
25 B[i; j] = rand();
26 }
27 }
28
29 multiply(A, B);
30 /*
31 for(i = 0; i < N; ++i) {
32 printf("|\t");
33 for(j = 0; j < N; ++j)
34 printf("%d\t", C[i; j]);
35 printf("|\n");
36 }
37 */
38 return 0;
39 }
A.3 Determinante: Formula de Leibniz
O algoritmo implementado para o calculo do determinante de uma matriz recorre a aplicacao da
formula de Leibniz. O algoritmo itera sobre as posicoes da matriz, percorrendo os ındices das linhas de
forma crescente e sucessiva. Os ındices das colunas variam de acordo com permutacoes de um vector
de ındices, inicialmente contendo uma sequencia tambem crescente e sucessiva.
No codigo da Listagem A.3, o referido vector e declarado e inicializado nas linhas 40 a 42. O mesmo
e passado como argumento da funcao leibniz, definida nas linhas 22 a 37. Sao entao criadas tantas
novas sequencias de ındices quantas as possıveis permutacoes da sequencia original. Para o efeito,
em cada chamada recursiva da funcao leibniz, determinam-se todas as permutacoes possıveis de
64
um elemento do vector com os elementos em posicoes seguintes ao mesmo (linhas 31 a 35). Cada
permutacao origina uma troca de sinal do argumento signature, que pode ter o valor 1 ou -1. Tambem
se realiza uma chamada recursiva que preserva o vector de ındices, sem realizar uma permutacao, caso
em que o argumento signature nao troca de sinal. O ciclo presente nas linhas 31 a 35 e responsavel
por ramificar a execucao do programa, de modo a conseguir um caminho de execucao para cada
permutacao dos elementos da sequencia de ındices mencionada.
No caso de paragem da recursao, na linha 25, o vector contem uma sequencia de ındices ja proces-
sada, e e entao utilizado para indexar a matriz cujo determinante se pretende calcular. Cada acesso
a matriz realiza-se com um ındice de linha incrementado unitariamente, e com o ındice de coluna indi-
cado pelo vector. Acumula-se entao o produto de todos os elementos acedidos numa variavel auxiliar.
O resultado deste produto e multiplicado pelo valor do argumento signature, que sera igual a 1 ou -1,
consoante tenha sido realizado um numero par ou ımpar de permutacoes, respectivamente. Este valor
corresponde a uma parcela do determinante da matriz, o qual, apos a chamada da funcao leibniz, na
linha 44, fica guardado na variavel global det.
Listagem A.3: Formula de Leibniz para o determinante de uma matriz.
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 #define N 10
5
6 double A[N;N], det = 0;
7
8 void fill matrix(unsigned int seed) {
9 int i, j;
10 srand(seed);
11 for(i = 0; i < N; ++i)
12 for(j = 0; j < N; ++j)
13 A[i;j] = rand();
14 }
15
16 void swap(int *first, int *second) {
17 int temp = *first;
18 *first = *second;
19 *second = temp;
20 }
65
21
22 void leibniz(int *array, int current, int size, int signature) {
23 int a;
24 double temp = 1;
25 if (current == size - 1) {
26 for (a = 0; a < size; ++a)
27 temp *= A[array[a];a];
28 det += signature * temp;
29 }
30 else {
31 for (a = current; a < size; ++a) {
32 swap(array + current, array + a);
33 leibniz(array, current + 1, size, current == a ? signature : -signature);
34 swap(array + current, array + a);
35 }
36 }
37 }
38
39 int main() {
40 int a, indices[N];
41 for (a = 0; a < N; ++a)
42 indices[a] = a;
43 fill matrix(64777);
44 leibniz(indices, 0, N, 1);
45 printf("det(A) = %.1f\n", det);
46 return 0;
47 }
66
BTempos de Execucao
Em seguida, apresentam-se os tempos, em segundos, medidos para cada execucao dos programas
de teste da solucao desenvolvida, referidos na Seccao 5.1. Na mesma seccao, apresentam-se os
resultados da analise estatıstica destes dados. O codigo de cada programa pode ser consultado no
Apendice A.
67
Tabela B.1: Execucao da soma de vectores num processador Intel x86-64 a 1.80 GHz.
Condicoes de Teste100x100 250x250 500x500
Medicoes Ext. Trad. Ext. Trad. Ext. Trad.1 0,042 0,022 0,103 0,041 0,408 0,1492 0,042 0,022 0,103 0,041 0,412 0,1493 0,042 0,022 0,103 0,041 0,407 0,1494 0,042 0,022 0,103 0,041 0,407 0,1495 0,041 0,022 0,103 0,041 0,406 0,1496 0,042 0,022 0,103 0,041 0,409 0,1497 0,042 0,022 0,103 0,041 0,407 0,1498 0,042 0,022 0,103 0,041 0,408 0,1499 0,042 0,022 0,103 0,041 0,406 0,149
10 0,041 0,022 0,103 0,041 0,408 0,149Media 0,042 0,022 0,103 0,041 0,408 0,149
D. Padrao 0,000 0,000 0,000 0,000 0,002 0,000
Tabela B.2: Execucao da soma de vectores num processador Intel x86-64 a 2.00 GHz.
Condicoes de Teste100x100 250x250 500x500
Medicoes Ext. Trad. Ext. Trad. Ext. Trad.1 0,012 0,018 0,024 0,031 0,070 0,0512 0,017 0,009 0,032 0,018 0,075 0,0623 0,013 0,008 0,036 0,014 0,088 0,0404 0,016 0,009 0,025 0,019 0,109 0,0675 0,012 0,009 0,025 0,021 0,089 0,0616 0,012 0,009 0,024 0,014 0,089 0,0427 0,015 0,012 0,024 0,014 0,073 0,0548 0,011 0,008 0,025 0,014 0,079 0,0409 0,012 0,008 0,021 0,017 0,069 0,039
10 0,011 0,008 0,027 0,021 0,068 0,044Media 0,013 0,010 0,026 0,018 0,081 0,050
D. Padrao 0,002 0,003 0,004 0,005 0,013 0,010
Tabela B.3: Execucao da soma de vectores num processador ARMv6 a 700 MHz.
Condicoes de Teste100x100 250x250 500x500
Medicoes Ext. Trad. Ext. Trad. Ext. Trad.1 0,169 0,077 0,339 0,139 1,234 0,5152 0,169 0,077 0,337 0,138 1,220 0,5143 0,172 0,078 0,339 0,138 1,221 0,5124 0,171 0,077 0,340 0,138 1,222 0,5155 0,170 0,076 0,340 0,138 1,228 0,5126 0,169 0,076 0,340 0,138 1,225 0,5117 0,171 0,077 0,337 0,139 1,222 0,5138 0,169 0,076 0,337 0,138 1,220 0,5149 0,169 0,076 0,338 0,138 1,219 0,514
10 0,170 0,076 0,340 0,139 1,219 0,513Media 0,170 0,077 0,339 0,138 1,223 0,513
D. Padrao 0,001 0,001 0,001 0,000 0,005 0,001
68
Tabela B.4: Execucao do produto de matrizes num processador Intel x86-64 a 1.80 GHz.
Condicoes de Teste100x100 250x250 500x500
Medicoes Ext. Trad. Ext. Trad. Ext. Trad.1 0,043 0,046 0,656 0,932 6,785 8,6972 0,043 0,046 0,657 0,935 6,780 8,6983 0,043 0,046 0,654 0,930 6,783 8,6984 0,043 0,046 0,655 0,935 6,780 8,7135 0,043 0,046 0,665 0,931 6,786 8,7016 0,043 0,046 0,652 0,931 6,783 8,6947 0,043 0,046 0,654 0,932 6,790 8,7018 0,043 0,046 0,674 0,932 6,781 8,6979 0,043 0,047 0,658 0,931 6,787 8,697
10 0,043 0,046 0,673 0,930 6,783 8,701Media 0,043 0,046 0,660 0,932 6,784 8,700
D. Padrao 0,000 0,000 0,008 0,002 0,003 0,005
Tabela B.5: Execucao do produto de matrizes num processador Intel x86-64 a 2.00 GHz.
Condicoes de Teste100x100 250x250 500x500
Medicoes Ext. Trad. Ext. Trad. Ext. Trad.1 0,018 0,024 0,175 0,148 1,044 1,1502 0,018 0,023 0,190 0,116 1,029 1,1243 0,016 0,022 0,180 0,129 1,025 1,1274 0,022 0,023 0,211 0,177 1,001 1,0945 0,017 0,019 0,200 0,186 0,997 1,1076 0,017 0,022 0,172 0,182 1,053 1,1877 0,014 0,018 0,191 0,207 1,038 1,1148 0,015 0,015 0,190 0,186 1,008 1,0819 0,017 0,014 0,211 0,203 1,010 1,064
10 0,017 0,023 0,174 0,205 1,024 1,106Media 0,017 0,020 0,189 0,174 1,023 1,115
D. Padrao 0,002 0,004 0,014 0,032 0,019 0,035
Tabela B.6: Execucao do produto de matrizes num processador ARMv6 a 700 MHz.
Condicoes de Teste100x100 250x250 500x500
Medicoes Ext. Trad. Ext. Trad. Ext. Trad.1 0,151 0,137 2,512 2,315 29,031 28,4022 0,149 0,135 2,498 2,324 28,990 28,3963 0,154 0,135 2,507 2,317 28,995 28,2594 0,150 0,136 2,526 2,310 29,005 28,4175 0,149 0,135 2,514 2,325 28,957 28,3896 0,151 0,137 2,512 2,332 28,986 28,8107 0,150 0,136 2,514 2,330 29,008 28,8298 0,150 0,135 2,507 2,324 28,954 28,3699 0,149 0,136 2,507 2,334 28,972 28,66010 0,150 0,136 2,516 2,333 28,981 28,445
Media 0,150 0,136 2,511 2,324 28,988 28,498D. Padrao 0,001 0,001 0,007 0,008 0,024 0,197
69
Tabela B.7: Execucao da formula de Leibniz num processador Intel x86-64 a 1.80 GHz.
Condicoes de Teste9x9 10x10 11x11 12x12
Medicoes Ext. Trad. Ext. Trad. Ext. Trad. Ext. Trad.1 0,109 0,113 1,185 1,225 14,017 14,497 174,257 180,3872 0,109 0,113 1,185 1,230 14,021 14,487 174,217 180,3363 0,109 0,113 1,186 1,227 14,023 14,497 174,197 180,3244 0,109 0,113 1,188 1,225 14,018 14,505 174,240 180,3225 0,109 0,113 1,185 1,226 14,023 14,490 174,252 180,3136 0,109 0,113 1,186 1,226 14,020 14,488 174,219 180,3157 0,109 0,113 1,186 1,225 14,015 14,497 174,319 180,3148 0,109 0,113 1,186 1,226 14,024 14,507 174,269 180,3299 0,109 0,113 1,185 1,225 14,021 14,494 174,241 180,34510 0,109 0,113 1,186 1,225 14,024 14,483 174,226 180,318
Media 0,109 0,113 1,186 1,226 14,021 14,495 174,244 180,330D. Padrao 0,000 0,000 0,001 0,002 0,003 0,008 0,034 0,022
Tabela B.8: Execucao da formula de Leibniz num processador Intel x86-64 a 2.00 GHz.
Condicoes de Teste9x9 10x10 11x11 12x12
Medicoes Ext. Trad. Ext. Trad. Ext. Trad. Ext. Trad.1 0,032 0,040 0,396 0,412 4,080 4,368 49,495 52,2662 0,036 0,036 0,416 0,424 4,000 4,292 50,365 52,0573 0,036 0,036 0,408 0,404 4,084 4,208 50,211 51,6424 0,036 0,060 0,392 0,404 4,068 4,160 50,638 51,3315 0,040 0,060 0,412 0,436 4,108 4,232 51,526 51,7106 0,056 0,060 0,428 0,452 4,124 4,436 49,999 51,5107 0,032 0,060 0,400 0,512 4,032 4,296 49,525 51,6688 0,028 0,036 0,344 0,408 4,016 4,332 49,719 51,7009 0,032 0,064 0,392 0,444 4,008 4,316 49,931 51,569
10 0,028 0,060 0,412 0,376 3,932 4,304 49,493 51,662Media 0,036 0,051 0,400 0,427 4,045 4,294 50,090 51,712
D. Padrao 0,008 0,012 0,023 0,037 0,058 0,079 0,637 0,267
Tabela B.9: Execucao da formula de Leibniz num processador ARMv6 a 700 MHz.
Condicoes de Teste9x9 10x10 11x11
Medicoes Ext. Trad. Ext. Trad. Ext. Trad.1 0,455 0,461 4,812 4,859 56,648 57,2952 0,455 0,550 4,813 4,859 56,651 57,2863 0,455 0,463 4,810 4,858 56,649 57,2864 0,454 0,459 4,812 4,858 56,649 57,2885 0,456 0,462 4,811 4,857 56,652 57,3096 0,456 0,461 4,813 4,859 56,657 57,2847 0,455 0,462 4,813 4,860 56,655 57,2858 0,455 0,459 4,812 4,857 56,654 57,2869 0,454 0,459 4,812 4,858 56,655 57,28910 0,454 0,461 4,812 4,859 56,657 57,286
Media 0,455 0,470 4,812 4,858 56,653 57,289D. Padrao 0,001 0,028 0,001 0,001 0,003 0,008
70