O comentário do descritor de arquivos de Satō Katsura está no caminho certo, supondo que existam mais de 1021 (geralmente o limite FD de usuário de 1024, -3 para stdin / stdout / stderr) valores distintos de $ 4 e que você está usando gawk
.
Quando você imprime em um arquivo usando >
ou >>
, o arquivo permanece aberto até um close()
explícito, para que seu script esteja acumulando FDs. Desde antes do Gawk v3.0, a falta de FDs ( ulimit -n
) é tratada de forma transparente: uma lista vinculada de arquivos abertos é percorrida e a LRU (usada menos recentemente) é fechada "temporariamente" (fechada do ponto de vista do SO liberar um FD, o gawk
rastreia internamente a reabertura transparente mais tarde, se necessário. Você pode ver isso acontecendo (da v3.1) adicionando -W lint
ao invocá-lo.
Podemos simular o problema assim (em bash
):
printf "%s\n" {0..999}\ 2\ 3\ 0{0..9}{0..9}{0..9} | time gawk -f a.awk
Isso gera 1.000.000 de linhas de saída com 1.000 valores únicos de $ 4 e leva ~ 17s no meu laptop. Meu limite é de 1024 FDs.
printf "%s\n" {0..499}\ 2\ 3\ {0..1}{0..9}{0..9}{0..9} | time gawk -f a.awk
Isso também gera 1.000.000 de linhas de saída, mas com 2000 valores únicos de $ 4 - isso leva ~ 110 segundos para ser executado (mais de seis vezes mais tempo e com falhas de página menores adicionais de 1M).
O acima é a entrada "mais pessima" do ponto de vista de manter $ 4, o arquivo de saída muda cada linha (e garante que o arquivo de saída necessário precisa ser (re) aberto toda vez).
Existem duas maneiras de ajudar com isso: menos rotatividade no uso de nome de arquivo (ou seja, pré-classificação por $ 4) ou parte da entrada com GNU split
.
Pré-classificação:
printf "%s\n" {0..499}\ 2\ 3\ {0..1}{0..9}{0..9}{0..9} |
sort -k 4 | time gawk -f a.awk
(pode ser necessário ajustar as opções sort
para concordar com a numeração de campos de awk
)
Em ~ 4.0s, isso é ainda mais rápido que o primeiro caso, já que o manuseio de arquivos é minimizado. (Observe que a classificação de arquivos grandes provavelmente usará arquivos temporários no disco em $TMPDIR
ou /tmp
.)
E com split
:
printf "%s\n" {0..499}\ 2\ 3\ {0..1}{0..9}{0..9}{0..9} |
time split -l 1000 --filter "gawk -f a.awk"
Isso leva ~ 38 segundos (então você pode concluir que até mesmo a sobrecarga de iniciar 1000 gawk
processos é menor do que o ineficiente manuseio interno de FD). Nesse caso, você deve usar >>
em vez de >
no script awk, caso contrário, cada novo processo irá chocar a saída anterior. (A mesma ressalva se aplica se você refizer seu código para chamar close()
.)
Você pode combinar os dois métodos:
printf "%s\n" {0..499}\ 2\ 3\ {0..1}{0..9}{0..9}{0..9} |
time split -l 50000 --filter "sort -k 4 | gawk -f a.awk"
Isso leva cerca de 4s para mim, ajustando o chunking (50000) permite que você troque a sobrecarga de processamento de processos / arquivos com os requisitos de uso de disco de sort
. YMMV.
Se você sabe antecipadamente o número de arquivos de saída (e não é muito grande), pode usar a raiz para aumentar (por exemplo, ulimit -n 8192
e su
para si mesmo) ou também poderá ajustar o limite geralmente, consulte Como posso aumentar a disponibilidade limite de arquivos para todos os processos? . O limite será determinado pelo seu sistema operacional e sua configuração (e possivelmente a libc se você não tiver sorte).