Comparação de desempenho lendo PDFs

Eu estava achando bem divertido traduzir para diferentes linguagens o programa de ver o número de páginas num arquivo PDF. Serve para aprender novas linguagens e me aperfeiçoar naquelas que eu já sei. E, depois de tudo implementado, dá pra comparar o desempenho dos compiladores e descobrir algumas coisas bem interessantes.

Fiz os testes da seguinte forma:

Linguagens usadas: Python, F#, D, Java e C++

Entrada: 261 arquivos PDF.

Saída: o nome de cada arquivo seguido do seu número de páginas.

Execução: comparar o desempenho de passar todos os arquivos pela linha de comando ao processo e iniciar um novo processo para ler cada arquivo. Todos os processos foram executados seqüencialmente, nada em paralelo.

A medição foi feita usando o PowerShell. Resumidamente, os comandos usados foram:

$all_pdfs = ls -filter *.pdf -recurse | foreach { $_.fullname }
measure-command { $all_pdfs | foreach { &$cmd $_ | out-host } }
measure-command { &$cmd @all_pdfs | out-host }

onde a variável $cmd contém uma função que chama o programa desejado (C++, F#, Java, Python, ou D). O código completo do script está no final deste post.

Cada teste foi rodado em seqüência 3 vezes e o menor tempo foi registrado na tabela.

1 processo 1 processo (memória)
múltiplos processos
C++ 2.44s (32 bits)
2.03s (64 bits)
m < 10MiB 4.73s (32 bits)
3.99s (64 bits)
Java 2.19s (64 bits) m > 70MiB 42.79s (64 bits)
F# 3.67s (32 bits)
3.55s (64 bits)
ngen:
3.41s (64 bits)
10MiB < m < 70MiB 19.19s (32 bits)
72.43s (64 bits)
ngen:
14.94s (64 bits)
D 3.82s (32 bits) m > 70MiB 6.88s (32 bits)
Python 20.34s (64 bits) 10MiB < m < 70MiB 32.5s (64 bits)

Implementações usadas:

C++

MS VC++ 2010 Express + Windows SDK v7.1
Modo release, otimizações no máximo (/Ox), NDEBUG, buffer security check desligado (/GS-), floating-point rápido (/fp:fast).

Java

JDK 6 update 21 (64 bits)
Nenhuma opção de otimização. Todas as que eu tentei pioravam a performance.

F#

F# 2.0.0.0  + Visual Studio 2010 Shell
Modo release

D

DMD 2.049
Com as opções de otimização: -O -release -inline -noboundscheck
A idéia não era misturar 32 e 64 bits nos testes, mas quando eu vi eu estava com Java e Python em 64 bits (é necessário instalar um pacote separado para cada arquitetura), mas C++ em 32 bits. Sendo que F# é 2-em-1 (32 e 64 bits), baixei o compilador 64 bits para C++ e só então me dei conta que o compilador 64 bits de D ainda não está pronto, ele está sendo finalizado e será lançado em breve. Daí não quis reverter tudo pra 32 bits e deixei assim mesmo.

Python

Python 3.1.2 (64 bits)
Nenhuma opção de otimização, porque os arquivos pyc (Python compilado) e pyo (Python compilado e otimizado) resultaram iguais.

Máquina usada:

Intel Core 2 Quad Q8400 2.66GHz

4GiB de RAM

Windows 7 Ultimate 64 bits (inclusive para rodar as versões 32 bits)

Winamp tocando música durante todos os testes, porque fazer isso em silêncio teria sido muito chato.

Conclusões:

Embora os números já estejam aí em cima, eu gosto de ler uma análise com palavras para sustentar os dados exibidos. Então vou escrever o que concluí.

C++ continua sendo o mais rápido, nenhuma surpresa aqui. Mas eu tinha me esquecido como certos detalhes da linguagem são complicados. Ainda resolvi usar vários recursos do novo C++ (o C++0x ou C++1x) e nem sei se fiz direito. Por usar esses recursos novos, faltou testar com outros compiladores C++, pra ver se o mérito é apenas do VC++ ou se outras implementações também atingem esse desempenho.

Java conseguiu a façanha de marcar o segundo melhor tempo e o segundo pior tempo. Se todos os arquivos são processados pelo mesmo programa o JIT faz um trabalho incrível e chega muito perto da velocidade do C++ (considerando a freqüência que se usa new em Java, é incrível mesmo). Mas se o programa for fechado e reiniciado para cada arquivo, o tempo de carga da JVM pesa muito, e a mesma tarefa que antes levou pouco mais de 2 segundos, passa a levar 42 segundos!!

F#, por ser também uma linguagem compilada com JIT, ficou bem próximo ao Java tanto na velocidade do código gerado pelo JIT quanto pela lentidão da inicialização. Quando eu vi o tempo de mais de 1 minuto, vi que tinha alguma coisa errada e resolvi testar um pouco melhor, pois eu sabia que anteriormente tinha feito outro teste que não tinha sido tão lento.

É que no meu teste anterior eu tinha compilado em 32 bits, e a lentidão se manifesta se compilar pra 64 bits. Será que isso é um bug ou uma característica do funcionamento da máquina virtual e compilador JIT? Imagino que Java também tenha caído nessa, porque fiz outros testes com Java 32 bits que ficaram na faixa de 15 a 22 segundos. Só não coloquei na tabela porque esse teste foi feito com Java 5, em outro computador e com um conjunto menor de arquivos, então não dá pra comparar diretamente, mas dá pra ter uma idéia da ordem de grandeza dos tempos.

Como a CLR (a infraestrutura do C#, F# e Visual Basic .NET) suporta a geração de código nativo com o programa ngen.exe, testei também o seu funcionamento. Como o meu sistema é 64 bits, ele só aceitou gerar executável 64 bits. O resultado foi bom, já que tirou o F# 64 da situação de pior desempenho de inicialização. Não sei por que Java não tem essa opção.

Eu achei que o desempenho do D fosse competir diretamente com o C++, mas na verdade ficou um pouco mais distante do que eu imaginava. O desempenho do Digital Mars D deve ser semelhante ao desempenho do Digital Mars C++, que eu não testei por falta de suporte aos recursos experimentais do novo padrão do C++. Onde D e C++ se destacam juntos é na inicialização (ver a última coluna). Por gerarem código nativo e não usarem nenhuma máquina virtual, executar todo o trabalho em vários processos (seqüenciais, lembrando) não adicionou muita sobrecarga, tendo um desempenho muitíssimo melhor que as outras 3 linguagens nesse teste.

Python corresponde a outra categoria, porque a implementação padrão usa uma máquina virtual sem JIT. Seria bom ter adicionado outra(s) linguagem(ns) nessa categoria. Mas o interessante é que, apesar disso torná-lo bem mais lento que as outras linguagens, ele não ficou em último lugar na coluna da direita por causa dos tropeços das máquinas virtuais do Java e da Microsoft (CLR, CLI, .NET, essas coisas) na inicialização.

Por último, adicionei a coluna do meio, do uso de memória. Enquanto o tempo eu medi com o Measure-Command do PowerShell, a memória foi no olhômetro mesmo, usando o gerenciador de tarefas, por isso os dados não estão tão precisos. Sendo a única implementação sem coletor de lixo, C++ usou muito menos memória que os outros, que deixavam bastante lixo se acumular antes de executar o coletor.

Edit 2014-04-12: Todos os fontes no BitBucket (o repositório é Git):
https://bitbucket.org/marcuscf/pdfpagecount/commits/tag/v2010-09-19

Código fonte em Java

Código fonte em C++

Código fonte em D

Código fonte em F#

Código fonte em Python

Script em PowerShell pra rodar tudo isso.

Próximo post: premiação.

Anúncios

Um pensamento sobre “Comparação de desempenho lendo PDFs

  1. Comparação de desempenho (2) « Visions of hope

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s