Минимизация чтения и записи на диск в Python для операции, требующей большого объема памяти - PullRequest
17 голосов
/ 12 сентября 2011

Фон

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

Требования

Ключевым аспектом этой конкретной программы, которую я должен написать, является то, что она должна:

  1. Прочитать большой корпус (между 5G и 30G, и потенциально более крупный материал по линии)
  2. Обработка данных в каждой строке.
  3. Из этих обработанных данных построить большое количество векторов (размерность некоторых из этих векторов составляет> 4 000 000). Обычно он строит сотни тысяч таких векторов.
  4. Все эти векторы должны быть сохранены на диск в том или ином формате.

Шаги 1 и 2 нетрудно выполнить эффективно: просто используйте генераторы и используйте конвейер анализа данных. Большая проблема - операция 3 (и при соединении 4)

Скобки: технические данные

В случае, если фактическая процедура построения векторов влияет на решение:

Для каждой строки в корпусе один или несколько векторов должны обновлять свои базовые веса.

Если вы рассматриваете их в терминах списков Python, каждая строка при обработке обновляет один или несколько списков (создавая их при необходимости), увеличивая значения этих списков на один или несколько индексов на значение (которое может отличаться) на основе индекса).

Векторы не зависят друг от друга и не имеет значения, в каком порядке считываются линии корпуса.

Попытки решения

Есть три крайности, когда это нужно сделать:

  1. Я мог бы построить все векторы в памяти. Затем запишите их на диск.
  2. Я мог бы построить все векторы прямо на диске, используя полку рассола или какую-нибудь такую ​​библиотеку.
  3. Я мог бы строить векторы в памяти по одному и записывать их на диск, проходя через корпус один раз за вектор.

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

* 1052 1053 ** * Цель * 1054 1055 * **

Хорошее решение будет включать:

  1. Построение в памяти как можно больше.
  2. Когда память заполнится, выгрузите все на диск.
  3. Если биты снова нужны с диска, восстановите их в памяти, чтобы добавить данные в эти векторы.
  4. Вернитесь к 1, пока все векторы не будут построены.

Проблема в том, что я не совсем уверен, как это сделать. Кажется, что немного беспомощно беспокоиться о системных атрибутах, таких как ОЗУ, но я не понимаю, как подобного рода проблемы могут быть оптимально решены без учета этого. В результате я не знаю, как начать работать с такими вещами.

Вопрос

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

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

Дополнительные детали

Тип машины, на которой работает эта проблема, обычно имеет 20+ ядер и ~ 70 ГБ ОЗУ. Проблема может быть распараллелена (как MapReduce) в том, что отдельные векторы для одного объекта могут быть построены из сегментов корпуса, а затем добавлены, чтобы получить вектор, который был бы построен из всего корпуса.

Часть вопроса включает определение предела того, сколько памяти может быть встроено в память до того, как должна произойти запись на диск. Предлагает ли python какой-либо механизм для определения объема доступной оперативной памяти?

Ответы [ 11 ]

5 голосов
/ 12 сентября 2011

взгляните на pytables .Одним из преимуществ является то, что вы можете работать с очень большими объемами данных, хранящихся на диске, как если бы они были в памяти.

edit: потому что производительность ввода-вывода будет узким местом (если не узким местом)Вы захотите рассмотреть технологию SSD: высокая скорость ввода-вывода в секунду и практически отсутствие времени поиска.Размер вашего проекта идеально подходит для современных доступных SSD-накопителей.

3 голосов
/ 12 сентября 2011

На ум приходят пара библиотек, которые вы, возможно, захотите оценить:

  • joblib - упрощает параллельные вычисления и обеспечивает прозрачное кэширование на выходе иотложенная переоценка.

  • mrjob - упрощает написание потоковых заданий Hadoop в Amazon Elastic MapReduce или в вашем собственном кластере Hadoop.

2 голосов
/ 12 сентября 2011

Подумайте об использовании существующего решения БД в памяти, такого как Redis .Проблема переключения на диск после того, как ОЗУ исчезла, и уловки, чтобы настроить этот процесс уже должны быть на месте.Клиент Python.

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

2 голосов
/ 12 сентября 2011

Вы не упомянули ни один из способов, но если нет, вам следует использовать массивы NumPy для своих списков, а не собственные списки Python, которые должны помочь ускорить процесс и уменьшить использование памяти, так кака также делать все, что вы делаете, быстрее и проще.

Если вы вообще знакомы с C / C ++, вы также можете изучить Cython , который позволяет вам написать некоторые иливесь ваш код на C, который намного быстрее, чем Python, и хорошо интегрируется с массивами NumPy.Возможно, вы захотите профиль вашего кода, чтобы узнать, какие места занимают больше всего времени, и написать эти разделы на C.

Трудно сказать, какой будет лучший подход, но оКонечно, любые ускорения в критических частях помогут.Также имейте в виду, что после исчерпания ОЗУ ваша программа начнет работать в виртуальной памяти на диске, что, вероятно, вызовет гораздо большую активность дискового ввода-вывода, чем сама программа, поэтому, если вас беспокоит дисковый ввод-вывод, вашЛучше всего убедиться, что пакет данных, с которым вы работаете в памяти, не будет намного больше, чем доступная оперативная память.

2 голосов
/ 12 сентября 2011

Две идеи:

  1. Используйте числовые массивы для представления векторов. Они намного более эффективны при использовании памяти, за счет чего они заставляют элементы вектора иметь одинаковый тип (все целые или двойные числа ...).

  2. Делать несколько проходов, каждый с разным набором векторов. То есть, выбирайте первые 1М векторы и выполняйте только те вычисления, в которых они задействованы (вы сказали, что они независимы, поэтому я предполагаю, что это жизнеспособно). Затем еще раз передать все данные со вторыми векторами 1M.

Кажется, вы находитесь на грани того, что вы можете сделать со своим оборудованием. Было бы полезно, если бы вы могли описать, какое оборудование (в основном, ОЗУ) доступно для этой задачи. Если имеется 100 тыс. Векторов, каждый из которых имеет 1 МБ, это дает ~ 370 ГБ. Если метод нескольких проходов является жизнеспособным, и у вас есть машина с 16 ГБ ОЗУ, то это около ~ 25 проходов - распараллелить будет легко, если у вас есть кластер.

1 голос
/ 12 сентября 2011

Я бы посоветовал сделать это следующим образом:

1) Построить упомянутый вами простой конвейер

2) Построить ваши векторы в памяти и «сбросить» их в БД.( Redis и MongoDB являются хорошими кандидатами)

3) Определите, сколько памяти занимает эта процедура, и распараллелите ее соответствующим образом (или даже лучше используйте подход карты / сокращения, илираспределенная очередь задач, такая как сельдерей )

Плюс все советы, упомянутые ранее (numPy и т. д.)

1 голос
/ 12 сентября 2011

Использовать базу данных.Эта проблема кажется достаточно большой, поэтому выбор языка (Python, Perl, Java и т. Д.) Не будет иметь значения.Если каждое измерение вектора является столбцом в таблице, добавление некоторых индексов, вероятно, является хорошей идеей.В любом случае это много данных, и они не будут обрабатываться очень быстро.

1 голос
/ 12 сентября 2011

Трудно сказать точно, потому что отсутствуют некоторые детали, например. это выделенная коробка? Процесс работает на нескольких машинах? Изменилась ли оперативная память?

В общем, я не рекомендую переопределять работу операционной системы.

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

Вы также захотите кэшировать дорогостоящие вычисления, которые вскоре понадобятся. Короче говоря, когда вычисляется дорогостоящая операция, которую можно использовать снова, вы сохраняете ее в словаре (или, возможно, на диске, в memcached и т. Д.), А затем сначала смотрите туда, а затем снова вычисляете. Документация по Django имеет хорошее введение .

0 голосов
/ 15 сентября 2011

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

Это часть карты.

Используйте одно задание, чтобы объединить более 20 наборов векторов из каждого из предыдущих заданий - это шаг сокращения.

Вы проигралиинформация из 2 * N строк, где N - число параллельных процессов, но вы получаете, не добавляя сложную логику, чтобы попытаться захватить эти строки для обработки.

0 голосов
/ 12 сентября 2011

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

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

Оказывается, что модуль Python psutil делает только трюк.

Например, скажемЯ хочу иметь цикл while, который добавляет материал в очередь, чтобы другие процессы могли работать до тех пор, пока моя память не будет заполнена на 80%.Следующий псевдокод сделает свое дело:

while (someCondition):
   if psutil.phymem_usage().percent > 80.0:
      dumpQueue(myQueue,somefile)
   else:
      addSomeStufftoQueue(myQueue,stuff)

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

PS.Переходит к Шону для предложения этого модуля.

...