Использование памяти Python? загрузка больших словарей в память - PullRequest
30 голосов
/ 06 февраля 2010

Привет всем, у меня есть файл на диске, который только 168 МБ. Это просто список слов через запятую, идентификатор слово может быть длиной 1-5 слов. Там 6,5 миллиона строк. Я создал словарь в Python, чтобы загрузить его в память, чтобы я мог искать входящий текст по этому списку слов. Когда python загружает его в память, он показывает 1,3 ГБ памяти. Есть идеи, почему это так?

так скажем, мой файл слов выглядит так ...

1,word1
2,word2
3,word3

затем добавьте 6,5 миллиона к этому Затем я перебираю этот файл и создаю словарь (python 2.6.1)

  def load_term_cache():
      """will load the term cache from our cached file instead of hitting mysql. If it didn't 
      preload into memory it would be 20+ million queries per process"""
      global cached_terms
      dumpfile = os.path.join(os.getenv("MY_PATH"), 'datafiles', 'baseterms.txt')
      f = open(dumpfile)
      cache = csv.reader(f)
      for term_id, term in cache:
          cached_terms[term] = term_id
      f.close()

Просто делать это взрывает память. Я просматриваю монитор активности, и он привязывает память ко всем доступным примерно до 1,5 ГБ оперативной памяти. На моем ноутбуке он только начинает меняться. Любые идеи, как наиболее эффективно хранить пары ключ / значение в памяти с Python?

спасибо

ОБНОВЛЕНИЕ: Я пытался использовать модуль anydb, и после 4,4 миллиона записей он просто умирает число с плавающей запятой - это прошедшие секунды, с тех пор как я попытался загрузить его

56.95
3400018
60.12
3600019
63.27
3800020
66.43
4000021
69.59
4200022
72.75
4400023
83.42
4600024
168.61
4800025
338.57

Вы можете видеть, что он работал отлично. 200 000 строк вставляются каждые несколько секунд, пока я не столкнусь со стеной, и время не удвоится.

  import anydbm
  i=0
  mark=0
  starttime = time.time()
  dbfile = os.path.join(os.getenv("MY_PATH"), 'datafiles', 'baseterms')
  db = anydbm.open(dbfile, 'c')
  #load from existing baseterm file
  termfile = os.path.join(os.getenv("MY_PATH"), 'datafiles', 'baseterms.txt.LARGE')
  for line in open(termfile):
    i += 1
    pieces = line.split(',')
    db[str(pieces[1])] = str(pieces[0])
    if i > mark:
      print i
      print round(time.time() - starttime, 2)
      mark = i + 200000
  db.close()

Ответы [ 4 ]

35 голосов
/ 06 февраля 2010

много идей. Однако, если вам нужна практическая помощь, отредактируйте свой вопрос, чтобы показать ВСЕ ваш код. Также скажите нам, что это «оно», которое показывает используемую память, что оно показывает, когда вы загружаете файл с нулевым количеством записей, и на какой платформе вы находитесь, и какую версию Python.

Вы говорите, что "слово может быть длиной 1-5 слов". Какова средняя длина ключевого поля в байтах? Все ли идентификаторы целые? Если да, каковы минимальное и максимальное целое число? Если нет, какова средняя длина, если идентификатор в байтах? Чтобы включить перекрестную проверку всего вышеперечисленного, сколько байтов содержится в файле строки размером 6,5 мегабайта?

Глядя на ваш код, файл из одной строки word1,1 создаст дикт d['1'] = 'word1' ... разве это не баснословно?

Обновление 3: дополнительные вопросы: как закодировано слово? Вы уверены, что ни на одном из двух полей не загружены пробелы в конце?

Обновление 4 ... Вы спросили ", как наиболее эффективно хранить пары ключ / значение в памяти с python " и , но никто не ответил на этот вопрос с какой-либо точностью .

У вас есть файл 168 Мб с 6,5 миллионами строк. Это 168 * 1,024 ** 2 / 6,5 = 27,1 байта на строку. Отбросьте 1 байт для запятой и 1 байт для новой строки (при условии, что это * x платформа), и у нас осталось 25 байт на строку. Предполагая, что «id» предназначен для того, чтобы быть уникальным, и, поскольку он выглядит как целое число, давайте предположим, что «id» имеет длину 7 байтов; это оставляет нас со средним размером 18 байт для слова. Это соответствует вашим ожиданиям?

Итак, мы хотим сохранить 18-байтовый ключ и 7-байтовое значение в таблице поиска в памяти.

Предположим, 32-битная платформа CPython 2.6.

>>> K = sys.getsizeof('123456789012345678')
>>> V = sys.getsizeof('1234567')
>>> K, V
(42, 31)

Обратите внимание, что sys.getsizeof(str_object) => 24 + len(str_object)

Кортежи были упомянуты одним ответчиком. Обратите внимание на следующее:

>>> sys.getsizeof(())
28
>>> sys.getsizeof((1,))
32
>>> sys.getsizeof((1,2))
36
>>> sys.getsizeof((1,2,3))
40
>>> sys.getsizeof(("foo", "bar"))
36
>>> sys.getsizeof(("fooooooooooooooooooooooo", "bar"))
36
>>>

Вывод: sys.getsizeof(tuple_object) => 28 + 4 * len(tuple_object) ... он позволяет только указатель на каждый элемент, он не учитывает размеры элементов.

Аналогичный анализ списков показывает, что sys.getsizeof(list_object) => 36 + 4 * len(list_object) ... опять же необходимо добавить размеры предметов. Существует еще одно соображение: CPython перераспределяет списки, чтобы он не вызывал систему realloc () при каждом вызове list.append (). Для достаточно большого размера (например, 6,5 миллиона!) Общее распределение составляет 12,5 процента - см. Источник (Objects / listobject.c). Это перераспределение не выполняется с кортежами (их размер не изменяется).

Вот затраты на различные альтернативы для таблицы поиска на основе памяти:

Список кортежей:

Каждый кортеж будет принимать 36 байтов для самого кортежа, плюс K и V для содержимого. Так что N из них займет N * (36 + K + V); тогда вам нужен список для их хранения, поэтому нам нужно 36 + 1,125 * 4 * N для этого.

Всего для списка кортежей: 36 + N * (40,5 + K + v)

Это 26 + 113,5 * N ( около 709 МБ , когда 6,5 миллиона)

Два параллельных списка:

(36 + 1,125 * 4 * N + K * N) + (36 + 1,125 * 4 * N + V * N) то есть 72 + N * (9 + K + V)

Обратите внимание, что разница между 40,5 * N и 9 * N составляет около 200 МБ, когда N составляет 6,5 млн.

Значение хранится как int, а не str:

Но это еще не все. Если идентификаторы на самом деле являются целыми числами, мы можем сохранить их как таковые.

>>> sys.getsizeof(1234567)
12

Это 12 байтов вместо 31 байта для каждого объекта значения. Эта разница в 19 * N означает дальнейшую экономию около 118 МБ, когда N составляет 6,5 млн.

.

Использовать array.array ('l') вместо списка для (целого) значения:

Мы можем хранить эти 7-значные целые числа в array.array ('l'). Нет int-объектов и указателей на них - только 4-байтовое целочисленное значение со знаком. Бонус: массивы перераспределены только на 6,25% (для больших N). Таким образом, это 1,0625 * 4 * N вместо предыдущего (1,125 * 4 + 12) * N, дальнейшая экономия составляет 12,25 * N, то есть 76 МБ.

Итак, мы сократились до 709 - 200 - 118 - 76 = около 315 МБ .

N.B. За исключением ошибок и пропусков - это 0127 в моем TZ :-(

20 голосов
/ 06 февраля 2010

Посмотрите (Python 2.6, 32-разрядная версия) ...:

>>> sys.getsizeof('word,1')
30
>>> sys.getsizeof(('word', '1'))
36
>>> sys.getsizeof(dict(word='1'))
140

Строка (занимающая 6 байт на диске, очевидно) получает служебную информацию в 24 байта (независимо от того, сколько она длинна, добавьте 24 к ее длине, чтобы узнать, сколько памяти она занимает). Когда вы разбиваете его на кортеж, это немного больше. Но dict - это то, что действительно взрывает: даже пустой диктат занимает 140 байтов - чистые накладные расходы на поддержание невероятно быстрого поиска на основе хеша. Чтобы быть быстрым, хеш-таблица должна иметь низкую плотность - и Python гарантирует, что dict всегда имеет низкую плотность (занимая для этого много дополнительной памяти).

Самый эффективный для памяти способ хранения пар ключ / значение - это список кортежей, но поиск, конечно, будет очень медленным (даже если вы сортируете список и используете bisect для ищите, это все равно будет очень медленным, чем диктат).

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

8 голосов
/ 06 февраля 2010

конвертирует ваши данные в dbm (импортирует anydbm или использует berkerley db, импортируя bsddb ...), а затем использует dbm API для доступа к ним.

причина взрыва в том, что у python есть дополнительная метаинформация для любых объектов, и дикт должен создать хеш-таблицу (что потребовало бы больше памяти). Вы только что создали так много объектов (6,5M), что метаданные становятся слишком большими.

import bsddb
a = bsddb.btopen('a.bdb') # you can also try bsddb.hashopen
for x in xrange(10500) :
  a['word%d' %x] = '%d' %x
a.close()

Этот код запускается всего за 1 секунду, поэтому я думаю, что скорость в порядке (так как вы сказали 10500 строк в секунду). btopen создает файл БД длиной 499 712 байт, а hashopen создает 319 488 байт.

При вводе xrange 6,5M и использовании btopen я получил 417 080 КБ в размере выходного файла и около 1 или 2 минуты для завершения вставки. Поэтому я думаю, что он полностью подходит для вас.

0 голосов
/ 29 сентября 2016

У меня такая же проблема, хотя я позже. Остальные хорошо ответили на этот вопрос. И я предлагаю простой в использовании (может быть, не так просто :-)) и довольно эффективную альтернативу, это pandas.DataFrame. Хорошо сохраняет память при сохранении больших данных.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...