mslomp
(usa Slackware)
Enviado em 27/09/2008 - 11:45h
vou tentar ajudar em sua decisão focando-me no seu problema em específico (matemática), e levando em conta as linguagens C e C++ apenas, sem entrar em xiitismos e formalismos acadêmicos (que na teoria são muito bonitinhos, mas na prática nem sempre funcionam - e mais adiante ilustrarei isso)
como engenheiro e também colaborador do projeto GCC, posso analisar sua questão sob 2 pontos de vista distintos, mas que possuem muito em comum.
há tempos atrás pesquisei muito sobre o método de elementos finitos (fem) para análise térmica e de tensões em materiais, e como sabe, uma análise dessas pode levar à resolução de matrizes gigantescas, da ordem 100 mil x 100 mil, por exemplo. resolvê-las computacionalmente exige muito critério, onde um algoritmo mal pensado pode significar minutos ou até mesmo horas a mais de processamento. na época, dava meus primeiros passos com otimização de código e explorava o "submundo" do código do GCC, pude determinar a melhor abordagem para o meu problema, visando tanto a precisão quanto a velocidade de resposta, e é aqui que encaixarei minha opinião enquanto colaborador do GCC:
há um consenso entre os desenvolvedores do GCC, embora velado, que C++, do ponto de vista do desenvolvimento do compilador, dá muito mais dor de cabeça que o compilador C. isso porque a orientação a objetos, os templates e demais "features" do C++ podem, se não bem utilizados, levar a situações de código extremamente sujo e ininteligível não apenas para nós humanos, mas também para o próprio compilador. se você verificar a base de bugs (bugzilla) do GCC, vai notar que há muito mais ICEs (internal compiler error) reportados para o compilador C++ do que para o C.
e na minha opinião, quando se trabalha com matemática, é importante que o código seja o mais limpo e claro possível, pois um bugzinho qualquer pode virar um inferno. e, mais do que isso, vamos ver o que muda internamente, no campo do código gerado, ao utilizar C (numa abordagem procedural direta) ou C++ (oop):
tomemos um exemplo bobo, com uma soma simples. em C usando uma função e em C++ usando o método de uma classe:
C:
int soma(int,int);
int main(void) {
int x;
x = soma(3, 5);
return x;
}
int soma(int a,int b) {
return(a+b);
}
C++:
class oper {
public:
oper() {};
int soma(int a, int b) {
return(a + b);
}
};
int main(void) {
int x;
oper o;
x = o.soma(3, 5);
return x;
}
compilando ambos sem otimizações (a fim de enxergar o que acontece na prática), vamos ver o código gerado para a chamada da função soma em ambos os casos (não colocarei toda a saída assembly por questões de espaço):
$ gcc -S -c teste.c -o teste.c.s
$ g++ -S -c teste.cpp -o teste.cpp.s
C:
pushl $5
pushl $3
call _soma
C++:
pushl $5
pushl $3
leal -9(%ebp), %eax
pushl %eax
call __ZN4oper4somaEii
para ser mais otimista, nem estou considerando a descarga da pilha após cada chamada no caso C++. veja que temos 2 instruções a mais, um lea e um push, que a grosso modo salvam algumas informações de escopo local e buscam a posição do método na memória (visto que classes são tratadas como porções distintas do código). em um 486+, isso significa 2 ciclos de máquina a mais (lea = 1 ciclo, push = 1 ciclo também). aí você pode dizer: "ah, mas meu core duo pro ultra master com 500 núcleos e refrigeração a nitrogênio líquido nem vai sentir isso, afinal, o que são para ele 2 ciclos, apenas alguns míseros nanosegundos, talvez nem isso". vai sim, pode acreditar. tome o caso de nossas matrizes 100 mil x 100 mil. coloque essa chamada em um laço com milhões e milhões de iterações, por exemplo. teremos então, milhões e milhões de ciclos a mais sendo executados, consumindo tempo de processamento. e, como dizem na terra do tio sam, "time is money". perceba que esse exemplo é bobo, mas imagine um código mais complexo, com dezenas de chamadas a métodos, atribuições de propriedades, referências e derreferências de classes, etc. a coisa toda vai crescer exponencialmente, e o processador gastará mais tempo com isso do que com o trabalho útil em si. e em casos assim, mesmo ativando-se todas as otimizações aplicáveis, ainda assim sobrarão alguns ciclos. e observe que nem entramos no mérito da fpu, onde a coisa tende a piorar.
bem, não querendo colocar lenha na discussão causada por sua questão, apenas gostaria de fazer uma observação. quando da implementação do tree-ssa (que foi um grande passo e tornou possível inclusive 20 novas otimizações na geração de código), a partir do GCC4, resolveram utilizar uma abordagem totalmente acadêmica. resultado: na prática a coisa não deslanchava a contento. começaram então a surgir patches nada acadêmicos, digamos assim, e então a coisa começou a tomar forma. o próprio kernel do linux passou por isso, e tornou-se viável de fato quando foi perdendo o radicalismo acadêmico do "minix-like".
como leitura adicional, há alguns anos publiquei um artigo demonstrando de maneira simples de que modo a otimização afeta o código gerado (naquele caso, utilizando o compilador microsoft do visual c++). não foca nenhuma linguagem em específico, porém talvez possa ser útil apenas para ilustrar o assunto e ajudar na reflexão:
http://www.codeproject.com/KB/tips/optimizations.aspx
e agora, após toda essa leitura, e apenas para descontrair, aqui vai uma contribuição: quer ficar mais próximo do hardware? puxe a cadeira mais para a frente. :D