Обработать очень большой (> 20 ГБ) текстовый файл по строкам
У меня есть несколько очень больших текстовых файлов, которые мне нужно обрабатывать, причем самое большое - около 60 ГБ.
Каждая строка имеет 54 символа в семи полях, и я хочу удалить последние три символа из каждого из трех первых полей, что должно уменьшить размер файла примерно на 20%.
Я новичок в Python и имею код, который будет делать то, что я хочу делать примерно в 3,4 ГБ в час, но для того, чтобы быть полезным упражнением, мне действительно нужно получать не менее 10 ГБ/ч - есть ли какие-либо способ ускорить это? Этот код не близок к вызову моего процессора, поэтому я делаю необразованное предположение, что он ограничен скоростью чтения и записи на внутреннем жестком диске?
ProcessLargeTextFile():
r = open("filepath", "r")
w = open("filepath", "w")
l = r.readline()
while l:
x = l.split(' ')[0]
y = l.split(' ')[1]
z = l.split(' ')[2]
w.write(l.replace(x,x[:-3]).replace(y,y[:-3]).replace(z,z[:-3]))
l = r.readline()
r.close()
w.close()
Любая помощь будет действительно оценена. Я использую IDLE Python GUI в Windows 7 и имею 16 ГБ памяти - возможно, другая ОС была бы более эффективной?
Изменить: Вот фрагмент файла для обработки.
70700.642014 31207.277115 -0.054123 -1585 255 255 255
70512.301468 31227.990799 -0.255600 -1655 155 158 158
70515.727097 31223.828659 -0.066727 -1734 191 187 180
70566.756699 31217.065598 -0.205673 -1727 254 255 255
70566.695938 31218.030807 -0.047928 -1689 249 251 249
70536.117874 31227.837662 -0.033096 -1548 251 252 252
70536.773270 31212.970322 -0.115891 -1434 155 158 163
70533.530777 31215.270828 -0.154770 -1550 148 152 156
70533.555923 31215.341599 -0.138809 -1480 150 154 158
Ответы
Ответ 1
Это более идиоматично писать свой код, подобный этому
def ProcessLargeTextFile():
with open("filepath", "r") as r, open("outfilepath", "w") as w:
for line in r:
x, y, z = line.split(' ')[:3]
w.write(line.replace(x,x[:-3]).replace(y,y[:-3]).replace(z,z[:-3]))
Основное сохранение здесь - это просто сделать split
один раз, но если ЦП не облагается налогом, это, вероятно, будет иметь очень малое значение.
Это может помочь сэкономить несколько тысяч строк за один раз и записать их одним ударом, чтобы уменьшить износ вашего жесткого диска. Миллион строк - всего 54 МБ ОЗУ!
def ProcessLargeTextFile():
bunchsize = 1000000 # Experiment with different sizes
bunch = []
with open("filepath", "r") as r, open("outfilepath", "w") as w:
for line in r:
x, y, z = line.split(' ')[:3]
bunch.append(line.replace(x,x[:-3]).replace(y,y[:-3]).replace(z,z[:-3]))
if len(bunch) == bunchsize:
w.writelines(bunch)
bunch = []
w.writelines(bunch)
предложенный @Janne, альтернативный способ генерации строк
def ProcessLargeTextFile():
bunchsize = 1000000 # Experiment with different sizes
bunch = []
with open("filepath", "r") as r, open("outfilepath", "w") as w:
for line in r:
x, y, z, rest = line.split(' ', 3)
bunch.append(' '.join((x[:-3], y[:-3], z[:-3], rest)))
if len(bunch) == bunchsize:
w.writelines(bunch)
bunch = []
w.writelines(bunch)
Ответ 2
Measure! У вас есть несколько полезных советов, как улучшить код Python, и я согласен с ними. Но сначала вы должны выяснить, какова ваша настоящая проблема. Мои первые шаги, чтобы найти ваше узкое место:
- Удалите любую обработку из вашего кода. Просто прочитайте и напишите данные и измерьте скорость. Если чтение и запись файлов происходит слишком медленно, это не проблема вашего кода.
- Если чтение и запись уже медленны, попробуйте использовать несколько дисков. Вы одновременно читаете и пишете. На том же диске? Если да, попробуйте использовать разные диски и повторите попытку.
- Может также помочь некоторая библиотека async io (Twisted?).
Если вы выяснили точную проблему, попросите снова об оптимизации этой проблемы.
Ответ 3
Как вы, кажется, не ограничены процессором, а скорее I/O, попробовали ли вы с некоторыми изменениями третьего параметра open
?
В самом деле, этот третий параметр может использоваться, чтобы предоставить размер буфера, который будет использоваться для операций с файлами!
Простое запись open( "filepath", "r", 16777216 )
будет использовать буферы 16 МБ при чтении из файла. Это должно помочь.
Используйте то же самое для выходного файла и измерьте/сравните с идентичным файлом для остальных.
Примечание. Это та же самая оптимизация, предложенная другими, но вы можете получить ее здесь бесплатно, без изменения кода, без необходимости буферизации.
Ответ 4
Я добавлю этот ответ, чтобы объяснить, почему буферизация имеет смысл, а также предлагает еще одно решение
Вы получаете потрясающе плохую производительность. Эта статья Возможно ли ускорение ввода-вывода python? показывает, что чтение 10 ГБ должно занимать около 3 минут. Последовательная запись имеет одинаковую скорость. Таким образом, вам не хватает фактора в 30 раз, и ваша целевая производительность по-прежнему в 10 раз медленнее, чем то, что должно быть возможным.
Почти наверняка такое несоответствие заключается в количестве голов, которое ищет диск. Головной поиск занимает миллисекунды. Один поиск соответствует нескольким мегабайтам последовательного чтения-записи. Огромно дорого. Операции копирования на одном диске требуют поиска между входом и выходом. Как уже было сказано, одним из способов сокращения запросов является буферизация таким образом, что многие мегабайты считываются перед записью на диск и наоборот. Если вы можете убедить систему python io сделать это, отлично. В противном случае вы можете читать и обрабатывать строки в массиве строк, а затем писать после того, как будут готовы 50 мб вывода. Этот размер означает, что поиск приведет к снижению производительности на 10% по отношению к самой передаче данных.
Другим очень простым способом устранения попыток между входными и выходными файлами в целом является использование машины с двумя физическими дисками и полностью отдельными io-каналами для каждого. Вход от одного. Вывод на другой. Если вы делаете много больших преобразований файлов, хорошо иметь машину с этой функцией.
Ответ 5
Те, кажется, очень большие файлы... Почему они такие большие? Какую обработку вы делаете в каждой строке? Почему бы вам не использовать базу данных с некоторыми сокращениями на карте (если это необходимо) или простыми операциями данных? Точка базы данных - это абстрагирование обработки и управления большими объемами данных, которые не могут вписаться в память.
Вы можете начать играть с идеей sqlite3, которая использует только плоские файлы в качестве баз данных. Если вы сочтете эту идею полезной, перейдите к чему-то более надежному и универсальному, например postgresql.
Создать базу данных
conn = sqlite3.connect('pts.db')
c = conn.cursor()
Создает таблицу
c.execute('''CREATE TABLE ptsdata (filename, line, x, y, z''')
Затем используйте один из приведенных выше алгоритмов, чтобы вставить все строки и точки в базе данных, вызывая
c.execute("INSERT INTO ptsdata VALUES (filename, lineNumber, x, y, z)")
Теперь, как вы его используете, зависит от того, что вы хотите сделать. Например, чтобы работать со всеми точками в файле, выполнив запрос
c.execute("SELECT lineNumber, x, y, z FROM ptsdata WHERE filename=file.txt ORDER BY lineNumber ASC")
И получите n
строки за раз из этого запроса с помощью
c.fetchmany(size=n)
Я уверен, что где-то в sql-операторе есть лучшая оболочка, но вы получаете идею.
Ответ 6
Ваш код довольно не идиоматичен и делает гораздо больше вызовов функций, чем нужно. Простейшая версия:
ProcessLargeTextFile():
with open("filepath") as r, open("output") as w:
for line in r:
fields = line.split(' ')
fields[0:2] = [fields[0][:-3],
fields[1][:-3],
fields[2][:-3]]
w.write(' '.join(fields))
и я не знаю о современной файловой системе, которая медленнее Windows. Поскольку, по-видимому, вы используете эти огромные файлы данных в качестве баз данных, считаете ли вы использование реальной базы данных?
Наконец, если вы просто заинтересованы в уменьшении размера файла, рассмотрели ли вы сжатие/архивирование файлов?
Ответ 7
ProcessLargeTextFile():
r = open("filepath", "r")
w = open("filepath", "w")
l = r.readline()
while l:
Как уже было предложено, вы можете использовать цикл for, чтобы сделать это более оптимальным.
x = l.split(' ')[0]
y = l.split(' ')[1]
z = l.split(' ')[2]
Вы выполняете разделенную операцию 3 раза здесь, в зависимости от размера каждой строки это будет иметь влияние на производительность. Вы должны разделить один раз и назначить x, y, z для записей в возвращаемом массиве.
w.write(l.replace(x,x[:-3]).replace(y,y[:-3]).replace(z,z[:-3]))
Каждая строка, которую вы читаете, записывается непосредственно в файл, что очень интенсивно. Вы должны рассмотреть возможность буферизации вывода в память и периодического нажатия на диск. Что-то вроде этого:
BUFFER_SIZE_LINES = 1024 # Maximum number of lines to buffer in memory
def ProcessLargeTextFile():
r = open("filepath", "r")
w = open("filepath", "w")
buf = ""
bufLines = 0
for lineIn in r:
x, y, z = lineIn.split(' ')[:3]
lineOut = lineIn.replace(x,x[:-3]).replace(y,y[:-3]).replace(z,z[:-3])
bufLines+=1
if bufLines >= BUFFER_SIZE:
# Flush buffer to disk
w.write(buf)
buf = ""
bufLines=1
buf += lineOut + "\n"
# Flush remaining buffer to disk
w.write(buf)
buf.close()
r.close()
w.close()
Вы можете настроить BUFFER_SIZE, чтобы определить оптимальный баланс между использованием памяти и скоростью.
Ответ 8
Поскольку вы упоминаете только экономию места в качестве преимущества, есть ли причина, по которой вы не можете просто сохранить файлы gzipped? Это должно сэкономить 70% и выше по этим данным. Или подумайте о том, чтобы заставить NTFS сжимать файлы, если по-прежнему важен случайный доступ. Вы получите гораздо более значительную экономию времени ввода-вывода после любого из них.
Что еще более важно, где ваши данные, что вы получаете всего 3,4 ГБ/час? Это ниже скорости USBv1.
Ответ 9
Прочитайте файл с помощью for l in r:
, чтобы воспользоваться буферизацией.
Ответ 10
Вы можете попытаться сохранить результат разбивки сначала, когда это сделаете, и не делайте этого каждый раз, когда вам нужно поле. Возможно, это ускорится.
вы также можете попробовать не запускать его в gui. Запустите его в cmd.