
Até hoje, quando me perguntavam sobre java e performance, eu dizia que a performance do java é igual a do C + +. Há algumas oscilações dependendo do formato do programa, mas estas diferenças não são significativas. Quando vi este post, logo pensei: " Ok, vamos ver qual a cagada que ele fez para dizer que Java é 2x mais rápido que C + + ". De fato, C + + estava executando 1 milhão de operações enquanto as outras linguagens apenas 100 mil (apesar de ele dizer que não isso não faz diferença, o_O). Além disso, ele não está considerando a carga das VMs que, no C + + não existe.
No entanto, eu gostei dos códigos. Fiz o download (Java e C + +), aumentei para 10 milhões o número de iterações, removi a coleta de tempo e mandei rodar usando o time no meu Core 2 Duo, com 2GB de RAM e Ubuntu 8.04 32 bits. Isso garantiria que o tempo de carga da VM seria levado em conta. Para minha surpresa, java foi 3 vezes mais rápido que C + +, mesmo com o código do C + + compilado com O3. Pensei: " não pode. Tem algo errado nessa comparação ". Fiz inúmeros testes no código C + +, ativando e desativando flags do gcc, modificando código e por aí vai. Nada de melhorar a performance.
vitor@vitor-laptop:~/Desktop$ g++ Chain.cpp -O3
vitor@vitor-laptop:~/Desktop$ javac Chain.java
vitor@vitor-laptop:~/Desktop$ time java Chain
real 0m7.932s
user 0m7.744s
sys 0m0.112s
vitor@vitor-laptop:~/Desktop$ time ./a.out
real 0m23.484s
user 0m23.233s
sys 0m0.228s
vitor@vitor-laptop:~/Desktop$
Cheguei a duas alternativas: Ou esta rapidez vem da reserva de memória que a VM faz ao iniciar o programa e, portanto, da inexistência de comunicação intensa com o SO, ou o java estaria paralelizando o loop de Chain. Para evitar a segunda hipótese, modifiquei ambas a versões para ficarem um pouco mais complexas, evitando alguma paralelização automática do java durante a compilação do código.
Versão C + +
#include <stdio.h>
class Person
{
public:
Person(int id, int count) : _next(NULL), _prev(NULL) { _count = count; _id = id; }
int shout(int shout, int nth)
{
if (shout < nth) return (shout + 1);
_prev->_next = _next;
_next->_prev = _prev;
return 1;
}
int count() { return _count; }
Person* next() { return _next; }
void next(Person* person) { this->_next = person ; }
Person* prev() { return _prev; }
void prev(Person* person) { this->_prev = person; }
private:
int _count;
int _id;
Person* _next;
Person* _prev;
};
class Chain
{
public:
Chain(const int id, const int size) : _first(NULL), _id(id)
{
Person* current = NULL;
Person* last = NULL;
for(int i = 0 ; i < size ; i++)
{
current = new Person(id + i, i);
if (_first == NULL) _first = current;
if (last != NULL)
{
last->next(current);
current->prev(last);
}
last = current;
}
_first->prev(last);
last->next(_first);
}
Person* kill(const int nth)
{
Person* current = _first;
int shout = 1;
while(current->next() != current)
{
Person* tmp = current;
shout = current->shout(shout,nth);
current = current->next();
if (shout == 1)
{
delete tmp;
}
}
_first = current;
return current;
}
Person* first() const { return _first; }
private:
Person* _first;
int _id;
};
int main(int argc, char** argv)
{
const int ITER = 10000000;
for(int i = 0 ; i < ITER ; ++i)
{
Chain* chain = new Chain(i, 40);
chain->kill(3);
delete chain;
}
return 0;
}
Versão Java
public class Chain
{
private int id;
private Person first = null;
public Chain(int id, int size)
{
this.id = id;
Person last = null;
Person current = null;
for (int i = 0 ; i < size ; i++)
{
current = new Person(id + i, i);
if (first == null) first = current;
if (last != null)
{
last.setNext(current);
current.setPrev(last);
}
last = current;
}
first.setPrev(last);
last.setNext(first);
}
public Person kill(int nth)
{
Person current = first;
int shout = 1;
while(current.getNext() != current)
{
shout = current.shout(shout, nth);
current = current.getNext();
}
first = current;
return current;
}
public Person getFirst()
{
return first;
}
public static void main(String[] args)
{
int ITER = 10000000;
for (int i = 0 ; i < ITER ; i++)
{
Chain chain = new Chain(i, 40);
chain.kill(3);
}
}
}
class Person
{
int id;
int count;
private Person prev = null;
private Person next = null;
public Person(int id, int count)
{
this.count = count;
this.id = id;
}
public int shout(int shout, int deadif)
{
if (shout < deadif) return (shout + 1);
this.getPrev().setNext(this.getNext());
this.getNext().setPrev(this.getPrev());
return 1;
}
public int getCount()
{
return this.count;
}
public Person getPrev()
{
return prev;
}
public void setPrev(Person prev)
{
this.prev = prev;
}
public Person getNext()
{
return next;
}
public void setNext(Person next)
{
this.next = next;
}
}
Rodei e vejam só: Nenhuma diferença. Os tempos foram muito parecidos com os que relatei anteriormente. Mas, desta vez, fiquei de olho no processamento e na memória. Deem uma olhada e vejam a mágica do Java:
Segundo: Olhem o gasto de memória. Em C + +, nem o sistema operacional, nem o compilador reaproveitou a memória dos " deletes ", tanto das Person, quanto das Chain. Já em Java, a memória permaneceu constante, como deveria, obviamente este é um dos benefícios de ter uma carga da VM com reserva de memória.
A meu ver, uma linguagem é mais rápida do que a outra quando sua performance é superior sem alterar o paradigma ou o modo de programação (Pode-se alterar a sintaxe e adicionar palavras-chave, mas não pode mudar o paradigma). Manter seu código legível e limpo é essencial. Como vocês podem ver, os códigos são praticamente iguais, só muda detalhes de sintaxe. Certamente, a versão em C + + poderia criar um buffer gigantesco no início do programa e trabalhar sobre ele, mas francamente, quem iria fazer isso?:)
Conclusão: Se você precisar construir um programa com muita alocação e desalocação de memória, escreva em Java. Se você vai rodar o seu código em um x-Core, faça o programa em Java. Caso contrário, faça em C + +.
Lanço um desafio a todos os programadores C + + de plantão a me apresentar uma versão do código e da linha de compilação que execute mais rápido que a versão em java que apresentei. Lembrando, não quebre o formato do código.
Alguém aí tem o MS Visual Studio Professional para fazer o teste com aquele Ultra-top-highlevel bytecode generator dele? Porque, pra mim, C + + de agora em diante, é lento!
Update: Me pediram para fazer 40 Chains de 1000000 de elementos para testar. Java foi 3 vezes mais rápido, novamente.
vitor@vitor-laptop:~/Desktop/Comparativo$ javac Chain.java
vitor@vitor-laptop:~/Desktop/Comparativo$ time java Chain
real 0m6.107s
user 0m7.824s
sys 0m0.132s
vitor@vitor-laptop:~/Desktop/Comparativo$ g++ Chain.cpp -O3
vitor@vitor-laptop:~/Desktop/Comparativo$ time ./a.out
real 0m16.819s
user 0m16.713s
sys 0m0.056s
Fizemos um teste com o OpenMP para tentar paralelizar o código original do C + +, mas infelizmente o OpenMP tem se revelado um tanto lento. Como resultado, apesar de ele ter utilizado os dois processadores, o tempo de processamento foi superior:
vitor@vitor-laptop:~/Desktop/Comparativo$ g++ Chain.cpp -O3 -fopenmp
vitor@vitor-laptop:~/Desktop/Comparativo$ time ./a.out
real 0m38.809s
user 0m37.503s
sys 0m0.276s
Resumo dos comentários do pessoal de C + +: Se vc implementar uma VM em baixo do C + + ele fica rápido.
Copyright © 2007 Vitor Pamplona | Todos os direitos reservados
Powered by Priki e design G. Wolfgang