melhor maneira de dividir arquivos grandes com base em um campo quando o awk é muito lento

6

Estou com problemas para lidar com enormes arquivos .gz (maiores que 500G). Meu objetivo é dividir cada um deles pelo quarto campo nesses arquivos. Existe este belo one-liner awk que eu usei antes para fazer isso:

zcat file.txt.gz | awk 'NR>1{print >  $4}'

Mas infelizmente isso leva muito tempo com arquivos grandes, então estou tentando dividi-los primeiro por tamanho e concatenar cada arquivo depois de ser dividido pelo campo. Posso dividi-los usando:

i=file.txt.gz
dir=$i
mkdir -p $dir
cd $dir
split -b 200M ../$i $i

for file in 'ls *'; do zcat $file | awk 'NR>1{print >  $4}'; done

Mas como faço para concatenar todos os arquivos corretos pelo quarto campo? Além disso, não há melhor maneira de fazer isso? Eu também estou recebendo um erro quando eu trabalho com arquivos gz divididos como este dizendo "fim de arquivo inesperado", então eu acho que minha divisão está errada também, mas não tenho certeza se eu estou indo na direção certa de qualquer maneira, por favor se você tenha sugestões seria muito útil.

Muito obrigado pela ajuda! Fra

    
por user971102 02.05.2017 / 07:25

1 resposta

2

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).

    
por 24.01.2018 / 21:05