Desenvolvimento - C/C++

Estudo comparativo da velocidade dos programas gerados por 3 compiladores utilizando Open GL: C++ Builder vs. Visual C++ vs. Dev-C++

Dentre várias vantagens da linguagem C/C++, o fato de ser padronizada e independente sempre me chamou a atenção. A liberdade de escolher o compilador de qualquer fabricante permite ao programador suprir suas necessidades respeitando os recursos existentes para um dado projeto de programação. Entretanto, freqüentemente vejo nos fóruns de discussão dúvidas sobre qual ferramenta de desenvolvimento utilizar e mesmo um programador experiente tem algumas dificuldades para discernir qual é o melhor compilador. O objetivo deste artigo é comparar 3 compiladores na característica mais importante para um programa escrito em C.

por Adenilson Cavalcanti da Silva



Introdução

Dentre várias vantagens da linguagem C/C++, o fato de ser padronizada e independente sempre me chamou a atenção. A liberdade de escolher o compilador de qualquer fabricante permite ao programador suprir suas necessidades respeitando os recursos existentes para um dado projeto de programação. Entretanto, frequentemente, vejo nos fóruns de discussão dúvidas sobre qual ferramenta de desenvolvimento utilizar e mesmo um programador experiente tem algumas dificuldades para discernir qual é o melhor compilador. O objetivo deste artigo é comparar 3 compiladores na característica mais importante para um programa escrito em C: a velocidade.

Na busca de aumentar a participação no mercado, os fabricantes de compiladores exibem orgulhosos tabelas com listagens de características diversas de seus produtos. Suporte a arquiteturas distribuídas, componentes para Web, bibliotecas para melhorar a produtividade e muitos outros listam nas tabelas de características dos produtos. Curiosamente, um item nunca presente nestas comparações é a velocidade dos programas gerados pelos compiladores.

Programas escritos em linguagem ANSI C/C++ são passíveis de compilação em qualquer ferramenta de desenvolvimento, independente do sistema operacional utilizado. Assim, oferecem o mecanismo perfeito para testar a eficiência do programa gerado pelo compilador.

Estudo de caso

São muitas as áreas da ciência da computação consideradas intensivas, entretanto, é consenso que computação gráfica é onde são levados aos limites o hardware e o software. Assim, esta área surge como uma escolha natural em um trabalho onde o objetivo é aferir velocidade de programas gerados por diferentes compiladores.

O programa presente neste artigo utilizará OpenGL, se você não faz idéia sobre o que estamos falando, sugiro passar no www.opengl.org onde existem centenas de links e tutoriais para todos os níveis de conhecimento. Talvez no futuro eu venha a escrever um artigo para iniciantes em OpenGL..... Se lhe interessa, mande e-mail! :-)

Existem muitas bibliotecas de programação gráfica disponíveis para uso com o C/C++, embora para gráficos com 3 dimensões sempre se pense em 2 opções: DirectX (módulo Direct 3-D) e OpenGL. A discussão sobre qual API é melhor já despertou inúmeras brigas e até grandes nomes na área como John Romero (o criador de Quake, Wolfenstein, Doom) já opinaram sobre o assunto (Thomas, 1998). Discutir vantagens e desvantagens de cada API exigiria um novo artigo, embora seja possível apresentar no quadro 1 algumas características importantes sobre elas. Leia e tente tirar suas conclusões.

Pessoalmente, concordo com o Romero: OpenGL é mais fácil de utilizar apesar de ser considerada de "baixo nível". Sem fugir ao assunto, escolhida a API o programa de teste é baseado em um demo lançado em conjunto com a GLUT (GL Utility Toolkit) e foi escrito por Simon Parkinson-Bates (a.k.a. Parko). Este programa desenha na tela um robô, sendo acessível um menu com diversas opções. A figura 1 apresenta o visual do programa.

Tabela 1:Comparativo de OpenGL e Direct 3D

Fator OpenGL Direct 3-D
Portabilidade Mac, Windows, Linux, etc. Apenas Windows
Placas aceleradoras Caras (costumam suportar OpenGL e Direct 3-D) Baratas (suportam apenas Direct 3-D) o que as torna populares
Design Procedural (um comando X uma ação) Orientado a objetos
Documentação Ampla Satisfatória
Compatibilidade entre versões Total Problemática (conflitos entre versões)
Versão atual OpenGL 2.0 (primeira em 93) DirectX 9.0 (primeira em torno de 95)
Abstração Nenhuma (comandos operam diretamente em polígonos) Oferece 2 modos de operação (Retained mode e Immediate mode)
Imagem do robô gerado pelo programa (com visão do modelo)

Figura 1: Imagem do robô gerado pelo programa (com visão do modelo).

programa é dividido em 4 módulos (figura 2)

  • Módulo principal: onde são invocadas as funções do programa para desenhar a cena;
  • Módulo timer: contém as funções para determinar o framerate da animação e pausa de execução no final do programa.
  • Módulo Espera: possui informação utilizada por "timer" para definir o tempo decorrido entre um frame e outro. Foi necessário devido a um bug encontrado em um dos compiladores (verResultados e discussão). Além disso, possui texto para identificar o compilador que gerou o programa.
  • Módulo gráfico: contém as funções para desenhar em OpenGL e responder aos eventos do usuário;
Esquema da organização do programa.

Figura 2: Esquema da organização do programa.

Os concorrentes

Abaixo segue a listagem dos compiladores testados neste artigo, onde serão informados dados sobre o programa (os preços foram obtidos no site da Associação Brasileira de Software). Ao final seguem comentários sobre as características do mesmo.

Visual C++

preço: Visual Studio 2002 .NET Profissional R$3.735

fabricante: Microsoft

versão testada: Visual C++ 6.0 (service pack 5)

O compilador padrão da indústria possui grande número de características sendo a opção entre a maior parte dos projetos profissionais. Apresenta uma IDE (Integrated Development Enviroment) gráfica bem integrada, com explorador para funções e classes, como visível na figura 3.

Os programas gerados por ele podem ser otimizados para velocidade ou tamanho (uma característica muito interessante).

Para criar formulários de aplicativos normais, oferece a biblioteca MFC (Microsoft Foundation Class) para facilitar as coisas. O processo de criação desses formulários é manual, sendo auxiliado por alguns "Wizards" para setar eventos e outras funções.

Inicialmente, o suporte para o padrão definido ANSI C/C++ (American National Standards Institute) era pobre, devido a preocupação em desenvolver um compilador otimizado para Windows. Entretanto, hoje o Visual C++ já oferece excelente suporte para características avançadas da linguagem como template metaprograms.

O Visual C++: um projeto encontra-se aberto

Figura 3: O Visual C++: um projeto encontra-se aberto

Borland C++ Builder

preço: C++ Builder 5.0 Professional R$1.885

fabricante: Borland

versão testada: C++ Builder 4.0 Professional

A primeira versão foi lançada somente em 96-97 (o Visual C++ foi lançado em 94), sendo pouco utilizado pelos programadores tradicionais. Possui uma característica única: primeiro compilador C/C++ onde se cria as interfaces por meio visual (semelhante ao Delphi). Para criar formulários, utiliza nativamente a VCL (Visual Components Library), porém oferece compatibilidade com OWL (Object Window Library) e a MFC.

Apresenta uma IDE bem completa e com excelentes capacidades de debugação, superior à concorrência neste último aspecto. Entretanto, é o mais pesado dos compiladores testados, exigindo uma máquina superior para boa performance em projetos grandes.

O suporte para o ANSI C/C++ é bom, só peca por incluir algumas extensões na linguagem para possibilitar a utilização da VCL (escrita em Object Pascal) pelo compilador. Porém, graças a isto, é possível utilizar componentes e classes escritos para Delphi nos projetos normais desenvolvidos no C++ Builder.

O ambiente de desenvolvimento do C++ Builder

Figura 4: O ambiente de desenvolvimento do C++ Builder

Dev-C++

preço: grátis

fabricante: Bloodshed Software

versão testada: Dev-C++ 4.0

Oferece uma IDE acompanhada do compilador Mingwin 2.95.2, este último baseado no Cygwin (porém sem restrições a manter o código fonte do programa aberto). A interface é bem simples de se utilizar, ao contrário dos concorrentes, porém oferece número reduzido de características.

O Cygwin foi um projeto para portar o compilador gcc (do grupo GNU) para o ambiente Windows, incluindo uma dll com capacidades de emular especificações de sistemas Unix. A sua utilização exige a compra de uma licença para manter o código fonte do programa fechado, acabando por afastar programadores deste compilador. Um grupo utilizou a base do Cygwin e excluiu esta restrição ao eliminar as especificações de sistemas Unix, tornando a utilização do compilador resultante (Minimalist Gnu compiler for Windows) livre por qualquer interessado.

Como ambas as ferramentas eram para terminal, exigindo conhecimento para operar softwares por linha de comando, muitos programadores iniciantes sentiam-se pouco a vontade para utilizar estes excelentes compiladores. Assim, Colin Laplace e Hongli Lai criaram uma IDE para facilitar a programação (curiosidade: a interface foi escrita no Delphi). Como a base do Dev-C++ é o compilador do grupo GNU, seu suporte ao ANSI C/C++ é excelente. Este compilador pode ser integrado com um debugador visual, o Insight Debugger (maiores informações: ).

O Dev-C++ oferece uma boa alternativa para programação.

Figura 5: O Dev-C++ oferece uma boa alternativa para programação.

Implementação

Compatibilidade entre os compiladores Para permitir a utilização do mesmo código entre os 3 compiladores, foi necessário escrever código para lidar com os erros presentes nos mesmos. Isto foi realizado através de comandos de pre-processador cuja ativação habilita código dependente do compilador. A seguir, iremos descrever os problemas encontrados com cada compilador. A falha no Borland C++ Builder tinha grande impacto no desempenho do aplicativo (30% de queda no framerate em máquinas com aceleração gráfica via hardware), sendo de difícil resolução pela ausência de documentação deste erro. O código responsável pela animação do robô utiliza a função "acos" (arcocoseno) para efeitos de transformação nas coordenadas dos polígonos do modelo (ver figura 6). Em dado momento da animação, é passado um valor negativo de coordenada para a função, resultando em uma exceção de domínio de função. A cada ciclo onde ocorria a passagem de valores negativos para esta função, o programa tinha o framerate diminuído em função da criação da exceção. Pesquisando documentação (2, 3, 4, 5, 5) e em consulta ao arquivo de biblioteca "math.h" utilizado pelo compilador, foi possível escrever uma função para captura da exceção. Assim, a exceção é tratada antes de afetar a performance do programa e a movimentação consegue seguir fluidamente. O código abaixo resolve o problema:

Listagem 1: Função para capturar a exceção

int std::_matherr(struct math_exception *e)
{
   if (e->type == DOMAIN)
    if (!strcmp(e->name,"acos"))
       return 1;
   return 0;
}
Exceção lançada pelo C++ Builder com o uso da função arcoseno. É importante observar que a queda do frame rate só é sensível em máquinas com aceleração gráfica por hardware: neste caso em que o programa rodou sem aceleração, não há mudança no frame-rate (Pentium I 100 Mhz, 48Mb RAM, Win95).

Figura 6: Exceção lançada pelo C++ Builder com o uso da função arcoseno.

É importante observar que a queda do frame rate só é sensível em máquinas com aceleração gráfica por hardware: neste caso em que o programa rodou sem aceleração, não há mudança no frame-rate (Pentium I 100 Mhz, 48Mb RAM, Win95).

A falha do Visual C++ é menos perceptível, porém mais grave. Para prosseguir na sua descrição, torna-se necessário explicar como a função responsável pela contagem de framerate funciona.

Esta função possui um contador que é incrementado a cada chamada da função, sendo o comando de chamada presente no loop de animação da cena. Este contador acumula o número de frames desenhados ao longo de um período de tempo definido no código como TIMEFRAME. Logo, serão necessárias 2 variáveis globais ou estáticas para guardar o tempo entre as chamadas e verificar se o intervalo definido em TIMEFRAME já foi superado (como queremos frames por segundo, o valor de TIMEFRAME será 1.0f). Caso positivo, a função escreve o valor do contador na janela do console e zera o contador. Este design de contador de frames foi sugerido por REFERENCIA (visite o site dele, você terá acesso a excelentes tutoriais sobre programação de jogos: www.gametutorials.com).

Em mudança ao design original, foi substituída a função da API do Windows (GetTickCount) para obter o tempo por uma função ANSI C/C++, visando portabilidade do código. Assim, utilizou-se a função clock() que retorna o tempo em ciclos do processador.

Para converter este valor para segundos, basta dividir o valor retornado pela função pela macro CLK_TCK. O código abaixo apresenta a versão revisada da função:

Listagem 2: Função frame _ rate

void frame_rate()
{
    //frames por segundo
    static float fps    = 0.0f;
    //ultimo momento em que a funcao escreveu a contagem de frames
    static float t_last 		= 0.0f;
    //string de saida com contagem dos frames
    static char strFrameRate[50] = {0};

    //float t_current = GetTickCount() * 0.001f;
    //momento atual: obtem o tempo em segundos
    float t_current = clock()/CLK_TCK;

    //Incrementa o contador
    ++fps;

    //Verificamos se a diferença de tempo entre o frame anterior e o atual
    //é superior ao intervalo de contagem de frames (no caso, 1 segundo).
    //Caso contrário, vamos incrementar o contador e retornar.
    if( t_current - t_last> TIMEFRAME )
    {
       //Caso já tenha passado pelo menos o tempo entre a contagem de frames,
       //simplesmente pegamos o tempo atual e passamos para a variavel que
       //guarda o ultimo momento de escrita do framerate
       t_last = t_current;

       //Copia o framerate para a string de saida
       sprintf(strFrameRate, "Frames por segundo: %d", int(fps));

       //Escreve no terminal o framerate
       printf("\n%s", strFrameRate);

       //Zera o contador de frames
       fps = 0;
    }
}

No Visual C++, utilizando este design obteve-se uma contagem de frames exatamente dobrada em relação os executáveis gerados pelos outros compiladores. Fabuloso, não? Porém um exame cuidadoso demonstrou que em um período de 10 segundos, a função escrevia a contagem de frames cinco (5) vezes, enquanto a mensagem deveria ser exibida uma (1) vez por segundo totalizando dez (10) mensagens.

Ou seja, a estrutura de conversão do tempo a partir de ciclos do processador possui um erro, gerando um valor que vale a metade do esperado. Isto permitia que o contador acumulasse a contagem de frames por 2 segundos, gerando um valor falso sugerindo uma melhor velocidade de execução do programa.

Para corrigir este problema, foi criada uma diretriz de pre-processador com o valor de TIMEFRAME corrigido para o Visual C++, como abaixo:

Listagem 3: Diretrix de pre-processador

#ifdef VISUAL_C
#define TIMEFRAME 0.5f
#define ORIGIN "Gerado pelo: VC++"
#endif 

Finalmente, é chegado o momento de fazer comentários sobre as especificidades do código para o compilador Dev-C++. Ou seja, não há nada a se comentar. Esclarecendo, somente o Dev-C++ foi capaz compilar o programa sem erros de execução: um resultado surpreendente, se consideramos o fato deste compilador ser gratuito.

Resultados e discussão

O quadro 2 demonstra o framerate obtido com o programa gerado por cada compilador, testado em 3 máquinas com configurações diferentes.

Quando comecei a escrever este artigo tinha convicção que o programa mais rápido seria gerado pelo Visual C++, mas não foi o ocorrido (talvez o compilador tenha recursos para eliminar bibliotecas do Windows desnecessárias? Qualquer sugestão será bem vinda!). Surpreendentemente, o maior framerate obtido foi com o programa criado com o Dev-C++. Sinceramente, não esperava este resultado, pois havia na disputa outros compiladores mais famosos e com grandes empresas responsáveis pelo seu desenvolvimento. Convido os leitores a enviarem resultados obtidos com os programas em máquinas com configurações diferentes.

Tabela 2:Taxa de framerate obtida pelos programas gerados por cada compilador. Utilizou-se a média 3 execuções do programa, onde foi registrado o framerate por 10 segundos. As linhas representam os valores obtidos em cada máquina, enquanto as colunas representam o compilador utilizado. O programa rodou com o modelo iluminado, sem ter seu wireframe visível (as observações nulas no Borland foram motivadas pelo programa dar erro quando as luzes eram acionadas, fechando imediatamente).

Média do frame rate
DEV BCB VC++
PII 400 75.23 72.66 71.2
PIII 700 10 10 9.44
PII 266 3.77 ----- 3111
Desvio padrão frame rate
DEV BCB VC++
PII 400 0.5 0.5 3047
PIII 700 0.0 0.0 9.44
PII 266 0.44 ----- 0.33

onde:

  1. PII 400 192MB Ram, ASUS GeForce 2 32Mb AGP (32bits cor), Win98 SE
  2. PIII 800, 128MB Ram, sem aceleração (24bits cor), Win98 SE
  3. PII 266, 64MB Ram, sem aceleração (16bits cor), Win95

Outra característica importante em programas de animação em tempo real é o desvio padrão do framerate. Em condições reais, é preferível um programa com menor framerate, porém com baixo desvio: consegue-se prever quais otimizações poderão beneficiar o desempenho do programa e qual será o impacto das mesmas no tempo geral de execução de uma dada função.

Novamente, o Dev-C++ mostrou bons resultados, apresentando um baixo desvio tanto com máquinas dotadas de aceleração gráfica via hardware, como sem este recurso. O Visual C++, por outro lado, apresentou o menor desvio para a máquina mais humilde (PII 266) e maior desvio para a máquina com aceleração via hardware. Esperava-se um comportamento uniforme independente do computador ser dotado ou não de aceleração via hardware.

O Borland C++ Builder mostrou-se instável: se por um lado o framerate obtido foi superior ao Visual C++, o programa simplesmente não rodava em certas ocasiões com o Window 95. A função para captura das exceções lançadas pela biblioteca matemática quando utilizada a função arcoseno (acosin) exigiu grande tempo de pesquisa por documentação e certo grau de empirismo até alcançar-se uma solução funcional. Será este erro presente nas versões recentes do C++ Builder? De qualquer forma, é uma vergonha para a Borland romper a tradição de grandes compiladores construída ao longo de muitos anos de trabalho (Turbo C++ já foi sinônimo de compilador C++ nas décadas de 80-90..... bons tempos aqueles!). :-(

O erro do Visual C++ foi mais simples de ser corrigido, porém é igualmente grave: se um programa depende de contagem de tempo para execução de uma tarefa, apresentará erros mesmo estando a implementação correta. Seria este problema vinculado ao service pack 5? Ou problema está relacionado com a macro de conversão de ciclo de processador para tempo em segundos? Fico esperando comentários sobre o assunto de outros programadores.

Conclusões

O desempenho alcançado pelos compiladores foi similar, não justificando a escolha de um ambiente de desenvolvimento somente pela velocidade de execução. Ainda assim, é uma surpresa o melhor desempenho ter sido alcançado por um compilador gratuito: o Dev-C++.

Definitivamente, existem outros fatores a serem considerados na escolha de um ambiente de desenvolvimento, como: facilidade de uso, recursos de produtividade, familiaridade. A melhor ferramenta será sempre aquela que se conhece melhor, pois nada substitui a capacidade criativa do programador.

Além de computação gráfica via OpenGL, existiriam outras áreas da ciência da computação úteis para a comparação entre compiladores: algoritmos de ordenação, estruturas de dados e acesso a banco de dados. Como OpenGL é uma API de baixo nível, talvez os compiladores não tenham grande espaço para realizar otimizações em torno do código.

Referências

  1. Thomas, J. R. OpenGL tutorial. 1998, 02-07-2002
  2. MICROSOFT Signal() with SIGFPE Requires floating-point support. 04-02-2000
  3. MICROSOFT DOC: Floating point control function sample code incorrect. 04-02-2001
  4. MICROSOFT Borland floating-point initialization I. 27-03-1999
  5. MICROSOFT Borland floating-point initialization II. 27-03-1999
  6. SWART, R. E. Borland Builder lost + fond: matherr. 12-12-2000
  7. Open GL Reference Manual. ARB, 1994.

Downloads

Código fonte Executáveis
Adenilson Cavalcanti da Silva

Adenilson Cavalcanti da Silva - Adenilson (a.k.a. Savago) desenvolve sistemas há 10 anos, utilizando diversas linguagens de programação e sistemas operacionais. Tendo se especializado em C++, está sempre a procura de novos desafios com características multidisciplinares. Mestre pela USP, possui interesse especial por visão artificial, *nix, programação baixo nível.