[Rio-pm] Isso não deveria estar certo???

breno breno em rio.pm.org
Sábado Dezembro 8 22:48:54 PST 2012


2012/12/9 Aureliano Guedes <guedes_1000 em hotmail.com>:
> Breno, so uma duvida, antigamente eu usava muito arquivos temporario, eu
> deixei de usar pois achava que perdia muito em performance.
>
> Você acha que eu perco muito em performance usando a solução 4 no lugar das
> outras soluções?
>

Resposta brevíssima: não importa.



Pronto. Pode parar de ler aqui. É sério. Se, por alguma curiosidade
mórbida, vc quiser mais detalhes, continue lendo :)



Como a maioria das coisas do mundo, depende. Se você tiver RAM
infinita, manter tudo em memória é sempre muito mais rápido do que
fazer acessos ao disco. Para saber mais:
https://en.wikipedia.org/wiki/Space-time_tradeoff

No entanto, há casos em que você *quer* armazenar dados em arquivos
temporários, não só por questões de memória, mas também para ter
estados intermediários em sua execução. Ou quando, como no exemplo
anterior, vc quer transformar um arquivo em outro. Afinal, pense
comigo: se vc vai ler um arquivo de entrada e escrever num arquivo de
saída, pq precisa que seja o mesmo arquivo? Preservar o arquivo
original pode te ajudar não só na simplicidade do código como nos
casos em que você quer validar os resultados repetindo a execução, ou
depurar seu programa caso algo estranho esteja acontecendo. Sem o
original intacto, como recuperá-lo? Você provavelmente está guardando
cópia do arquivo original em algum lugar (por esses mesmos motivos)
então pq não oficializar a coisa e gravar a saída processada em outro
arquivo (que portanto deixa de ser temporário)?

Vejamos as operações envolvidas no problema. Sem arquivos temporário,
temos os seguintes passos:

1) ler o arquivo todo do disco para memória
2) processar os dados
3) voltar a posição do handle para o início do arquivo
4) escrever o arquivo no disco
5) truncar o restante

Já com arquivos temporários, temos:

1) ler o arquivo do disco para memória
2) processar os dados
3) escrever o arquivo novo no disco
4) renomear arquivo no disco para substituir original (se não for usar)

Otimizações à parte, o processo em si é bastante parecido, com uma
ação de leitura e outra de escrita (ok, duas se rolar o rename, mas
não é lá uma grande operação de E/S), então não deve fazer lá grandes
diferenças em termos de desempenho. Antes de continuar, esqueci de
colocar na resposta anterior uma outra solução entre a 4 e a 5, vamos
chamá-la de 4.5:

----------8<-----------
#!/usr/bin/env perl
use strict;
use warnings;
use autodie;

my $nome_original = 'arquivo.txt';

open my $orig_fh, '<', $nome_original;

my @linhas;
my $i = 0;
while ( my $linha = <$orig_fh> ) {
  if ($linha =~ s/^>.+$/>contig$i/ ) {
    $i++;
  }
}
continue {
    push @linhas, $linha;
}
close $orig_fh;

open $orig_fh, '>', $nome_original;
print $orig_fh @linhas;
close $orig_fh;
---------->8-----------

Essa solução tem duas diferenças em relação à 4. Aqui, estamos
acumulando os dados processados em memória (na variável @linhas) e,
após o processamento, reabrimos o mesmo arquivo, só que para escrita.
Continuamos com o perigo da manipulação sem locking, mas eliminamos a
necessidade do arquivo temporário.

Particularmente, continuo preferindo a solução 4, pelos mesmos motivos de antes.

Uma nota sobre desempenho: quando o único fator a ser considerado é
desempenho, a melhor solução é escrever em Assembly. Mais ainda:
quando o único fator a ser considerado for desempenho, ou você está
desenvolvendo algo muito específico *mesmo* ou, em geral, há uma
chance muito grande de algo estar fundamentalmente errado no seu
raciocínio. Esse erro é conhecido como "otimização prematura". Nas
palavras de Donald Knuth:

"Programadores desperdiçam um tempo enorme pensando sobre, ou se
preocupando com, a velocidade de partes não críticas de seus
programas, e essas tentativas de melhorar a eficiência na verdade
acabam tendo um forte impacto negativo quando depuração e manutenção
são consideradas. Precisamos esquecer eficiências menores em, digamos,
97% do tempo: otimização prematura é a raiz de todo o mal. No entanto
não podemos deixar passar a oportunidade naqueles 3% crítico."

Sempre que pensar em desempenho, lembre-se dessas sábias palavras.

(Para saber mais sobre a questão: http://c2.com/cgi/wiki?PrematureOptimization)

Para o seu caso, isso significa: programe SEMPRE com foco em
legibilidade, testabilidade e manutenção. Preocupe-se com desempenho
SOMENTE depois que isso se tornar um problema. Tem pessoas que se
prendem a benchmarks do tipo: a função X me deixa fazer 32000
operações por segundo, enquanto que a função Y me deixa fazer em 27000
operações por segundo, então é FATO que devemos escolher a função X,
pois é mais rápida, certo? Errado. Isso não deveria ser critério de
desempate, vc deveria escolher a função que for mais clara e fácil de
testar e de dar manutenção depois (extender, reduzir, alterar,
acoplar, etc). Afinal, se você faz mais de 1000 operações por segundo
com essa função, já está usando bastante. Mas repare que, mesmo que
você faça 25000 operações *por segundo*, ambas as funções vão
continuar com desempenho imperceptivelmente parecido. Isso é um caso
típico de otimização prematura - você provavelmente não deveria nem
estar procurando saber qual é a mais rápida nesse ponto.

Pense novamente no seu caso: o programa nem está pronto ainda. Digamos
que leve 3 segundos para rodar. E se levasse apenas 1 segundo (200% de
melhoria em desempenho!!) faria alguma diferença real? E se levasse 5,
em vez de 3? Há o caso maior também: se o seu programa leva 6 horas
pra rodar, faz diferença levar 6 horas e 20 minutos? ou 5 horas e 40
minutos? Provavelmente não. Agora, se leva 6 horas e uma otimização o
faria rodar em apenas 35 minutos, já é algo a se pensar.

Acredite: as pessoas normalmente passam muito mais tempo dando
manutenção a programas do que gastaram escrevendo os programas em
primeiro lugar. Se vc usar um voodoo ninja sagaz só pra satisfazer sua
sede de desempenho, vc está cometendo dois erros graves: o primeiro é
que está perdendo tempo se preocupando com desempenho antes de isso
ser um problema; e o segundo é que, dali pra frente, vc provavelmente
vai gastar muito mais tempo tentando entender o que aquilo faz, como
funciona e como resolver o pepino sem quebrar nada, do que tempo de
fato resolvendo o problema. Em outras palavras, vc agilizou o tempo de
execução mas retardou o tempo de manutenção. O problema é que
'execução' quem faz é a máquina, mas 'manutenção' quem faz é você.

Apenas quando o tempo de execução *realmente* se tornar um problema é
que, aí sim, vc pode procurar por gargalos. Note que não é saír
otimizando loucamente onde der, e sim observar o comportamento da
aplicação e ver onde ela *de fato* está gastando tempo. Muitas vezes
não é onde você acha. Hoje, no mundo Perl, a melhor maneira de se
identificar gargalos de desempenho é com o Devel::NYTProf
(https://metacpan.org/module/Devel::NYTProf), que te dá um diagnóstico
linha-a-linha de desempenho do seu programa. Usar é mole:

    perl   -d:NYTProf   meu_programa.pl    # atenção: isso vai rodar o
programa!!
    nytprofhtml --open

e o seu navegador deve abrir automaticamente com os resultados!

Você pode experimentar as diferentes soluções apresentadas nessa
thread, ou só as 4, 4.5 e 5, mas sinceramente eu perderia meu tempo
com coisas muito mais importantes como a resolução do problema em si
ou novos desafios. A menos que o desempenho desse programa realmente
esteja prejudicando o trabalho de alguém, caso em que eu rodaria o
nytprof pra saber se o problema está realmente no arquivo temporário
ou em outro lugar. Até que isso se torne um problema, como disse antes
na resposta brevíssima: não importa :)


[]s

-b


Mais detalhes sobre a lista de discussão Rio-pm