
¿Qué comando de shell es la forma más rápida de analizar millones de líneas de texto? Actualmente estoy usando GREP en mi script, pero me lleva horas terminarlo.
Entrada de muestra:
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
Salida de muestra:
(donde "2" en la línea uno es 'grep -c "permitir" ' y "0" es 'grep -c "denegar" ')
May 1 2014 00:00:00,2,0
May 1 2014 01:00:00,0,2
May 1 2014 02:00:00,1,1
Respuesta1
Aléjate de las expresiones regulares. Son lentos (en todos los idiomas) y son mucho más de lo que necesitamos aquí para lo que equivale a simples comparaciones de subcadenas.
- Tome una subcadena de 0:20 como primera clave
- Tome la subcadena de 21:22 (un solo carácter) como resultado booleano para la segunda clave.
- El valor de esa combinación debe ser un número entero que simplemente incrementas cada vez que lo ves.
He implementado esa idea a continuación en 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]))
No tengo idea de cómo funciona, pero inténtalo. Si es más lento, conviértalo a C++ (un poco más PITA, ¡por eso estoy usando Python!) y eso debería eliminar sus datos. No es una programación difícil, pero es lo que se requiere para una velocidad óptima.
Un poco de refactorización, más difícil de portar a menos que tengas un 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]))
Y una implementación en Python de un híbrido de las ideas de Steeldriver y las mías. Esta es probablemente la memoria más eficiente que obtendrá y utiliza una comparación de subcadenas en lugar de una extracción de expresiones regulares, por lo que debería ser ágil.Sin embargo, requirió una entrada ordenada.
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])
Evaluación comparativa
Con el fin de probar algo de esto (por mi propia curiosidad, más que por cualquier otra cosa), decidí hacer una pequeña evaluación comparativa en un archivo de 2.400.000 registros que cubre 2400 fechas distintas.
Utilicé el siguiente script de Python para generar un archivo grande con terminaciones aleatorias de Permitir/Denegar:
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)]))
Esto fue aproximadamente mil veces más rápido que el equivalente de Bash (consulte el registro de revisión) y nos brinda un archivo de registro diverso para jugar. No está ordenado, por lo que los dos puntos de referencia que necesitan datos intercalados (3 y 4 a continuación) utilizan una versión ordenada separada ( sort file > file-sorted
que tardó 0m4,253s en completarse).
- Mi primero: 0m1.544s
- Mi refactor con
defaultdict
: 0m1.413s - Steeldriver
awk
: 0m5.168s + 0m4.253s clasificación - Mi reimplementación de Python del n.° 3: clasificación 0m1.489s + 0m4.253s
Repetí la generación con 2,4 millones de fechas distintas (debería llevar mis dos primeras al límite). Este tipo tomó 0m6.999s. También agregué pypy
tiempos para las versiones de Python.
- 0m11.589s (0m7.080s en
pypy
) - 0m11.307s (0m7.087s en
pypy
) - 0m8.652s + 0m6.999s
- 0m6.586s + 0m6.999s (0m1.566s en
pypy
)
Análisis y resultados
- En conjuntos de claves pequeños, 1 y 2 funcionan mejor.
pypy
ayuda en conjuntos de claves más grandes. - 4 es más rápido que 3
awk
en gran parte porque no estamos regexionando - 4 es más rápido y ocupa menos espacio, pero solo si los datos vienen preclasificados
- 2 es más rápido si tenemos datos mezclados
- La clasificación externa esrealmente lento.
Respuesta2
Es difícil adivinar si podría ser más eficiente ya que no has publicado suficientes detalles sobre lo que estás haciendo ahora, perosi los datos están ordenados en orden de marca de tiempoYo sugeriría un algoritmo algo así como
- acumular
Allow
yDeny
cuenta hasta que cambie la marca de tiempo (o lleguemos al final de la entrada) - imprimir el resultado y (si no hemos llegado al final de la entrada) restablecer los recuentos
En awk, podrías hacer eso 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