Самый быстрый способ "grep" больших файлов

У меня есть большие файлы журналов (от 100 МБ до 2 ГБ), которые содержат (одну) конкретную строку, которую мне нужно проанализировать в программе Python. Я должен разбирать около 20 000 файлов. И я знаю, что искомая строка находится в 200 последних строках файла или в последних 15000 байт.

Поскольку это повторяющаяся задача, мне нужно, чтобы она была как можно быстрее. Каков самый быстрый способ получить его?

Я подумал о 4 стратегиях:

  • прочитайте весь файл в Python и найдите регулярное выражение (method_1)
  • читать только последние 15 000 байт файла и искать регулярное выражение (method_2)
  • сделать системный вызов grep (method_3)
  • сделать системный вызов grep после завершения последних 200 строк (method_4)

Вот те функции, которые я создал для тестирования этих стратегий:

import os
import re
import subprocess

def method_1(filename):
    """Method 1: read whole file and regex"""
    regex = r'\(TEMPS CP :[ ]*.*S\)'
    with open(filename, 'r') as f:
        txt = f.read()
    match = re.search(regex, txt)
    if match:
        print match.group()

def method_2(filename):
    """Method 2: read part of the file and regex"""
    regex = r'\(TEMPS CP :[ ]*.*S\)'
    with open(filename, 'r') as f:
        size = min(15000, os.stat(filename).st_size)
        f.seek(-size, os.SEEK_END)
        txt = f.read(size)
        match = re.search(regex, txt)
        if match:
            print match.group()

def method_3(filename):
    """Method 3: grep the entire file"""
    cmd = 'grep "(TEMPS CP :" {} | head -n 1'.format(filename)
    process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
    print process.communicate()[0][:-1]

def method_4(filename):
    """Method 4: tail of the file and grep"""
    cmd = 'tail -n 200 {} | grep "(TEMPS CP :"'.format(filename)
    process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
    print process.communicate()[0][:-1]

Я запускал эти методы на двух файлах ( "трассировка" - 207 МБ, а "trace_big" - 1,9 ГБ) и получила следующее время вычисления (в секундах):

+----------+-----------+-----------+
|          |   trace   | trace_big |
+----------+-----------+-----------+
| method_1 | 2.89E-001 | 2.63      |
| method_2 | 5.71E-004 | 5.01E-004 |
| method_3 | 2.30E-001 | 1.97      |
| method_4 | 4.94E-003 | 5.06E-003 |
+----------+-----------+-----------+

Итак, метод_2 кажется самым быстрым. Но есть ли другое решение, о котором я не думал?

Изменить

В дополнение к предыдущим методам, Gosha F предложил пятый метод с использованием mmap:

import contextlib
import math
import mmap

def method_5(filename):
    """Method 5: use memory mapping and regex"""
    regex = re.compile(r'\(TEMPS CP :[ ]*.*S\)')
    offset = max(0, os.stat(filename).st_size - 15000)
    ag = mmap.ALLOCATIONGRANULARITY
    offset = ag * (int(math.ceil(offset/ag)))
    with open(filename, 'r') as f:
        mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_COPY, offset=offset)
        with contextlib.closing(mm) as txt:
            match = regex.search(txt)
            if match:
                print match.group()

Я тестировал его и получаю следующие результаты:

+----------+-----------+-----------+
|          |   trace   | trace_big |
+----------+-----------+-----------+
| method_5 | 2.50E-004 | 2.71E-004 |
+----------+-----------+-----------+

Ответ 1

Вы также можете рассмотреть возможность использования сопоставления памяти (mmap), как этот

def method_5(filename):
    """Method 5: use memory mapping and regex"""
    regex = re.compile(r'\(TEMPS CP :[ ]*.*S\)')
    offset = max(0, os.stat(filename).st_size - 15000)
    with open(filename, 'r') as f:
        with contextlib.closing(mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_COPY, offset=offset)) as txt:
            match = regex.search(txt)
            if match:
                print match.group()

также некоторые примечания:

  • в случае использования команды оболочки ag может быть в некоторых случаях на порядок быстрее, чем grep (хотя только с 200 линиями greppable text разница, вероятно, исчезает по сравнению с накладными расходами при запуске оболочки)
  • просто компиляция вашего регулярного выражения в начале функции может иметь значение

Ответ 2

Вероятно, быстрее выполнить обработку в оболочке, чтобы избежать накладных расходов python. Затем вы можете передать результат в python script. В противном случае похоже, что вы сделали самую быструю вещь.

Искажение регулярного выражения должно быть очень быстрым. Метод 2 и 4 те же, но вы несете дополнительные накладные расходы на python, создавая системный вызов.

Ответ 3

Нужно ли быть в Python? Почему не оболочка script?
Я предполагаю, что метод 4 будет самым быстрым/наиболее эффективным. Это, конечно, как я напишу его как shell script. И это стало быстрее, чем 1 или 3. Я бы все же потратил его по сравнению с методом 2 на 100% уверен, хотя.