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

Использование памяти Python? загрузка больших словарей в память

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

так что пусть мой файл слов выглядит так...

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?

спасибо

UPDATE: я пытался использовать модуль 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()
4b9b3361

Ответ 1

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

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

Глядя на ваш код, 1-строчный файл word1,1 создаст dict 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). Это обобщение не выполняется с помощью кортежей (их размер не изменяется).

Ниже приведены затраты на различные альтернативы dict для справочной таблицы на основе памяти:

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

Каждый кортеж будет принимать 36 байт для самого 2-кортежа, плюс 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 not str:

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

>>> sys.getsizeof(1234567)
12

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

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

Мы можем сохранить эти 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:-(

Ответ 2

Взгляните (Python 2.6, 32-разрядная версия)...:

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

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

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

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

Ответ 3

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

причина взрыва заключается в том, что у python есть дополнительная метаинформация для любых объектов, а dict необходимо построить хеш-таблицу (которая потребует большего объема памяти). вы создали столько объектов (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 создает файл db длиной 499 712 байт, а hashopen создает 319 488 байт.

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

Ответ 4

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