Em um bash
script, preciso de vários valores de /proc/
arquivos. Até agora eu tenho dezenas de linhas grepping os arquivos diretamente assim:
grep -oP '^MemFree: *\K[0-9]+' /proc/meminfo
Em um esforço para tornar isso mais eficiente, salvei o conteúdo do arquivo em uma variável e fiz isso:
a=$(</proc/meminfo)
echo "$a" | grep -oP '^MemFree: *\K[0-9]+'
Em vez de abrir o arquivo várias vezes, isso deve abri-lo apenas uma vez e grep o conteúdo da variável, que eu assumi que seria mais rápido - mas na verdade é mais lento:
bash 4.4.19 $ time for i in {1..1000};do grep ^MemFree /proc/meminfo;done >/dev/null
real 0m0.803s
user 0m0.619s
sys 0m0.232s
bash 4.4.19 $ a=$(</proc/meminfo)
bash 4.4.19 $ time for i in {1..1000};do echo "$a"|grep ^MemFree; done >/dev/null
real 0m1.182s
user 0m1.425s
sys 0m0.506s
O mesmo vale para dash
e zsh
. Suspeitei do estado especial dos /proc/
arquivos como motivo, mas quando copio o conteúdo de /proc/meminfo
um arquivo regular e uso que os resultados são os mesmos:
bash 4.4.19 $ cat </proc/meminfo >meminfo
bash 4.4.19 $ time for i in $(seq 1 1000);do grep ^MemFree meminfo; done >/dev/null
real 0m0.790s
user 0m0.608s
sys 0m0.227s
Usar uma string here para salvar o pipe o torna um pouco mais rápido, mas ainda não tão rápido quanto com os arquivos:
bash 4.4.19 $ time for i in $(seq 1 1000);do <<<"$a" grep ^MemFree; done >/dev/null
real 0m0.977s
user 0m0.758s
sys 0m0.268s
Por que abrir um arquivo é mais rápido do que ler o mesmo conteúdo de uma variável?
Aqui, não se trata de abrir um arquivo versus ler o conteúdo de uma variável, mas sim de bifurcar um processo extra ou não.
grep -oP '^MemFree: *\K[0-9]+' /proc/meminfo
bifurca um processo que executagrep
que abre/proc/meminfo
(um arquivo virtual, na memória, sem E/S de disco envolvido) o lê e corresponde ao regexp.A parte mais cara disso é bifurcar o processo e carregar o utilitário grep e suas dependências de biblioteca, fazer a vinculação dinâmica, abrir o banco de dados locale, dezenas de arquivos que estão no disco (mas provavelmente armazenados em cache na memória).
A parte sobre leitura
/proc/meminfo
é insignificante em comparação, o kernel precisa de pouco tempo para gerar as informações egrep
precisa de pouco tempo para lê-las.Se você executar
strace -c
isso, verá que umaopen()
e umaread()
chamadas de sistema usadas para ler/proc/meminfo
são amendoins em comparação com tudo o quegrep
faz para iniciar (strace -c
não conta a bifurcação).Dentro:
Na maioria dos shells que suportam esse
$(<...)
operador ksh, o shell apenas abre o arquivo e lê seu conteúdo (e remove os caracteres de nova linha à direita).bash
é diferente e muito menos eficiente, pois bifurca um processo para fazer essa leitura e passa os dados para o pai por meio de um pipe. Mas aqui, é feito uma vez, então não importa.Dentro:
O shell precisa gerar dois processos, que estão sendo executados simultaneamente, mas interagem entre si por meio de um pipe. Essa criação, desmontagem, escrita e leitura do cachimbo tem um custo pequeno. O custo muito maior é a geração de um processo extra. A programação dos processos também tem algum impacto.
Você pode descobrir que usar o operador zsh o
<<<
torna um pouco mais rápido:No zsh e no bash, isso é feito escrevendo o conteúdo de
$a
um arquivo temporário, que é mais barato do que gerar um processo extra, mas provavelmente não lhe dará nenhum ganho comparado a obter os dados diretamente/proc/meminfo
. Isso ainda é menos eficiente do que sua abordagem que copia/proc/meminfo
em disco, pois a gravação do arquivo temporário é feita a cada iteração.dash
não suporta here-strings, mas seus heredocs são implementados com um pipe que não envolve a geração de um processo extra. Dentro:O shell cria um tubo, bifurca um processo. O filho executa
grep
com seu stdin como a extremidade de leitura do pipe, e o pai grava o conteúdo na outra extremidade do pipe.Mas esse manuseio de pipe e sincronização de processo provavelmente ainda será mais caro do que apenas obter os dados diretamente
/proc/meminfo
.O conteúdo do
/proc/meminfo
é curto e não leva muito tempo para ser produzido. Se você deseja economizar alguns ciclos de CPU, deseja remover as partes caras: bifurcar processos e executar comandos externos.Curti:
Evite
bash
embora cuja correspondência de padrões seja muito ineficiente. Comzsh -o extendedglob
, você pode encurtá-lo para:Observe que
^
é especial em muitos shells (Bourne, fish, rc, es e zsh com a opção extendedglob pelo menos), recomendo citá-lo. Observe também queecho
não pode ser usado para produzir dados arbitrários (daí o meu usoprintf
acima).No seu primeiro caso, você está apenas usando o utilitário grep e encontrando algo em file
/proc/meminfo
,/proc
é um sistema de arquivos virtual, então/proc/meminfo
o arquivo está na memória e requer muito pouco tempo para buscar seu conteúdo.Mas no segundo caso, você está criando um pipe e passando a saída do primeiro comando para o segundo comando usando esse pipe, o que é caro.
A diferença é por causa de
/proc
(porque está na memória) e pipe, veja o exemplo abaixo:Você está chamando um comando externo em ambos os casos (grep). A chamada externa requer um subshell. Bifurcar essa concha é a causa fundamental do atraso. Ambos os casos são semelhantes, portanto: um atraso semelhante.
Se você quiser ler o arquivo externo apenas uma vez e usá-lo (de uma variável) várias vezes, não saia do shell:
O que leva apenas cerca de 0,1 segundo em vez do 1 segundo completo para a chamada grep.