Как ускорить чтение нескольких файлов и помещение данных в фреймворк?
У меня есть несколько текстовых файлов, скажем, 50, что мне нужно прочитать в массивный фреймворк. На данный момент я использую следующие шаги.
- Прочитайте каждый файл и проверьте, что такое метки. Информация, которая мне нужна, часто содержится в первых нескольких строках. Те же ярлыки просто повторяются для остальной части файла, причем каждый раз каждый из них имеет разные типы данных.
- Создайте фрейм с этими метками.
- Снова прочитайте файл и заполните dataframe со значениями.
- Объединить этот фрейм данных с основным фреймворком данных.
Это очень хорошо подходит для файлов размером 100 КБ - несколько минут, но при 50 МБ это занимает всего несколько часов, и это не практично.
Как я могу оптимизировать свой код? В частности -
- Как определить, какие функции занимают больше всего времени, которые мне нужно оптимизировать? Это чтение файла? Это запись в dataframe? Где моя программа тратит время?
- Должен ли я рассматривать многопоточность или многопроцессорность?
- Могу ли я улучшить алгоритм?
- Возможно, прочитайте весь файл за один раз в списке, а не за строкой,
- Разбирать данные в кусках/весь файл, а не по строкам,
- Назначить данные в кадр данных в кусках/один раз, а не в строке за строкой.
- Есть ли что-нибудь еще, что я могу сделать, чтобы сделать мой код быстрее?
Вот пример кода. Мой собственный код немного сложнее, поскольку текстовые файлы более сложны, так что я должен использовать около 10 регулярных выражений и несколько циклов while для считывания данных и размещения их в нужном месте в правильном массиве. Чтобы поддерживать MWE просто, я не использовал повторные метки во входных файлах для MWE, поэтому мне хотелось бы, чтобы я дважды читал файл без причины. Надеюсь, это имеет смысл!
import re
import pandas as pd
df = pd.DataFrame()
paths = ["../gitignore/test1.txt", "../gitignore/test2.txt"]
reg_ex = re.compile('^(.+) (.+)\n')
# read all files to determine what indices are available
for path in paths:
file_obj = open(path, 'r')
print file_obj.readlines()
['a 1\n', 'b 2\n', 'end']
['c 3\n', 'd 4\n', 'end']
indices = []
for path in paths:
index = []
with open(path, 'r') as file_obj:
line = True
while line:
try:
line = file_obj.readline()
match = reg_ex.match(line)
index += match.group(1)
except AttributeError:
pass
indices.append(index)
# read files again and put data into a master dataframe
for path, index in zip(paths, indices):
subset_df = pd.DataFrame(index=index, columns=["Number"])
with open(path, 'r') as file_obj:
line = True
while line:
try:
line = file_obj.readline()
match = reg_ex.match(line)
subset_df.loc[[match.group(1)]] = match.group(2)
except AttributeError:
pass
df = pd.concat([df, subset_df]).sort_index()
print df
Number
a 1
b 2
c 3
d 4
Мои входные файлы:
test1.txt
a 1
b 2
end
test2.txt
c 3
d 4
end
Ответы
Ответ 1
Оказывается, сначала создавая пустой DataFrame, ища индекс, чтобы найти нужное место для строки данных, а затем обновляя только одну строку DataFrame - это процесс с очень тугим временем.
Более быстрый способ сделать это - прочитать содержимое входного файла в примитивную структуру данных, такую как список списков или список dicts, а затем преобразовать их в DataFrame.
Используйте списки, когда все данные, которые вы читаете, находятся в одних и тех же столбцах. В противном случае используйте dicts, чтобы явно указать, в какой столбец должен идти каждый бит данных.
Ответ 2
Прежде чем вытащить многопроцессорный молот, первым шагом должно быть некоторое профилирование. Используйте cProfile, чтобы быстро просмотреть, какие функции занимают много времени. К сожалению, если ваши строки находятся в одном вызове функции, они будут отображаться в виде вызовов библиотеки. line_profiler лучше, но требует немного больше времени настройки.
Примечание. Если вы используете ipython, вы можете использовать% timeit (магическая команда для модуля timeit) и% prun (магическая команда для модуля профиля) как для ваших заявлений, так и для функций. В поиске Google будут показаны некоторые руководства.
Pandas - замечательная библиотека, но я был случайной жертвой плохого использования с ужасающими результатами. В частности, будьте осторожны с операциями append()/concat(). Это может быть вашим узким местом, но вы должны профиль, чтобы быть уверенным. Обычно операции numpy.vstack() и numpy.hstack() выполняются быстрее, если вам не нужно выполнять выравнивание индекса/столбца. В вашем случае это похоже на то, что вы сможете обойтись с помощью серии или 1-D numpy ndarrays, которые могут сэкономить время.
BTW, блок try
в python намного медленнее, часто 10 раз или больше, чем проверка на недопустимое условие, поэтому убедитесь, что он вам абсолютно необходим, когда он вставляется в цикл для каждой отдельной строки. Вероятно, это еще один вредитель времени; Я предполагаю, что вы застряли блок try, чтобы проверить AttributeError в случае сбоя match.group(1). Сначала я должен проверить действительное соответствие.
Даже этих небольших модификаций должно быть достаточно, чтобы ваша программа работала значительно быстрее, прежде чем пытаться что-то радикальное, как многопроцессорность. Эти библиотеки Python являются удивительными, но приносят новый набор проблем для решения.
Ответ 3
Я использовал это много раз, так как это была простая процедура многопроцессорной реализации.
import pandas as pd
from multiprocessing import Pool
def reader(filename):
return pd.read_excel(filename)
def main():
pool = Pool(4) # number of cores you want to use
file_list = [file1.xlsx, file2.xlsx, file3.xlsx, ...]
df_list = pool.map(reader, file_list) #creates a list of the loaded df's
df = pd.concat(df_list) # concatenates all the df into a single df
if __name__ == '__main__':
main()
Используя это, вы сможете существенно увеличить скорость своей программы без особых усилий. Если вы не знаете, сколько процессоров у вас есть, вы можете проверить, потянув за свою оболочку и набрав
echo %NUMBER_OF_PROCESSORS%
EDIT: чтобы этот запуск выполнялся еще быстрее, рассмотрите возможность изменения ваших файлов на csvs и с помощью функции pandas pandas.read_csv
Ответ 4
Общие соображения python:
Прежде всего о измерении времени вы можете использовать такой фрагмент:
from time import time, sleep
class Timer(object):
def __init__(self):
self.last = time()
def __call__(self):
old = self.last
self.last = time()
return self.last - old
@property
def elapsed(self):
return time() - self.last
timer = Timer()
sleep(2)
print timer.elapsed
print timer()
sleep(1)
print timer()
Затем вы можете много раз тестировать код запуска и проверять diff.
Об этом, я прокомментирую inline:
with open(path, 'r') as file_obj:
line = True
while line: #iterate on realdines instead.
try:
line = file_obj.readline()
match = reg_ex.match(line)
index += match.group(1)
#if match:
# index.extend(match.group(1)) # or extend
except AttributeError:
pass
Вы предыдущий код wat не очень pythonic, вы можете попробовать/исключить.
Затем попробуйте выполнить только на минимально возможных строках.
Те же уведомления относятся ко второму блоку кода.
Если вам нужно прочитать одни и те же файлы несколько раз. вы можете хранить их в ОЗУ с помощью StringIO или проще хранить {path: content} dict, который вы читаете только один раз.
Известно, что регулярное выражение Python работает медленно, ваши данные кажутся довольно простыми, вы можете использовать методы разделения и полосы на ваших входных линиях.
striped=[l.split() for l in [c.strip() for c in file_desc.readlines()] if l]
Я рекомендую вам прочитать следующее: https://gist.github.com/JeffPaine/6213790 видео соответствующего видео здесь https://www.youtube.com/watch?v=OSGv2VnC0go
Ответ 5
Прежде всего, если вы читаете файл несколько раз, похоже, что это будет узким местом. Попробуйте прочитать файл в 1 строковый объект, а затем с помощью cStringIO
на нем несколько раз.
Во-вторых, вы не указали причин для построения индексов перед чтением во всех файлах. Даже если вы это сделаете, почему вы используете Pandas для ввода-вывода? Похоже, вы можете создать его в обычных структурах данных python (возможно, используя __slots__
), а затем поместить его в главный фрейм данных. Если вам не нужен индекс файла X до того, как вы прочитаете файл Y (как вам кажется 2-й цикл), вам просто нужно зацикливать файлы один раз.
В-третьих, вы можете использовать простые строки split
/strip
в цепочках, чтобы вытащить выделенные пробелы в пространстве, или, если это более сложно (есть строковые кавычки и т.д.), используйте модуль CSV
из стандартной библиотеки Python, Пока вы не покажете, как вы на самом деле создаете свои данные, сложно предложить исправление, связанное с этим.
То, что вы показали до сих пор, можно сделать довольно быстро с помощью простого
for path in paths:
data = []
with open(path, 'r') as file_obj:
for line in file_obj:
try:
d1, d2 = line.strip().split()
except ValueError:
pass
data.append(d1, int(d2)))
index, values = zip(*data)
subset_df = pd.DataFrame({"Number": pd.Series(values, index=index)})
Здесь разница в таймингах, когда я запускаю на виртуальной машине с дисковым пространством, не предварительно выделенным (сгенерированные файлы имеют размер примерно 24 МБ):
import pandas as pd
from random import randint
from itertools import combinations
from posix import fsync
outfile = "indexValueInput"
for suffix in ('1', '2'):
with open(outfile+"_" + suffix, 'w') as f:
for i, label in enumerate(combinations([chr(i) for i in range(ord('a'), ord('z')+1)], 8)) :
val = randint(1, 1000000)
print >>f, "%s %d" % (''.join(label), val)
if i > 3999999:
break
print >>f, "end"
fsync(f.fileno())
def readWithPandas():
data = []
with open(outfile + "_2", 'r') as file_obj:
for line in file_obj:
try:
d1, d2 = str.split(line.strip())
except ValueError:
pass
data.append((d1, int(d2)))
index, values = zip(*data)
subset_df = pd.DataFrame({"Numbers": pd.Series(values, index=index)})
def readWithoutPandas():
data = []
with open(outfile+"_1", 'r') as file_obj:
for line in file_obj:
try:
d1, d2 = str.split(line.strip())
except ValueError:
pass
data.append((d1, int(d2)))
index, values = zip(*data)
def time_func(func, *args):
import time
print "timing function", str(func.func_name)
tStart = time.clock()
func(*args)
tEnd = time.clock()
print "%f seconds " % (tEnd - tStart)
time_func(readWithoutPandas)
time_func(readWithPandas)
Результирующее время:
timing function readWithoutPandas
4.616853 seconds
timing function readWithPandas
4.931765 seconds
Вы можете попробовать эти функции с помощью наращивания индекса и посмотреть, какова будет разница во времени. Почти наверняка замедление происходит от нескольких чтений на диске. И поскольку Pandas не займет времени, чтобы создать ваш фреймворк из словаря, вам лучше понять, как создать свой индекс в чистом Python, прежде чем передавать данные на Pandas. Но сделайте как чтение данных, так и создание индекса в 1 диске.
Я полагаю, что еще одно предостережение в том, что если вы печатаете изнутри своего кода, ожидайте, что потребуется огромное количество времени. Время, затрачиваемое на то, чтобы писать обычный текст в два раза больше времени, необходимого для чтения/записи на диск.
Ответ 6
Вы можете импортировать модель многопроцессорности и использовать пул рабочих процессов для одновременного открытия нескольких файлов в виде файлов, что ускоряет загрузку части вашего кода. Чтобы проверить время, либо импортируйте функцию datetime, и используйте следующий код:
import datetime
start=datetime.datetime.now()
#part of your code goes here
execTime1=datetime.datetime.now()
print(execTime1-start)
#the next part of your code goes here
execTime2=datetime.datetime.now()
print(execTime2-execTime1)
Чтобы прочитать каждый файл только один раз, рассмотрите возможность использования другой многопроцессорной обработки script для создания списка строк в каждом файле, поэтому вы можете проверить соответствие без операции ввода-вывода файлов.
Ответ 7
Сначала используйте профилировщик для script (см. этот вопрос). Проанализируйте, какая именно часть потребляет больше времени. Посмотрите, можете ли вы его оптимизировать.
Во-вторых, я считаю, что чтение файла ввода-вывода, скорее всего, является узким местом. Его можно оптимизировать с использованием параллельного подхода. Я бы предложил одновременное чтение файлов и создание фрейма данных. Каждый поток может вытолкнуть вновь созданный кадр данных в очередь. Очередь мониторинга основного потока может захватывать кадры данных из очереди и объединять их с кадром основных данных.
Надеюсь, это поможет.
Ответ 8
1 создайте один выходной шаблон для файлов (например, кадр данных результата должен иметь столбец A, B C)
2 прочитайте каждый файл, преобразуйте его в выходной шаблон (который был установлен на шаге 1) и сохраните файл, например temp_idxx.csv, это можно сделать параллельно:)
3 объединить эти файлы temp_idxx.csv в один массивный файл и удалить temps
Преимущество этой процедуры заключается в том, что ее можно запускать параллельно, и она не будет потреблять всю память
cons создают выходной формат и придерживаются его, а использование дискового пространства
Ответ 9
Прочитайте файлы непосредственно в фреймворке pandas, используя pd.read_csv. Чтобы создать ваш subset_df. Используйте такие методы, как skipfooter, чтобы пропустить строки в конце файла, который, как вам известно, вам не нужен. Существует еще много доступных методов, которые могут заменить некоторые из функций цикла регулярного выражения, которые вы используете, например, error_bad_lines и skip_blank_lines.
Затем используйте инструменты, предоставленные pandas, чтобы очистить данные, которые не нужны.
Это позволит вам читать открытые и читать файл только один раз.
Ответ 10
Ваш код не делает то, что вы описываете.
Вопрос: 1. Прочитайте каждый файл и проверьте, что такое метки. Информация, которая мне нужна, часто содержится в первых нескольких строках.
Но вы читаете файл целый, а не только несколько строк.
Этот результат при чтении файлов дважды!
Вопрос: 2. Снова прочитайте файл и заполните данные с помощью значений.
Вы перезаписываете df['a'|'b'|'c'|'d']
в цикле снова и снова, что бесполезно
Я верю, что это не то, что вы хотите.
Это работает для данных, заданных в вопросе, но не для того, чтобы иметь дело с n значениями.
Предложение с другой логикой:
data = {}
for path in paths:
with open(path, 'r') as file_obj:
line = True
while line:
try:
line = file_obj.readline()
match = reg_ex.match(line)
if match.group(1) not in data:
data[ match.group(1) ] = []
data[match.group(1)].append( match.group(2) )
except AttributeError:
pass
print('data=%s' % data)
df = pd.DataFrame.from_dict(data, orient='index').sort_index()
df.rename(index=str, columns={0: "Number"}, inplace=True)
Выход:
data={'b': ['2'], 'a': ['1'], 'd': ['4'], 'c': ['3']}
<class 'pandas.core.frame.DataFrame'>
Index: 4 entries, a to d
Data columns (total 1 columns):
Number 4 non-null object
dtypes: object(1)
memory usage: 32.0+ bytes
Number
a 1
b 2
c 3
d 4
Таблица времени:
Code from Q: to_dict_from_dict
4 values 0:00:00.033071 0:00:00.022146
1000 values 0:00:08.267750 0:00:05.536500
10000 values 0:01:22.677500 0:00:55.365000
Протестировано с помощью Python: 3.4.2 - pandas: 0.19.2 - re: 2.2.1