Script de aceleração

0

Qual comando shell é o modo mais rápido de analisar milhões de linhas de texto. Atualmente estou usando o GREP no meu script, mas demora horas e horas para terminar.

Entrada de amostra:

May  1 2014 00:00:00 Allow
May  1 2014 00:00:00 Allow
May  1 2014 01:00:00 Deny
May  1 2014 01:00:00 Deny
May  1 2014 02:00:00 Allow
May  1 2014 02:00:00 Deny

Exemplo de saída:

(onde "2" na linha um é 'grep -c "permite"' e "0" é 'grep -c "nega"')

May 1 2014 00:00:00,2,0
May 1 2014 01:00:00,0,2
May 1 2014 02:00:00,1,1
por Roboman1723 13.06.2014 / 19:29

2 respostas

2

Afaste-se das expressões regulares. Eles são lentos (em todos os idiomas) e são muito mais do que precisamos aqui, o que equivale a simples comparações de substring.

  • Pegue uma substring de 0:20 como a primeira chave
  • Pegue a substring de 21:22 (single char) como o resultado booleano da segunda chave
  • O valor dessa combinação deve ser um inteiro que você acabou de incrementar a cada vez que você a vê.

Eu implementei essa ideia abaixo em Python:

data = {}
with open("file", "r") as f:
    for line in f:
        key = line[0:20]
        allowed = int(line[21:22] != "A")

        if not key in data:
            data[key] = [0,0]
        data[key][allowed] += 1

for key, val in data.items():
    print('%s,%d,%d' % (key, val[0], val[1]))

Nenhuma ideia de como isso funciona, mas dê uma chance. Se isso for mais lento, converta-o para C ++ (um pouco mais de um PITA, e é por isso que estou usando o Python!) E isso deve rasgar seus dados. Não é uma programação difícil, mas é o que é necessário para uma velocidade ideal.

Um pouco de refatoração, mais difícil de portar, a menos que você tenha um equivalente a defaultdict :

from collections import defaultdict

data = defaultdict(lambda: [0,0])
with open("file", "r") as f:
    for line in f:
        key = line[0:20]
        allowed = int(line[21:22] != "A")
        data[key][allowed] += 1

for key, val in data.items():
    print('%s,%d,%d' % (key, val[0], val[1]))

E uma implementação em Python de um híbrido de steeldriver e minhas ideias. Esta é provavelmente a mais eficiente de memória que você obterá e está usando a comparação de substring em vez de uma extração de regex, por isso deve ser nippy. É necessária uma entrada classificada.

last = ""
score = [0,0]

with open("file", "r") as f:
    for line in f:
        key = line[0:20]
        allowed = int(line[21:22] != "A")

        if key and key != last:
            print '%s,%d,%d' % (last, score[0], score[1])
            score = [0,0]
            last = key

        score[allowed] += 1

print '%s,%d,%d' % (last, score[0], score[1])

Avaliação comparativa

No interesse de obter algo disso testado (para minha própria curiosidade, tanto quanto qualquer outra coisa), decidi fazer um pequeno benchmarking em um arquivo de registro de 2.400.000 que abrange 2400 datas separadas.

Eu usei o seguinte script Python para gerar um arquivo grande com terminações aleatórias Permitir / Negar:

import itertools, datetime, random

CHOICES = ['Allow', 'Deny']

with open("file", "w") as f:
    for _ in itertools.repeat(None, 2400):
        epoch = random.randint(1, 1404226041)
        d = datetime.datetime.fromtimestamp(epoch)
        print d
        dstr = d.strftime('%b %d %Y %X')

        for _ in itertools.repeat(None, 1000):
            f.write('%s %s\n' % (dstr, CHOICES[random.randint(0,1)]))

Isso foi cerca de mil vezes mais rápido que o equivalente ao Bash (consulte o log de revisão) e nos fornece um arquivo de log diversificado para ser usado. Ele não está trocado, portanto, os dois benchmarks que precisam de entrada agrupada (3 e 4 abaixo) estão usando uma versão separada separada ( sort file > file-sorted , que levou 0m4.253s para ser concluída).

  1. Meu primeiro: 0m1.544s
  2. Meu refatorador com defaultdict : 0m1.413s
  3. Classificação de awk : 0m5.168s + 0m4.253s da Steeldriver
  4. Minha reimplementação do Python da classificação # 3: 0m1.489s + 0m4.253s

Repeti a geração com 2,4 milhões de datas distintas (deveria levar os meus dois primeiros aos seus limites). Esse tipo levou 0m6.999s. Eu também adicionei pypy timings para as versões do Python.

  1. 0m11.589s (0m7.080s em pypy )
  2. 0m11.307s (0m7.087s em pypy )
  3. 0m8.652s + 0m6.999s
  4. 0m6.586s + 0m6.999s (0m1.566s em pypy )

Análise e resultados

  • Em pequenos conjuntos de chaves, 1 e 2 têm melhor desempenho. pypy ajuda em conjuntos de chaves maiores.
  • 4 é mais rápido que 3 awk em grande parte porque não estamos regexando
  • 4 é o mais rápido e tem o menor footprint, mas apenas se os dados forem pré-classificados
  • 2 é mais rápido se tivermos dados misturados
  • A classificação externa é muito lenta.
por Oli 13.06.2014 / 22:38
1

É difícil adivinhar se pode ser mais eficiente, pois você não postou detalhes suficientes sobre o que está fazendo agora, mas se os dados forem classificados na ordem timestamp Eu sugiro um algoritmo algo como

  • acumula as contagens de Allow e Deny até que o registro de data e hora seja alterado (ou chegamos ao final da entrada)
  • imprime o resultado e (se não tivermos chegado ao final da entrada) redefinir as contagens

No awk, você poderia fazer isso como algo como

awk '

FNR==1 {t = $1FS$2FS$3FS$4}

$1FS$2FS$3FS$4 == t {
  a += $5=="Allow" ? 1 : 0
  d += $5=="Deny" ? 1 : 0
}

$1FS$2FS$3FS$4 != t {
  printf "%s,%d,%d\n",t,a,d
  a = $5=="Allow" ? 1 : 0
  d = $5=="Deny" ? 1 : 0
  t = $1FS$2FS$3FS$4
}

END {printf "%s,%d,%d\n",t,a,d}

' input
    
por steeldriver 13.06.2014 / 22:29