Самый эффективный способ в Python перебрать большой файл (10 ГБ+)

Я работаю над скриптом Python для просмотра двух файлов: один содержит список UUID, другой содержит большое количество записей журнала — каждая строка содержит один из UUID из другого файла. Цель программы состоит в том, чтобы создать список UUID из файла file1, а затем каждый раз, когда этот UUID будет найден в файле журнала, увеличивать связанное значение каждый раз, когда будет найдено совпадение.

Короче говоря, подсчитайте, сколько раз каждый UUID появляется в файле журнала. На данный момент у меня есть список, который заполнен UUID в качестве ключа и «попадания» в качестве значения. Затем еще один цикл, который перебирает каждую строку файла журнала и проверяет, соответствует ли UUID в журнале UUID в списке UUID. Если он совпадает, он увеличивает значение.

    for i, logLine in enumerate(logHandle):         #start matching UUID entries in log file to UUID from rulebase
        if logFunc.progress(lineCount, logSize):    #check progress
            print logFunc.progress(lineCount, logSize)  #print progress in 10% intervals
        for uid in uidHits:
            if logLine.count(uid) == 1:             #for each UUID, check the current line of the log for a match in the UUID list
                uidHits[uid] += 1                   #if matched, increment the relevant value in the uidHits list
                break                                #as we've already found the match, don't process the rest
        lineCount += 1               

Он работает как надо, но я уверен, что есть более эффективный способ обработки файла. Я просмотрел несколько руководств и обнаружил, что использование count быстрее, чем использование скомпилированного регулярного выражения. Я думал, что чтение файлов по частям, а не построчно, улучшит производительность за счет уменьшения времени дискового ввода-вывода, но разница в производительности для тестового файла ~ 200 МБ была незначительной. Если у кого-то есть другие способы, буду очень признателен :)


person SG84    schedule 02.06.2011    source источник
comment
Файловый ввод-вывод обычно буферизуется независимо от размера фрагментов, которые вы фактически читаете.   -  person    schedule 02.06.2011
comment
Нужно ли быть более эффективным? Сколько времени это занимает? Как долго вам это нужно? Возможно, вы уже достигли предела производительности вашего хранилища (диска), и в этом случае не имеет значения, насколько быстрее работает ваш скрипт Python.   -  person Nicholas Knight    schedule 02.06.2011
comment
Сейчас он работает с тестовым файлом - это половина файла размером 10 ГБ, и это заняло около 30 минут. Будучи моей первой прогулкой по Python, я действительно не знаю, быстро это или медленно. Нет необходимости в том, чтобы он завершился за x минут, но чем быстрее, тем лучше ;)   -  person SG84    schedule 02.06.2011
comment
В вашем примере второй оператор if пуст (после этого нет кода с отступом). Не могли бы вы это исправить?   -  person Steven Rumbalski    schedule 02.06.2011
comment
Вы просматриваете все UID для каждой строки в файле. Вместо этого найдите UUID в каждой строке и посмотрите его в словаре. Старайтесь делать как можно меньше в той части кода, которая вызывается чаще всего.   -  person Rosh Oxymoron    schedule 02.06.2011


Ответы (5)


Думайте функционально!

  1. Напишите функцию, которая возьмет строку файла журнала и вернет uuid. Назовите это uuid, скажем.

  2. Примените эту функцию к каждой строке файла журнала. Если вы используете Python 3, вы можете использовать встроенную карту функций; в противном случае вам нужно использовать itertools.imap.

  3. Передайте этот итератор в collections.Counter.

    collections.Counter(map(uuid, open("log.txt")))
    

Это будет в значительной степени оптимально эффективно.

Пара комментариев:

  • Это полностью игнорирует список UUID и просто подсчитывает те, которые появляются в файле журнала. Вам нужно будет немного изменить программу, если вы этого не хотите.

    • Your code is slow because you are using the wrong data structures. A dict is what you want here.
person Katriel    schedule 02.06.2011
comment
Спасибо за вклад - как только этот тестовый прогон завершится и я верну свои ресурсы, я посмотрю. Я думаю, что использовал список вместо словаря, потому что хотел сохранить порядок UUIDS, но я думаю, что мог бы использовать список позже в качестве индекса, а затем извлекать соответствующие значения из словаря? - person SG84; 02.06.2011
comment
@ SG84, вы можете посмотреть отличную статью о генераторах Python dabeaz.com/generators/Generators.pdf, особенно для обработки больших файлов. Просветишь :-) - person OnesimusUnbound; 02.06.2011
comment
Я пробовал генератор ранее, после того, как я поднял свой мозг с пола и заставил его работать, прирост производительности не сильно отличался от простого использования цикла for над файлом. спасибо за ссылку, хотя, это все делает для хорошего чтения - person SG84; 02.06.2011
comment
Итак, после того, как я освоился с генераторами и модулем коллекций, я изменил пару функций в своем коде, и общее время выполнения файла размером 250 МБ снизилось с 96,4 секунды до 5,4!! Это крупная победа. Большое спасибо всем за их вклад :) - person SG84; 06.06.2011

Как уже говорилось выше, с файлом размером 10 ГБ вы, вероятно, довольно быстро достигнете пределов своего диска. Для улучшения кода отлично подойдет совет генератора. В python 2.x это будет выглядеть примерно так

uuid_generator = (line.split(SPLIT_CHAR)[UUID_FIELD] for line in file)

Похоже, это не обязательно должно быть проблемой Python. Если вы не делаете ничего более сложного, чем подсчет UUID, Unix может решить ваши проблемы быстрее, чем Python.

cut -d${SPLIT_CHAR} -f${UUID_FIELD} log_file.txt | sort | uniq -c 
person blinsay    schedule 02.06.2011

Это не 5-строчный ответ на ваш вопрос, но на PyCon'08 было представлено отличное руководство под названием Трюки с генераторами для системных программистов. Существует также дополнительное руководство под названием A Curious Course on Coroutines and Concurrency.

В учебнике по генератору в качестве примера специально используется обработка больших файлов журнала.

person Peter Rowell    schedule 02.06.2011

Вы пробовали mincemeat.py? Это реализация Python системы распределенных вычислений MapReduce. Я не уверен, что вы получите прирост производительности, поскольку я еще не обработал 10 ГБ данных перед его использованием, хотя вы можете изучить эту структуру.

person OnesimusUnbound    schedule 02.06.2011

Попробуйте измерить, на что тратится больше всего времени, используя профилировщик http://docs.python.org/library/profile.html

Где лучше оптимизировать, зависит от характера ваших данных: если список uuid не очень длинный, вы можете обнаружить, например, что большая часть времени тратится на «if logFunc.progress(lineCount, logSize )». Если список очень очень длинный, вам может помочь сохранить результат uidHits.keys() в переменной вне цикла и выполнить итерацию по ней вместо самого словаря, но Рош Оксюморон предлагает сначала найти идентификатор, а затем тогда проверка его в uidHits, вероятно, поможет еще больше.

В любом случае вы можете исключить переменную lineCount и вместо нее использовать i. И find(uid) != -1 может быть лучше, чем count(uid) == 1, если строки очень длинные.

person slowdog    schedule 02.06.2011