Подтвердить что ты не робот

Минимизация чтения и записи на диск в Python для работы с большой памятью

Фон

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

Требования

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

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

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

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

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

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

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

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

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

Есть три экстремума, когда дело доходит до того, как это сделать:

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

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

Цели

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

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

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

Вопрос

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

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

Дополнительные сведения

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

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

Ответ 3

Две идеи:

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

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

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

Ответ 4

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

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

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

Ответ 5

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

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

Ответ 6

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

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

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

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

Ответ 7

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

Ответ 8

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

1) Постройте простой конвейер, о котором вы говорили

2) Постройте свои векторы в памяти и "сфотографируйте" их в БД. (Redis и MongoDB являются хорошими кандидатами)

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

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

Ответ 9

Из другого комментария я делаю вывод, что ваш корпус вписывается в память, и у вас есть несколько ядер, чтобы бросить на проблему, поэтому я бы попробовал это:

  • Найдите способ хранения вашего корпуса в памяти. Это может быть своего рода диск с файловой системой или база данных. Не знаю, какой из них лучше для вас.
  • У вас есть небольшая оболочка script, а также запускается каждый второй процесс следующего, если осталось x памяти (или, если вы хотите сделать вещи немного сложнее, y I/O с дискеты):

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

Ответ 10

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

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

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

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

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

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

PS. Подходит для Sean для указания этого модуля.

Ответ 11

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

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

Используйте одно задание для объединения 20+ наборов векторов из каждого из ранних заданий. Это шаг уменьшения.

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