while read LINE lento em bash script, problema e contorno...

Iniciado por boladegude, 28 de Dezembro de 2013, 01:48

tópico anterior - próximo tópico

boladegude

keywords:
while read lento em bash script
while read extremely slow in bash (a workaround)
very slow efficiency with big file
file processing loop too slow

Identifiquei um problema que acho que talvez devesse ser reportado como um bug do bash, mas não tenho certeza.
Ao mesmo tempo exponho como consegui um workaround para o problema.


Ocorre que no seguinte algoritmo (simplificado aqui), ocorre a degradação do tempo de processamento que começa a aumentar gradativamente até que se torna praticamente infinito.
Depois de mais de 24h o algoritmo ainda estava processando e não tinha chegado nem na metade do arquivo de 600.000 linhas.

O algoritmo é esse...

while read LINE
do
  while read char
  do
    if [ "$char" = "something1" ]
    then
      do this1
    else
      if ( $test ) # true or false
      then
        do this2
      else
        do this3
      fi
    fi
  done <<< LINE
done < /home/user/arquivo.txt

O comando if ( $test ) acima era o que estava causando o bug.
Esse comando no processamento de um arquivo de 600.000 linhas faz o algoritmo não terminar nunca.

Para resolver o problema apenas mudei esse comando para if [ $test = true ] e o problema foi resolvido, o algoritmo terminou em 2 horas.

O algoritmo ficou assim...

while read LINE
do
  while read char
  do
    if [ "$char" = "something1" ]
    then
      do this1
    else
      if [ $test = true ]
      then
        do this2
      else
        do this3
      fi
    fi
  done <<< LINE
done < /home/user/arquivo.txt

Assim o problema foi contornado!

Mas fica a dúvida... Por que esse problema? Isso é um bug do bash?
Se alguém tiver uma explicação ou outra solução para isto, por favor poste no tópico.
De qualquer forma espero que o que expus ajude outras pessoas que encontrem o mesmo problema.

Um abraço,
boladegude.
Become an Ubuntu user. Don't just use it as a virtual machine.
If you have some space in your HD then dual boot it with Windows or Mac.
It's a fine system. You shall be willing to study a little, but is worth your effort.

irtigor

Pelo que vejo, o mais provável é que o problema esteja na lógica aplicada, mas sem mais detalhes, não dá pra ajudar direito (preciso ver o código funcional, alguns exemplos de entrada e saber o quê deveria acontecer).

boladegude

Citação de: irtigor online 28 de Dezembro de 2013, 12:21
Pelo que vejo, o mais provável é que o problema esteja na lógica aplicada, mas sem mais detalhes, não dá pra ajudar direito (preciso ver o código funcional, alguns exemplos de entrada e saber o quê deveria acontecer).

Olá irtigor,

Acredite, não é um problema de lógica.
O fato da substituição de...
if ( $test )
...por ...
if [ $test = true ]
... resolver o caso, demostra bem que o problema tem a ver com a forma de escrever o comando.

A lógica está bem, tanto é que o processamento resultou com sucesso após 2h, com o workaround acima.
A função do algoritmo é substituir o aparecimento de sequencias de escape ISO em todo o arquivo pelos correspondentes UTF-8.
E deu certo!
A demora de 2h eu acho que tem a ver com bastante processamento de caracteres.
Become an Ubuntu user. Don't just use it as a virtual machine.
If you have some space in your HD then dual boot it with Windows or Mac.
It's a fine system. You shall be willing to study a little, but is worth your effort.

irtigor

Isso só aumentou a minha desconfiança que o problema foi na lógica empregada, mas sem o contexto correto, não dá pra confirmar. De qualquer forma, se funcionou (provavelmente apesar dessa linhas), ótimo.

boladegude

#4
Citação de: irtigor online 28 de Dezembro de 2013, 14:16
Isso só aumentou a minha desconfiança que o problema foi na lógica empregada, mas sem o contexto correto, não dá pra confirmar. De qualquer forma, se funcionou (provavelmente apesar dessa linhas), ótimo.

O algoritmo deu certo!
A demora de 2h após o workaround tem a ver com intenso processamento de caracteres.
Mas nem se compara à demora antes da substituição... haviam se passado 24h e tinham sido processadas apenas 300.000 linhas do total de 600.000 linhas e além disso o algoritmo não andava mais (empacou), foi progressivamente aumentando a demora até o inaceitável, entende?

Veja algo "parecido" com o que estou tentando lhe explicar no post inicial do tópico...
http://stackoverflow.com/questions/10364570/bash-while-read-line-efficiency-with-big-file
Become an Ubuntu user. Don't just use it as a virtual machine.
If you have some space in your HD then dual boot it with Windows or Mac.
It's a fine system. You shall be willing to study a little, but is worth your effort.

zekkerj

Olá boladegude, ainda está interessado na solução?

Seu problema não estava no "$test", mas sim no uso de parênteses "( )". O que estava acontecendo é que vc estava criando um sub-processo do shell pra executar o conteúdo da variável $test, o IF seria executado caso o sub-processo retornasse 0 (sem erro).

Quando vc mudou de "( $test )" para "[ $test = true ]" vc deixou de usar um sub-processo, pra usar um módulo do próprio shell chamado "test", o que faz com que a comparação seja feita pelo próprio processo, o que reduz sobremaneira o peso de processamento.
Pesquise antes de perguntar, sua dúvida pode já ter sido respondida.
Não respondo dúvidas por MP, coloque sua dúvida no fórum onde ela pode ser pesquisada pelos seus colegas!
Não venha ao fórum apenas para perguntar. Se você sabe a resposta de um problema, porque não ajudar seu colega? ;D

boladegude

Citação de: zekkerj online 17 de Maio de 2014, 11:12
Olá boladegude, ainda está interessado na solução?

Seu problema não estava no "$test", mas sim no uso de parênteses "( )". O que estava acontecendo é que vc estava criando um sub-processo do shell pra executar o conteúdo da variável $test, o IF seria executado caso o sub-processo retornasse 0 (sem erro).

Quando vc mudou de "( $test )" para "[ $test = true ]" vc deixou de usar um sub-processo, pra usar um módulo do próprio shell chamado "test", o que faz com que a comparação seja feita pelo próprio processo, o que reduz sobremaneira o peso de processamento.

zekkerj,
Muitíssimo obrigado!!!!!!!!!!
Algumas pessoas devem ter visto isso e não tiveram a boa vontade de ajudar :/
Isso me ajudou muito e acho que ajudou a muitos outros também!

Eu não havia me dado por conta de que os parênteses "()" do "if" eram um subshell, pensei que no "if" os parêteses tinham simplesmente a função de delimitadores de expressão taís como os colchetes "[]".

Dá pra comprovar mesmo que é um subshell assim...
user@cmptl1-1:~$ if (echo 'is it a subshell?') then echo 'yes, it is a subshell!'; fi
is it a subshell?
yes, it is a subshell!

E a melhor solução para corrigir o algoritmo que apresentei no post inicial é utilizar a variável $test sem estar entre (), assim...

user@cmptl1-1:~$ if $test; then echo 'nice!'; fi
nice!

Sua explicação me ajudou bem mais além disso... pois eu tinha outro subshell entre os dois "whiles" para preservar o IFS (que não mostrei aqui por simplicidade).
Removi o tal subshell e guardei o IFS numa variárel (OLDIFS), com isso meu algoritmo ficou 65% mais rápido  :o

Mostro como ficou no post abaixo...

Mais uma vez obrigado.
Um abraço,
boladegude.
Become an Ubuntu user. Don't just use it as a virtual machine.
If you have some space in your HD then dual boot it with Windows or Mac.
It's a fine system. You shall be willing to study a little, but is worth your effort.

boladegude

#7
while IFS=$'' read -r LINE || [ -n "$LINE" ]  ## após o primeiro $ são apóstrofos e não aspas.
do
 OLDIFS="$IFS"
 while IFS= read -r -n1 char
 do
   if [ "$char" = "something1" ]
   then
     do this1
   else
     if $test # true or false
     then
       do this2
     else
       do this3
     fi
   fi
 done <<< LINE
 IFS="$OLDIFS"
done < /home/user/arquivo.txt

Nas linhas onde salva e recupera a variável IFS, siga a formatação exata...
Ref: http://stackoverflow.com/questions/4128235/bash-shell-scripting-what-is-the-exact-meaning-of-ifs-n
... Essa estratégia evita ter que usar um subshell para manter o IFS do primeiro while.
Become an Ubuntu user. Don't just use it as a virtual machine.
If you have some space in your HD then dual boot it with Windows or Mac.
It's a fine system. You shall be willing to study a little, but is worth your effort.

boladegude

#8
Se alguém não conseguiu entender como é que tinha sido feito antes com o subshell entre os "whiles" então veja a seguir...

while IFS=$'' read -r LINE || [ -n "$LINE" ]
do
 (
 while IFS= read -r -n1 char
 do
   if [ "$char" = "something1" ]
   then
     do this1
   else
     if $test # true or false
     then
       do this2
     else
       do this3
     fi
   fi
 done <<< LINE
 )
done < /home/user/arquivo.txt

Use o método do post anterior por causa do que foi claramente explicado pelo zekkerj.
Become an Ubuntu user. Don't just use it as a virtual machine.
If you have some space in your HD then dual boot it with Windows or Mac.
It's a fine system. You shall be willing to study a little, but is worth your effort.