Programação, diversão e arte. Por Ricardo Giaviti
Comparação de desempenho entre linguagens de programação
No dia 08/07/2008 o blog var/log/mind publicou uma comparação de desempenho entre algumas linguagens de programação. As linguagens testadas pelo blog foram C++, JAVA, Ruby, JRuby, Python, Jython, Groovy e PHP. Esse tema gerou até um bafafá na blogosfera nacional e internacional.
A tipo de benchmark feito pelo é blog foi bem simples e baseado em um pedaço de lógica onde é medido o tempo de cada iteração num total de 100000 iterações.
O mais espantoso foi que JAVA obteve um desempenho melhor do que C++. Esse resultado me deixou com uma pulga atrás da orelha porque sempre acreditei (mas nunca havia testado) de que C++ por ser somente compilado seria bem mais rápido do que JAVA que é compilado e interpretado pela JVM.
Então resolvi fazer o mesmo teste que o blog var/log/mind fez. Utilizei os mesmos códigos disponibilizados por ele e rodei eles em minha máquina. Somente não testei Python, Jython e o Groovy.
Ambiente de Testes
Meu ambiente de testes difere do ambiente de testes do var/log/mind (veja a tabela mais abaixo), assim como versões de compiladores e interpretadores. Abaixo segue o meu ambiente de testes e mais um pouco abaixo as versões dos compiladores e interpretadores usados no teste.
Meu ambiente:
- Processador: AMD 4200+ X2 64 Bits
- Memória: 3GB RAM
- SO: Windows XP SP2 Professional
As diferenças entre o ambiente de teste original do var/log/mind e o meu ambiente de teste:
| MeuPost.com | var/log/mind | |
|---|---|---|
| Processador | AMD 4200+ X2 64 Bits | Intel® Core™ Duo CPU T2600 @ 2.16GHz |
| Memória | 3GB RAM | 2GB |
| SO: | Windows XP SP2 Professional | Ubuntu Gutsy Gibbon 7.10 |
Compiladores e Interpretadores usados:
- C++: MinGW32 GCC: 3.4.2
- JAVA 1.6
- Ruby 1.8.6-26
- JRuby 1.1
- PHP 5.2.3
Teste:
Para realizar o teste rodei 5 vezes o código de cada linguagem diferente e anotei todos os resultados em nanossegundos. Depois tirei a média dos resultados.
Resultados
Seguinte a tendência dos resultados, em meu ambiente JAVA também foi superior ao C++. Os resultados também mostraram que o Python está mais maduro que Ruby embora nas últimas versões do Ruby (1.9) o desempenho tenha melhorado muito.
Outro fato interessante é que o JRuby está também mais rápido que Ruby. Já o PHP mostra a força de sua maturidade e popularidade ganhando tanto do Ruby como do JRuby, isso em meu ambiente, porque no var/log/mind tanto o Ruby como o JRuby ganharam do PHP.
Acredito que esse desempenho superior do Ruby no var/log/mind está diretamente associado ao sistema operacional. O Ruby está rodando muito mais rápido em ambientes Linux do que em Windows.
Abaixo segue a tabela de resultados:
| Iteração 1 | Iteração 2 | Iteração 3 | Iteração 4 | Iteração 5 | Média | Total | |
| C++ | 12000 | 12000 | 12000 | 12000 | 12000 | 12000 | 60000 |
| Java | 1509 | 1369 | 1384 | 1376 | 1385 | 1404,6 | 7023 |
| Ruby | 491990 | 493750 | 494690 | 493280 | 494840 | 444232 | 2468550 |
| Jruby | 244040 | 246360 | 243830 | 243450 | 246750 | 244886 | 1224430 |
| PHP | 125200 | 125030 | 124880 | 124890 | 124900 | 124980 | 624900 |
| 1 segundo = 1000000 microssegundo = 1000000000 nanossegundo | |||||||
Para facilitar a visualização dos resultados, abaixo segue um gráfico com o resultado do teste:
Códigos
Abaixo segue os códigos utilizados nos testes. São os mesmos do var/log/mind com uma única diferença no código C++. Como o var/log/mind usou o Linux, ele usou a função gettimeofday(), em Windows essa função não existe, então eu como não sou um programador C++, achei essa função no Blog OpenAsthra e coloquei no código.
Todos os outros códigos são exatamente iguais.
C++
#include <time.h> #include <windows.h> #include <stdio.h> #include <stdlib.h> #include <sys/time.h> #if defined(_MSC_VER) || defined(_MSC_EXTENSIONS) #define DELTA_EPOCH_IN_MICROSECS 11644473600000000Ui64 #else #define DELTA_EPOCH_IN_MICROSECS 11644473600000000ULL #endif struct timezone { int tz_minuteswest; /* minutes W of Greenwich */ int tz_dsttime; /* type of dst correction */ }; int gettimeofday(struct timeval *tv, struct timezone *tz) { FILETIME ft; unsigned __int64 tmpres = 0; static int tzflag = 0; if (NULL != tv) { GetSystemTimeAsFileTime(&ft); tmpres |= ft.dwHighDateTime; tmpres <<= 32; tmpres |= ft.dwLowDateTime; tmpres /= 10; /*convert into microseconds*/ /*converting file time to unix epoch*/ tmpres -= DELTA_EPOCH_IN_MICROSECS; tv->tv_sec = (long)(tmpres / 1000000UL); tv->tv_usec = (long)(tmpres % 1000000UL); } if (NULL != tz) { if (!tzflag) { _tzset(); tzflag++; } tz->tz_minuteswest = _timezone / 60; tz->tz_dsttime = _daylight; } return 0; } #define TEST #ifdef TEST #endif class Person { public: Person(int count) : _next(NULL), _prev(NULL) { _count = count; } 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; Person* _next; Person* _prev; }; class Chain { public: Chain(int size) : _first(NULL) { Person* current = NULL; Person* last = NULL; for(int i = 0 ; i < size ; i++) { current = new Person(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(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() { return _first; } private: Person* _first; }; int main(int argc, char** argv) { int ITER = 100000; Chain* chain; struct timeval start, end; gettimeofday(&start,NULL); for(int i = 0 ; i < ITER ; i++) { chain = new Chain(40); chain->kill(3); delete chain; } gettimeofday(&end,NULL); fprintf(stdout,"Time per iteration = %d microsecondsnr", (((end.tv_sec - start.tv_sec) * 1000000) + (end.tv_usec - start.tv_usec)) / ITER); //fprintf(stdout,"Last man standing is %dnr", (chain->first()->count() + 1)); return 0; } }
JAVA:
package com.dnene.josephus; public class Chain { private Person first = null; public Chain(int size) { Person last = null; Person current = null; for (int i = 0; i < size; i++) { current = new Person(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 = 100000; long start = System.nanoTime(); for (int i = 0; i < ITER; i++) { Chain chain = new Chain(40); chain.kill(3); } long end = System.nanoTime(); System.out.println("Time per iteration = " + ((end - start) / (ITER)) + " nanoseconds."); } } package com.dnene.josephus; public class Person { int count; private Person prev = null; private Person next = null; public Person(int count) { this.count = count; } 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; } }
Ruby / JRuby
class Person attr_reader :count, :prev, :next attr_writer :count, :prev, :next def initialize(count) #puts 'Initializing person : ' + count.to_s() @count = count @prev = nil @next = nil end def shout(shout, deadif) if shout < deadif return shout + 1 end @prev.next = @next @next.prev = @prev return 1 end end class Chain attr_reader :first attr_writer :first def initialize(size) @first = nil last = nil for i in (1..size) current = Person.new(i) if @first == nil @first = current end if last != nil last.next = current current.prev = last end last = current end @first.prev = last last.next = @first end def kill(nth) current = @first shout = 1 while current.next != current shout = current.shout(shout,nth) current = current.next end @first = current return current end end ITER=100000 start = Time.now ITER.times { |i| chain = Chain.new(40) chain.kill(3) } ends = Time.now puts 'Time per iteration = ' + ((ends - start) * 1000000 / ITER).to_s() + " microseconds"
PHP
<?php class Person { function __construct($c) { $this->count = $c; } function getPrev() { return $this->prev; } function setPrev($pr) { $this->prev = $pr; } function getNext() { return $this->next; } function setNext($nxt) { $this->next = $nxt; } function shout($shout, $nth) { if ($shout < $nth) { return $shout + 1; } $this->getPrev()->setNext($this->getNext()); $this->getNext()->setPrev($this->getPrev()); return 1; } } class Chain { var $first; function __construct($size) { for($i = 0; $i < $size ; $i++) { $current = new Person($i); if ($this->first == null) $this->first = $current; if ($last != null) { $last->setNext($current); $current->setPrev($last); } $last = $current; } $this->first->setPrev($last); $last->setNext($this->first); } function kill($nth) { $current = $this->first; $shout = 1; while($current->getNext() !== $current) { $shout = $current->shout($shout,$nth); $current = $current->getNext(); } $this->first = $current; } } $start = microtime(true); $ITER = 100000; for($i = 0 ; $i < $ITER ; $i++) { $chain = new Chain(40); $chain->kill(3); } $end = microtime(true); printf("Time per iteration = %3.2f microsecondsnr",(($end - $start) * 100000 / $ITER)); ?>
É isso pessoal.
Esse tema de desempenho sempre gera polêmica pois sempre há brechas e margens para discussão. Então, quem quiser debater sobre o assunto.
Até a próxima.
| Print article | This entry was posted by RicardoSEP on 27/07/2008 at 12:22 pm, and is filed under Programação. Follow any responses to this post through RSS 2.0. You can leave a response or trackback from your own site. |










about 1 year ago
É mesmo como você falou:
“gera polêmica pois sempre há brechas e margens para discussão”.
OK sabemos que estão “comparando” coisas completamente diferentes, no caso da sua versão do teste, é uma linguagem compilada, duas pseudo-compiladas e duas interpretadas. Como se pode ver pelos resultados, C++ executa sempre no mesmo tempo, bem razoável, e as demais, mostram mais ou menos variação.
O maior problema neste tipo de teste, na verdade é o código usado.
Repare que até visualmente, os programas se parecem, a impressão que dá, é que eles tentaram “ser justos”, comparando programas com “design” muito semelhantes, ocorre, que quando uma linguagem é compilada, pouco importa o tamanho do fonte, e muitas vezes até a lógica utilizada, pois o compilador (dependendo) pode otimizar alguma coisa.
Já nas linguagens pseudo-compiladas e interpretadas, o tamanho do código e a lógica utilizada influenciam bastante.
Olhando rapidamente o código Ruby, podemos ver que dá pra melhorar muita coisa ali…
O mais correto na minha opinião, seria pegar o enunciado do problema original, entregar para um especialista em cada linguagem, fazer umas 3 ou 4 rodadas de teste + otimização, e depois que não houvesse mais onde melhorar, fazer a comparação final.
Apesar de não provar muita coisa, acredito que seria MUITO mais justo.
Grande abraço.
about 1 year ago
Já com o Ruby essa discussão é antiga. Esse assunto foi muito abordado no Rails Summit. Ví muitas críticas no interpretador do Matz e também muitas alternativas subindo (Rubinius, REE, Yarv…). Acredito que o interpretador do Matz já está com os dias contados, isso não tem mais jeito.
Quanto ao Python e ao PHP, eles já estão mais maduros que o Ruby então é até justificável a diferença, porém não acredito que eles ficarão a frente do Ruby por muito tempo, já que, como eu disse, várias alternativas ao interpretador do Matz estão chegando.
Abraço