Como o ksh93 é tão rápido?

8

Assim, em geral, tenho a tendência de olhar para sed para processamento de texto - especialmente para arquivos grandes - e geralmente evito fazer esse tipo de coisa no próprio shell.

Eu acho que isso pode mudar. Eu estava bisbilhotando em man ksh e notei isso:

<#pattern     Seeks forward to the beginning of the
              next line containing pattern.

<##pattern    The same as <# except that  the  por‐
              tion  of  the file that is skipped is
              copied to standard output.

Cético quanto à utilidade do mundo real, decidi experimentá-lo. Eu fiz:

seq -s'foo bar
' 1000000 >file

... para um milhão de linhas de dados que se parecem com:

1foo bar
...
999999foo bar
1000000

... e colocou contra sed como:

p='^[^0-8]99999.*bar'
for c in "sed '/$p/q'" "ksh -c ':<##@(~(E)$p)'"    
do </tmp/file eval "time ( $c )"
done | wc -l

Assim, ambos os comandos devem chegar a 999999foo bar e sua implementação de correspondência de padrões deve avaliar pelo menos o início e o fim de cada linha para fazer isso. Eles também precisam verificar o primeiro caractere contra um padrão negado. Isso é uma coisa simples, mas ... Os resultados não foram o que eu esperava:

( sed '/^[^0-8]99999.*bar/q' ) \
    0.40s user 0.01s system 99% cpu 0.419 total
( ksh -c ':<##@(~(E)^[^0-8]99999.*bar)' ) \
    0.02s user 0.01s system 91% cpu 0.033 total
1999997

ksh usa ERE aqui e sed a BRE. Eu fiz a mesma coisa com ksh e um padrão de shell antes, mas os resultados não diferiram.

De qualquer forma, essa é uma discrepância bastante significativa - ksh supera sed 10 vezes. Eu já li antes que David Korn escreveu seu próprio io lib e o implementa em ksh - possivelmente isso está relacionado? - mas eu não sei quase nada sobre isso. Como é que a casca faz isso tão bem?

Ainda mais surpreendente para mim é que ksh realmente deixa seu deslocamento exatamente onde você pergunta. Para obter (quase) o mesmo de (GNU) sed , você tem que usar -u - muito lento .

Veja um teste grep v. ksh :

1000000         #grep + head
( grep -qm1 '^[^0-8]99999.*bar'; head -n1; ) \
    0.02s user 0.00s system 90% cpu 0.026 total
999999foo bar   #ksh + head
( ksh -c ':<#@(~(E)^[^0-8]99999.*bar)'; head -n1; )  \
    0.02s user 0.00s system 73% cpu 0.023 total

ksh bate grep aqui - mas nem sempre - eles estão praticamente empatados. Ainda assim, isso é muito bom, e ksh fornece lookahead - a entrada de head é iniciada antes de sua correspondência.

Parece bom demais para ser verdade, eu acho. Quais são esses comandos fazendo diferente sob o capô?

Ah, e aparentemente não há nem mesmo uma subshell aqui:

ksh -c 'printf %.5s "${<file;}"'
    
por mikeserv 22.12.2014 / 11:30

1 resposta

7

O ksh não só usa o sfio como também usa o seu próprio alocador de memória.

No entanto, meu palpite é que o sfio faz a diferença neste caso. Eu apenas tentei executar o seu exemplo sob strace e pode ver que o ksh chama de leitura / gravação ~ 200 vezes (blocos de 65 KB) enquanto o sed faz isso ~ 3400 vezes (blocos de 4 KB). Com sed -u meu laptop quase derretido, leituras são feitas por byte e escreve por linha. Ksh simples usa lseek. Grep usa ~ 400 vezes (blocos de 32 KB).

    
por 22.12.2014 / 14:48