Прочитать большой файл параллельно?
У меня есть большой файл, который мне нужно прочитать и сделать словарь. Я хотел бы, чтобы это было как можно быстрее. Однако мой код в python слишком медленный. Вот минимальный пример, который показывает проблему.
Сначала сделайте некоторые поддельные данные
paste <(seq 20000000) <(seq 2 20000001) > largefile.txt
Теперь вот минимальная часть кода на Python, чтобы прочитать ее и сделать словарь.
import sys
from collections import defaultdict
fin = open(sys.argv[1])
dict = defaultdict(list)
for line in fin:
parts = line.split()
dict[parts[0]].append(parts[1])
Тайминги:
time ./read.py largefile.txt
real 0m55.746s
Однако можно прочитать весь файл гораздо быстрее, чем:
time cut -f1 largefile.txt > /dev/null
real 0m1.702s
Мой процессор имеет 8 ядер, можно ли распараллелить эту программу в python, чтобы ускорить его?
Одной из возможностей может быть чтение в больших фрагментах ввода, а затем одновременное выполнение 8 процессов на разных неперекрывающихся подчленях, создающих словари параллельно из данных в памяти, а затем чтение в другом большом фрагменте. Возможно ли это в python с использованием многопроцессорности?
Обновление. Поддельные данные были не очень хорошими, поскольку для каждого ключа было только одно значение. Лучше
perl -E 'say int rand 1e7, $", int rand 1e4 for 1 .. 1e7' > largefile.txt
(Связано с Читайте в большом файле и создайте словарь.)
Ответы
Ответ 1
Несколько лет назад на сайте Тима Брея была серия блога "Проект широкомасштабного поиска" [1]. Вы можете найти там решение [2] Fredrik Lundh из ElementTree [3] и PIL [4]. Я знаю, что ссылки на публикации обычно не приветствуются на этом сайте, но я думаю, что эти ссылки дают вам лучший ответ, чем копировать его код.
[1] http://www.tbray.org/ongoing/When/200x/2007/10/30/WF-Results
[2] http://effbot.org/zone/wide-finder.htm
[3] http://docs.python.org/3/library/xml.etree.elementtree.html
[4] http://www.pythonware.com/products/pil/
Ответ 2
Возможно, это можно будет распараллелить, чтобы ускорить его, но параллельное выполнение нескольких чтений вряд ли поможет.
Ваша система вряд ли будет полезно выполнять множественные чтения параллельно (исключение связано с чем-то вроде полосатого массива рейдов, и в этом случае вам все равно нужно знать шаг, чтобы оптимально его использовать).
Что вы можете сделать, выполняется относительно дорогостоящая операция string/dictionary/list параллельно с чтением.
Итак, один поток читает и выталкивает (большие) фрагменты в синхронизированную очередь, один или несколько потребительских потоков вытягивают куски из очереди, разбивают их на строки и заполняют словарь.
(Если вы переходите на несколько потребительских потоков, как говорит Pappnese, создайте по одному словарю на поток, а затем присоедините их).
Советов:
Re. Баунти:
C, очевидно, не имеет GIL, с которым можно бороться, поэтому несколько потребителей, скорее всего, будут лучше масштабироваться. Однако поведение чтения не меняется. Нижняя сторона заключается в том, что C не имеет встроенной поддержки хэш-карт (если вы все еще хотите словарь в стиле Python) и синхронизированных очередей, поэтому вам нужно либо найти подходящие компоненты, либо написать свои собственные.
Основная стратегия нескольких потребителей, каждый из которых строит свой собственный словарь, а затем объединяет их в конце, по-прежнему, вероятно, лучший.
Использование strtok_r
вместо str.split
может быть быстрее, но помните, что вам также нужно будет управлять памятью для всех ваших строк вручную. О, и вам нужна логика для управления фрагментами строк. Честно говоря, C дает вам так много вариантов. Думаю, вам просто нужно просмотреть его и посмотреть.
Ответ 3
Кажется соблазнительным думать, что использование пула обработки будет решать такие проблемы, но в конечном итоге это будет немного сложнее, чем это, по крайней мере, в чистом Python.
Поскольку OP упомянул, что списки на каждой строке ввода были бы более практичными, чем два элемента, я сделал несколько более реалистичный входной файл, используя:
paste <(seq 20000000) <(seq 2 20000001) <(seq 3 20000002) |
head -1000000 > largefile.txt
После профилирования исходного кода я обнаружил, что самая медленная часть процесса - это процедура разделения строк. (.split()
занимает примерно 2x больше, чем .append()
на моей машине.)
1000000 0.333 0.000 0.333 0.000 {method 'split' of 'str' objects}
1000000 0.154 0.000 0.154 0.000 {method 'append' of 'list' objects}
Итак, я рассмотрел разделение на другую функцию и использовал пул для распределения работы по разбиению полей:
import sys
import collections
import multiprocessing as mp
d = collections.defaultdict(list)
def split(l):
return l.split()
pool = mp.Pool(processes=4)
for keys in pool.map(split, open(sys.argv[1])):
d[keys[0]].append(keys[1:])
К сожалению, добавление пула замедлило работу более чем на 2 раза. Первоначальная версия выглядела так:
$ time python process.py smallfile.txt
real 0m7.170s
user 0m6.884s
sys 0m0.260s
по сравнению с параллельной версией:
$ time python process-mp.py smallfile.txt
real 0m16.655s
user 0m24.688s
sys 0m1.380s
Поскольку вызов .map()
в основном должен сериализовать (расчешите) каждый вход, отправить его в удаленный процесс и затем десериализовать (unpickle) возвращаемое значение из удаленного процесса, использование пула таким образом происходит намного медленнее. Вы получаете некоторое улучшение, добавляя больше ядер в пул, но я бы сказал, что это принципиально неправильный способ распространения этой работы.
Чтобы действительно ускорить это по всем ядрам, я предполагаю, что вам нужно будет читать большие фрагменты ввода, используя какой-то фиксированный размер блока. Затем вы можете отправить весь блок в рабочий процесс и получить список сериализованных списков (хотя пока неизвестно, сколько будет стоить вам десериализация). Чтение ввода в блоках фиксированного размера звучит, как будто это может быть сложно с ожидаемым входом, однако, поскольку я предполагаю, что каждая строка не обязательно имеет одинаковую длину.
Ответ 4
Одна вещь, которую вы могли бы попробовать, - получить счетчик строк из файла, затем создать 8 потоков, которые делают словарь от 1/8 файла каждого, затем присоединяться к словарям, когда все потоки закончены. Это, вероятно, ускорит его, если это добавление, которое требует времени, а не чтения строк.
Ответ 5
Больше кардинального решения для медленного добавления слова: замените словарь на массив пар строк. Заполните его, а затем выполните сортировку.
Ответ 6
Если ваши данные в файле не так часто меняются, вы можете его сериализовать.
Интерпретатор Python будет десериализовать его гораздо быстрее.
Вы можете использовать модуль cPickle.
Или создание 8 отдельных процессов - это другой вариант.
Потому что, имея единственный диктофон, это становится намного более возможным.
Вы можете взаимодействовать между этими процессами через Pipe в модуле "многопроцессорности" или "сокетном" модуле.
С наилучшими пожеланиями
Барыш ЧУХАДАР.