Bem vindo a uma nova edição do Calendário Perl de Advento. Com alguma ajuda de The Perl Foundation e da turma do Perl Mongers criei o PerlAdvent.PM aqui, onde os calendários atual e futuros podem viver; também estamos trabalhando para dar uma casa a todos os calendários anteriores. Eu espero delegar mais a tarefa de escrever para uma variedade mais ampla de voces da comunidade com o suporte de vários outros monges. E agora, o primeiro destes atos. Aproveite.
Quando você acabar de ler, vai notar que a amostra de código de hoje é extraordinariamente curta. Isto é porque o módulo em foco não lhe provê um toolkit (como um módulo para escrever mais código) mas sim uma ferramenta em si. Em algum momento você vai acabar escrevendo um programa que é simplesmente l e e e n t o para caramba. Neste momento seu próximo passo será usar um analisador de código, e por sorte Perl já inclui um, chamado Devel::DProf desde 5.6.0. DProf é demais, você o executa através do debugger, ele salva informação de tempo, e então você roda o parceiro dele, dprofpp, para ver onde está(ão) o(s) gargalo(s) de seu código.
O problema é que DProf pode não funcionar para todos os casos. DProf mantém quanto tempo é gasto em cada subrotina, o que funciona bem para código OO cheio de invocação de métodos. Mas e se você tiver subrotinas monolíticas que, por uma razão ou outra, você não planeja modificar em dezenas de pedaços geniais? E quais seriam os pedaços onde faria sentido decompor o código original? Ou, e se você não tem subrotinas como nosso simples exemplo abaixo da computação de pi por Monte Carlo? Bem, neste último caso dprofpp não vai mostrar nada!
Entra em cena Devel::SmallProf, o analisador linha-a-linha. SmallProf não tem um parceiro para mostrar resultados de forma aprimorada que seria o análogo de dprofpp, mas compensa muito esta falta em utilidade porque o SmallProf pode lhe dizer exatamente quais comandos estão consumindo a maior parte do tempo do processador. É claro que esta introspecção mais intensiva vem a um preço1. A análise com SmallProf é freqüentemente uma ordem de magnitude mais lenta do que com DProf! Então se for possível, é geralmente benéfico analisar primeiro com DProf para determinar a área geral do problema e então usar o $DB::profile do SmallProf para ativar a análise só para as áreas de interesse.
Aqui está um exemplo da "saída" do SmallProf. Note que as legendas das colunas foram parar no final da listagem (devido ao sort).
$ perl -d:SmallProf mod1.pl
$ sort -k 2nr,2 smallprof.out #POD provided command
99999 0.383615 1.879000 15: printf "PI ~ %1.8f after %0${COUNT}i
99999 0.303463 1.815000 10: if( ($x**2 + $y**2) <= 1 ){
99999 0.200046 1.721000 6: my $x = rand();
99999 0.178753 1.713000 7: my $y = rand();
78489 0.094622 1.257000 11: $HIT++;
1 0.000008 0.000000 5:for(my $i = 1; $i < 10**$COUNT; ++$i){
1 0.000006 0.000000 2:my $COUNT = 5; #Orders of magnitude
1 0.000002 0.000000 3:my $HIT = 0;
1 0.000002 0.000000 17:}
Profile of mod1.pl Page 1
================ SmallProf version 1.15 ================
0 0.000000 0.000000 1:#!/usr/bin/perl
0 0.000000 0.000000 4:
0 0.000000 0.000000 8:
0 0.000000 0.000000 9: #The equals condition is neglected in many
0 0.000000 0.000000 12: }
0 0.000000 0.000000 13:
0 0.000000 0.000000 14: #Multiply by 4 as we're using a single
0 0.000000 0.000000 16: ($i % 1_000) == 0;
=================================================================
count wall tm cpu time line
Depois de uma rápida revisão que funde quatro das cinco linhas mais lentas
(linhas 6-11, em mod1.pl) nós chegamos a uma versão mais densa e mais limpa (linha 6, em mod1b.pl) que executa muito mais rapidamente. Também podemos ter resultados mais elegantes usando o comando mais complicado provido por Tom Metro.
$ perl -d:SmallProf mod1b.pl
$ tail +4 smallprof.out | sort -t '\0' -k 1.29r,1.29 -k 1.11nr,1.18
count wall tm cpu time line
99999 0.575161 1.968000 6: $HIT++ if rand()**2 + rand()**2 <= 1;
99999 0.462837 1.896000 8: printf "PI ~ %1.8f after %0${COUNT}i
1 0.000010 0.000000 5:for(my $i = 1; $i < 10**$COUNT; ++$i){
1 0.000005 0.000000 2:my $COUNT = 5; #Orders of magnitude
1 0.000004 0.000000 3:my $HIT = 0;
1 0.000003 0.000000 10:}
0 0.000000 0.000000 1:#!/usr/bin/perl
0 0.000000 0.000000 4:
0 0.000000 0.000000 7:
0 0.000000 0.000000 9: ($i % 1_000) == 0;
| count | wall time | cpu time | |
|---|---|---|---|
| antes | 478489 | 1.160517 | 8.385 |
| depois | 200002 | 1.023103 | 3.931 |
P.S. Sim, eu sei que o resultado final não é mostrado ;->
1 #!/usr/bin/perl 2 my $COUNT = 5; #Orders of magnitude 3 my $HIT = 0; 4 5 for(my $i = 1; $i < 10**$COUNT; ++$i){ 6 my $x = rand(); 7 my $y = rand(); 8 9 #The equals condition is neglected in many implementations 10 if( ($x**2 + $y**2) <= 1 ){ 11 $HIT++; 12 } 13 14 #Multiply by 4 as we're using a single quadrant 15 printf "PI ~ %1.8f after %0${COUNT}i points\n", 4*$HIT/$i, $i if 16 ($i % 1_000) == 0; 17 }
1 #!/usr/bin/perl 2 my $COUNT = 5; #Orders of magnitude 3 my $HIT = 0; 4 5 for(my $i = 1; $i < 10**$COUNT; ++$i){ 6 $HIT++ if rand()**2 + rand()**2 <= 1; 7 8 printf "PI ~ %1.8f after %0${COUNT}i points\n", 4*$HIT/$i, $i if 9 ($i % 1_000) == 0; 10 }1. Devido ao modo como funciona baseado em linhas de código, o SmallProf pode lhe trazer resultados estranhos para código que usa AUTOLOAD.
Traduzido por Adriano Ferreira.