Прочитать большой файл параллельно?

У меня есть большой файл, который мне нужно прочитать и сделать словарь. Я хотел бы, чтобы это было как можно быстрее. Однако мой код в 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 

(Связано с Read в большом файле и делает словарь .)

6 Solutions collect form web for “Прочитать большой файл параллельно?”

Несколько лет назад на сайте Тима Брея была серия блога «Проект широкомасштабного поиска» [1]. Вы можете найти там решение [2] Fredrik Lundh из ElementTree [3] и PIL [4]. Я знаю, что ссылки на публикации обычно не приветствуются на этом сайте, но я думаю, что эти ссылки дают вам лучший ответ, чем копирование кода.

[1] http://www.tbray.org/ongoing/When/200x/2007/10/30/WF-Результат&#x44B;
[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/

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

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

Что вы можете сделать, выполняется относительно дорогостоящая операция string / dictionary / list параллельно с чтением.

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

(Если вы идете для нескольких потребительских потоков, как говорит Папнесе, создайте один словарь на поток, а затем присоедините их).


подсказки:

  • … нажимать куски на синхронизированную очередь …
  • … один или несколько потребительских потоков …

Число рейнольдса Баунти:

C, очевидно, не имеет GIL, с которым можно бороться, поэтому несколько потребителей, скорее всего, будут лучше масштабироваться. Однако поведение чтения не меняется. Нижняя сторона заключается в том, что C не имеет встроенной поддержки хэш-карт (если вы все еще хотите словарь в стиле Python) и синхронизированных очередей, поэтому вам нужно либо найти подходящие компоненты, либо написать свои собственные. Основная стратегия нескольких потребителей, каждый из которых строит свой собственный словарь, а затем объединяет их в конце, по-прежнему, вероятно, лучший.

Использование strtok_r вместо str.split может быть быстрее, но помните, что вам нужно будет управлять памятью для всех ваших строк вручную. О, и вам нужна логика для управления фрагментами строк. Честно говоря, C дает вам так много вариантов. Думаю, вам просто нужно будет профилировать его и посмотреть.

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

Поскольку ОП упомянул, что списки на каждой строке ввода будут более практичными, чем два элемента, я сделал несколько более реалистичный входной файл, используя:

 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) возвращаемое значение из удаленного процесса, использование пула таким образом выполняется намного медленнее. Вы получаете некоторое улучшение, добавляя больше ядер в пул, но я бы сказал, что это принципиально неправильный способ распространения этой работы.

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

Одна вещь, которую вы можете попробовать, – это получить количество строк из файла, затем создать 8 потоков, которые делают словарь от 1/8 файла каждого, затем присоединяться к словарям, когда все потоки закончены. Это, вероятно, ускорит его, если это добавление, которое требует времени, а не чтения строк.

Более кардинальное решение для медленного добавления словаря: замените словарь на массив пар строк. Заполните его, а затем выполните сортировку.

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

Другим вариантом является создание 8 отдельных процессов. Потому что, имея единственный диктофон, это становится намного более возможным. Вы можете взаимодействовать между этими процессами через Pipe в модуле «многопроцессорности» или «сокетном» модуле.

С наилучшими пожеланиями

Барыш ЧУХАДАР.

  • Почему создается диапазон от 0 до log (len (list), 2) так медленно?
  • Как управлять процессом интенсивного процессора на сервере
  • как эффективно получить k больших элементов списка в python
  • Быстрее альтернатива Zipfile-модулю Python?
  • Найдите две пары пар, которые суммируются с одинаковым значением
  • Оптимизация скоростей поиска в словаре Python путем сокращения размера ключа?
  • Ускорение в петлеобразных структурах
  • как оптимально подсчитывать элементы в списке python
  • Эффективная память в Python
  • Эффективное вычисление согласованного по границе среднего значения окрестности
  • Цитирование через большие файлы занимает несколько часов в Python
  •  
    Interesting Posts for Van-Lav
    Python - лучший язык программирования в мире.