Bem, estamos chegando ao final e aqui entra o nosso bate-papo sobre otimização.
Não basta fazer um programa funcionar. Tem de fazer ele funcionar bem, ter uma boa performance e ser estável.
Isso exige de analistas e programadores uma constante pesquisa, buscando metodologias alternativas, novas tecnologias e novos conhecimentos.
Para dar um exemplo de como pequenos detalhes podem influenciar grandemente o resultado dos seus programas, escrevi três pequenos exemplos em Perl. São três versões distintas do mesmo programa, que tem como objetivo mostrar na tela uma tabela com todos os números primos menores que 10000.
Sei que esse exemplo pode parecer artificial, mas escolhi-o por razões didáticas. Fui também tendencioso, já que sou matemático por formação acadêmica.
De qualquer forma, o objetivo é mostrar como é que pequenas coisas podem afetar grandemente um programa.
Um pequeno esclarecimento para os menos afeitos à bela ciência da Matemática: número primo é aquele que só é divisível por si mesmo e por um.
Para avaliar a performance do programa eu o executei usando um pequeno shell script que me dá no final a hora de início e de término da execução, permitindo que eu calcule o tempo total de execução. O shell script para executar o programa é bem simples:
teste.sh
#!/bin/bash
INICIO=`date`
perl primos1.pl
FINAL=`date
echo $INICIO
echo $FINAL
INICIO=`date
perl primos2.pl
FINAL=`date`
echo $INICIO
echo $FINAL
INICIO=`date
perl primos3.pl
FINAL=`date`
echo $INICIO
echo $FINAL
exit 0
Na minha primeira versão eu escolhi verificar se um número N era primo testando o resto de sua divisão por todos os números de 2 até (N-1). A variável $l (linha 7) controla o limite do teste. Essa será a única linha alterada nas três versões.
Veja a primeira versão:
primos1.pl
#!/usr/bin/perl
use strict;
sub primo {
my $n = shift;
my $l = $n - 1;
my $d;
for $d ( 2 .. $l ) {
if ( ( $n % $d ) == 0 ) { return 0; }
}
return 1;
}
my $n;
my $cp = 0;
for $n ( 2 .. 10000 ) {
if ( primo( $n ) ) {
$cp++;
printf( "%05d ", $n);
if ( ( $cp % 14 ) == 0 ) { print "\n" };
}
}
print "\n\n Quantidade de primos: ", $cp, "\n\n";
Lembrei então de um resultado matemático que diz que o maior divisor de um número não pode exceder a sua metade, ou seja, o maior divisor de N não pode ser maior do que N/2.
Reescrevi o programa, então, alterando apenas a linha 7, como você pode ver.
primos2.pl
#!/usr/bin/perl
use strict;
sub primo {
my $n = shift;
my $l = int( $n / 2 ) + 1;
my $d;
for $d ( 2 .. $l ) {
if ( ( $n % $d ) == 0 ) { return 0; }
}
return 1;
}
my $n;
my $cp = 0;
for $n ( 2 .. 10000 ) {
if ( primo( $n ) ) {
$cp++;
printf( "%05d ", $n);
if ( ( $cp % 14 ) == 0 ) { print "\n" };
}
}
print "\n\n Quantidade de primos: ", $cp, "\n\n";
Não fico satisfeito facilmente, portanto continuei buscando em minha já velha e cansada mente uma forma de melhorar o programa. Lembrei então de um outro teorema que afirma que o maior divisor inteiro de um número N não pode ser maior que a raiz quadrada de N. Com essa informação eu fiz a terceira versão, novamente alterando apenas a linha 7.
primos3.pl
#!/usr/bin/perl
use strict;
sub primo {
my $n = shift;
my $l = int( sqrt( $n ) ) + 1;
my $d;
for $d ( 2 .. $l ) {
if ( ( $n % $d ) == 0 ) { return 0; }
}
return 1;
}
my $n;
my $cp = 0;
for $n ( 2 .. 10000 ) {
if ( primo( $n ) ) {
$cp++;
printf( "%05d ", $n);
if ( ( $cp % 14 ) == 0 ) { print "\n" };
}
}
print "\n\n Quantidade de primos: ", $cp, "\n\n";
Você podem pensar que para uma mudança tão pequena não haveria uma alteração tão radical assim no tempo de execução, não é mesmo?
Se pensou assim, enganou-se!
Olha os tempos que obtive para as três versões:
Versão 1 (primos1.pl)
---------------------
Seg Mar 24 23:39:13 BRT 2008
Seg Mar 24 23:39:54 BRT 2008
Tempo total: 41 segundos
Versão 2 (primos2.pl)
---------------------
Seg Mar 24 23:39:54 BRT 2008
Seg Mar 24 23:40:16 BRT 2008
Tempo total: 22 segundos
Versão 3 (primos3.pl)
---------------------
Seg Mar 24 23:40:16 BRT 2008
Seg Mar 24 23:40:17 BRT 2008
Tempo total: 1 segundo
Como você pode ver, uma pequena alteração pode impactar drasticamente a performance de um programa!
É por isso que vale a pena planejar e otimizar.
Programadores precisam ter um conhecimento amplo. Devem procurar estudar bastante os assuntos que estão envolvidos nos programas que estão fazendo. Só assim poderão descobrir a melhor forma de fazer as coisas acontecerem.
OBS: Estes tempos foram obtidos em um K6 380 Mhertz com 128 Mbytes de RAM. Uma supermáquina, não é mesmo?
Conclusão
Espero que o artigo tenha sido instrutivo. Críticas e sugestões são bem vindas. Quem quiser pode escrever diretamente para mim (edlonewolf@gmail.com).
No próximo artigo vou tratar sobre a Modularização de um sistema e sobre as características dos bons módulos.
Até lá!