Perl Advent Calendar 2006-12-01

Coisas boas vêm em pequenos pacotes

por Jerrad Pierce

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;
countwall timecpu time
antes4784891.1605178.385
depois2000021.0231033.931
Note que o mantenedor atual do SmallProf criou uma versão XS que exige 5.8.8 no mínimo—nomeada Devel::FastProf. FastProf (segundo sua documentação) têm uma penalização de velocidade da ordem de 5x e provê um visualizador de resultados.

P.S. Sim, eu sei que o resultado final não é mostrado ;->

mod1.pl


   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 }

mod1b.pl


   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.