Оптимизация поиска и замены больших файлов в Python - PullRequest
6 голосов
/ 27 сентября 2010

Я полный новичок для Python или любого серьезного языка программирования в этом отношении. Я наконец-то получил прототип кода для работы, но я думаю, что он будет слишком медленным.

Моя цель - найти и заменить некоторые китайские иероглифы во всех файлах (они являются csv) в каталоге целыми числами в соответствии с файлом csv, который у меня есть. Файлы хорошо нумеруются по году-месяцу, например, 2000-01.csv, и будут единственными файлами в этом каталоге.

Я буду циклически перебирать около 25 файлов по 500 МБ каждый (и около миллиона строк). Словарь, который я буду использовать, будет иметь около 300 элементов, и я буду менять юникод (китайский символ) на целые числа. Я попытался выполнить тестовый запуск, и, предполагая, что все масштабируется линейно (?), Похоже, что для его запуска потребуется около недели.

Заранее спасибо. Вот мой код (не смейтесь!):

# -*- coding: utf-8 -*-

import os, codecs

dir = "C:/Users/Roy/Desktop/test/"

Dict = {'hello' : 'good', 'world' : 'bad'}

for dirs, subdirs, files in os.walk(dir):
    for file in files:
        inFile = codecs.open(dir + file, "r", "utf-8")
        inFileStr = inFile.read()
        inFile.close()
        inFile = codecs.open(dir + file, "w", "utf-8")
        for key in Dict:
            inFileStr = inFileStr.replace(key, Dict[key])
        inFile.write(inFileStr)
        inFile.close()

Ответы [ 4 ]

13 голосов
/ 27 сентября 2010

В вашем текущем коде вы читаете весь файл в память одновременно.Так как это файлы размером 500 МБ, это означает, что строки размером 500 МБ.И затем вы выполняете их повторную замену, что означает, что Python должен создать новую строку размером 500 МБ с первой заменой, затем уничтожить первую строку, затем создать вторую строку размером 500 МБ для второй замены, а затем уничтожить вторую строку и так далее,за каждую замену.Это оказывается довольно много копирования данных туда и обратно, не говоря уже об использовании большого количества памяти.

Если вы знаете, что замены всегда будут содержаться в строке, вы можете прочитать строку файлапострочно, перебирая его.Python буферизует чтение, что означает, что оно будет довольно оптимизировано.Вам следует открыть новый файл под новым именем для одновременной записи нового файла.Выполняйте замену по очереди в каждой строке и сразу же записывайте ее.Это значительно уменьшит объем используемой памяти и объем памяти, копируемой назад и вперед при замене:

for file in files:
    fname = os.path.join(dir, file)
    inFile = codecs.open(fname, "r", "utf-8")
    outFile = codecs.open(fname + ".new", "w", "utf-8")
    for line in inFile:
        newline = do_replacements_on(line)
        outFile.write(newline)
    inFile.close()
    outFile.close()
    os.rename(fname + ".new", fname)

Если вы не можете быть уверены, что они 'всегда буду на одной линии, все становится немного сложнее;вам придется читать в блоках вручную, используя inFile.read(blocksize), и внимательно следить за тем, возможно ли частичное совпадение в конце блока.Это не так просто сделать, но обычно все же стоит избегать строк размером 500 МБ.

Еще одним большим улучшением было бы то, что вы могли бы выполнять замены за один раз, вместо того, чтобы пытаться выполнить целую кучу замен по порядку.Есть несколько способов сделать это, но то, что подходит лучше всего, полностью зависит от того, что вы заменяете и на что.Для перевода отдельных символов во что-то еще может быть удобен метод translate объектов Unicode.Вы передаете его в кодировке Unicode (в виде целых чисел) в строки Unicode:

>>> u"\xff and \ubd23".translate({0xff: u"255", 0xbd23: u"something else"})
u'255 and something else'

Для замены подстрок (а не только отдельных символов) вы можете использовать модуль re.Функция re.sub (и метод скомпилированных регулярных выражений sub) может принимать вызываемый объект (функцию) в качестве первого аргумента, который затем будет вызываться для каждого совпадения:

>>> import re
>>> d = {u'spam': u'spam, ham, spam and eggs', u'eggs': u'saussages'}
>>> p = re.compile("|".join(re.escape(k) for k in d))
>>> def repl(m):
...     return d[m.group(0)]
...
>>> p.sub(repl, u"spam, vikings, eggs and vikings")
u'spam, ham, spam and eggs, vikings, saussages and vikings'
2 голосов
/ 27 сентября 2010

Я думаю, что вы можете значительно уменьшить использование памяти (и, таким образом, ограничить использование подкачки и ускорить процесс), читая строку за раз и записывая ее (после уже предложенных замен регулярного выражения) во временный файл - затем перемещая файлзаменить оригинал.

1 голос
/ 27 сентября 2010

Несколько вещей (не связанных с проблемой оптимизации):

dir + file должно быть os.path.join(dir, file)

Возможно, вы захотите не повторно использовать infile, а вместо этого открывать (и писать в)отдельный файлЭто также не увеличит производительность, но это хорошая практика.

Я не знаю, привязаны ли вы к вводу / выводу или к процессору, но если ваша загрузка процессора очень высока, вы можете использоватьмногопоточность, причем каждый поток работает с отдельным файлом (то есть с четырехъядерным процессором вы будете читать / записывать 4 разных файла одновременно).

0 голосов
/ 27 сентября 2010

Откройте файлы для чтения / записи ('r +') и избегайте двойного открытия / закрытия (и, вероятно, связанной с этим очистки буфера). Также, если это возможно, не записывайте обратно весь файл, ищите и записывайте только измененные области после замены содержимого файла. Чтение, замена, запись измененных областей (если есть).

Это все равно не поможет производительности слишком хотя бы много: я бы профилировал и определял, где на самом деле был удар по производительности, а затем переходил к оптимизации. Это может быть просто медленное чтение данных с диска, и вы мало что можете сделать с этим в Python.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...