init0 = (int*)calloc(max_x,sizeof(int));
init1 = (int*)calloc(max_x,sizeof(int));
init2 = (int*)calloc(max_x,sizeof(int));
for (x=0; x<max_x; x++) {
init2[x] = 0;
init1[x] = 0;
init0[x] = 0;
}
Como calloc
fornece memória que já está zerada, usar o loop for
para zerar novamente é obviamente inútil. Isto foi seguido (se a memória servir) preenchendo a memória com outros dados de qualquer maneira (e sem dependência de ser zerada), então todo o zeramento era completamente desnecessário de qualquer maneira. Substituir o código acima por um simples malloc
(como qualquer pessoa sensata teria usado para começar) melhorou a velocidade da versão C ++ o suficiente para superar a versão Java (por uma margem razoavelmente ampla, se a memória servir).
Considere (por outro exemplo) o valor de referência methcall
usado na entrada do blog no seu último link. Apesar do nome (e como as coisas podem parecer), a versão C ++ disso não é realmente muito sobre a sobrecarga da chamada de método. A parte do código que se torna crítica está na classe Toggle:
class Toggle {
public:
Toggle(bool start_state) : state(start_state) { }
virtual ~Toggle() { }
bool value() {
return(state);
}
virtual Toggle& activate() {
state = !state;
return(*this);
}
bool state;
};
A parte crítica é a state = !state;
. Considere o que acontece quando alteramos o código para codificar o estado como int
em vez de bool
:
class Toggle {
enum names{ bfalse = -1, btrue = 1};
const static names values[2];
int state;
public:
Toggle(bool start_state) : state(values[start_state])
{ }
virtual ~Toggle() { }
bool value() { return state==btrue; }
virtual Toggle& activate() {
state = -state;
return(*this);
}
};
Esta pequena alteração melhora a velocidade geral em cerca de uma margem de 5: 1 . Embora o benchmark fosse planejado para medir o tempo de chamada do método, na realidade, a maior parte do que ele estava medindo era o tempo de conversão entre int
e bool
. Eu certamente concordaria que a ineficiência mostrada pelo original é lamentável - mas dada a raridade com que parece surgir no código real, e a facilidade com que ele pode ser consertado quando / se surgir, tenho dificuldade em pensar como significando muito.
Caso alguém decida reexecutar os benchmarks envolvidos, devo também acrescentar que há uma modificação quase igualmente trivial na versão Java que produz (ou pelo menos uma vez produzida - eu não re-executei o testes com uma recente JVM para confirmar que eles ainda fazem) uma melhoria bastante substancial na versão Java também. A versão do Java tem um NthToggle :: activate () que se parece com isto:
public Toggle activate() {
this.counter += 1;
if (this.counter >= this.count_max) {
this.state = !this.state;
this.counter = 0;
}
return(this);
}
Mudar isto para chamar a função base ao invés de manipular this.state
diretamente dá uma melhoria considerável na velocidade (embora não o suficiente para acompanhar a versão modificada do C ++).
Então, o que acabamos com é uma falsa suposição sobre códigos de bytes interpretados versus alguns dos piores benchmarks (que eu já vi). Nem está dando um resultado significativo.
Minha própria experiência é que, com programadores igualmente experientes que prestam a mesma atenção à otimização, o C ++ superará o Java com mais frequência - mas (pelo menos entre esses dois), a linguagem raramente fará tanta diferença quanto os programadores e designers. . Os pontos de referência citados nos dizem mais sobre a (in) competência / (des) honestidade de seus autores do que sobre as linguagens que pretendem avaliar.
[Edit: Como implícito em um lugar acima mas nunca declarado tão diretamente quanto eu provavelmente deveria ter, os resultados que estou citando são aqueles que obtive quando testei isso ~ 5 anos atrás, usando implementações de C ++ e Java que eram atuais naquela hora. Eu não executei novamente os testes com as implementações atuais. Um relance, no entanto, indica que o código não foi corrigido, então tudo o que teria mudado seria a capacidade do compilador de encobrir os problemas no código.]
Se ignorarmos os exemplos de Java, no entanto, é realmente possível para o código interpretado ser executado mais rápido do que o código compilado (embora difícil e um tanto incomum).
A maneira usual como isso acontece é que o código que está sendo interpretado é muito mais compacto que o código da máquina, ou está sendo executado em uma CPU que possui um cache de dados maior que o cache de código.
Nesse caso, um pequeno intérprete (por exemplo, o intérprete interno de uma implementação da Forth) pode ser capaz de se ajustar inteiramente ao cache de código, e o programa que está interpretando se ajusta inteiramente ao cache de dados. O cache é tipicamente mais rápido que a memória principal por um fator de pelo menos 10, e frequentemente muito mais (um fator de 100 não é particularmente mais raro).
Então, se o cache é mais rápido que a memória principal por um fator de N, e leva menos que N instruções de código de máquina para implementar cada código de byte, o código de byte deve ganhar (estou simplificando, mas acho que o geral idéia ainda deve ser aparente).