Компиляция в байтовый код занимает слишком много памяти - PullRequest
9 голосов
/ 30 июля 2011

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

d = {(1,2,3):(1,2,3,4), (2,5,6)=(4,2,3,4,5,6), ... }

Таким образом, каждый ключ представляет собой 3-кортеж, а каждое значение представляет собой относительно небольшой кортеж произвольного размера (вероятно, никогда не превышающий 30 элементов).Что делает словарь большим, так это количество ключей.Небольшой пример того, с чем я работаю, имеет примерно 247257 ключей.Я генерирую этот словарь с помощью симуляции, чтобы я мог написать текстовый файл, который определяет этот словарь, и для примера, который я только что упомянул, это файл размером 94 МБ.Узкое место, с которым я сталкиваюсь, заключается в том, что начальная компиляция в байт-код python пожирает около 14 ГБ оперативной памяти.Поэтому, когда я впервые импортирую словарь, я вижу увеличение использования ОЗУ, и через 10 секунд все загружается.Если файл .pyc уже создан, импорт выполняется практически мгновенно.Используя pympler, я определил, что этот словарь занимает всего около 200 МБ в памяти.В чем здесь дело?Есть ли у меня какие-либо другие варианты того, как загрузить этот словарь в python или хотя бы скомпилировать в байт-кодЯ запускаю генерацию симуляции в C ++ и не могу писать файлы в любом нужном мне формате.Есть ли здесь какие-либо варианты (библиотеки Python и т. Д.)?Я взаимодействую с некоторым программным обеспечением, которому нужны эти данные в качестве словаря, поэтому, пожалуйста, никаких других предложений в этой области.Также на случай, если вам интересно, я определил словарь в текстовом файле, как приведенное выше определение, а также вот так:

d = {}
d[1,2,3] = (1,2,3,4)
d[2,5,6] = (4,2,3,4,5,6)
...

Оба дают одинаковый скачок памяти при компиляции в байт-код.На самом деле второй кажется немного хуже, что меня удивляет.Должен быть какой-то способ укротить количество оперативной памяти, необходимое для начальной компиляции.Кажется, что он должен каким-то образом выполнять компиляцию по одной паре ключ-значение за раз.Есть идеи?

Другая информация: использование python 2.6.5

Ответы [ 7 ]

3 голосов
/ 30 июля 2011

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

Вы пытались сохранить данные в отдельном файле в следующем формате и затем динамически загрузить его в python?

1,2,3=1,2,3
2,5,6=4,2,3,4,5,6

Скрипт Python должен выглядеть примерно так:

file = open("filename")
d = {}

for line in file:
    key, val = line.split("=")
    key = tuple(key.split(","))
    d[key] = tuple(val.split(","))

file.close()
2 голосов
/ 30 июля 2011
1 голос
/ 30 июля 2011

Я предполагаю, что ваш большой всплеск компиляции происходит, когда вы выполняете "import module_conisting_humungous_dict_statement".Тогда не имеет значения, если у вас есть только один оператор или 247257 отдельных операторов присваивания, весь модуль все равно будет скомпилирован сразу.Вы можете попробовать использовать форму отдельного оператора присваивания, а затем открыть файл, читать по одной строке за раз и выполнять его.Тогда вы будете компилировать только одну строку за раз.Это, вероятно, займет некоторое время.

0 голосов
/ 08 мая 2012
0 голосов
/ 02 августа 2011

Вот класс, который использует defaultdict для автоматического вложения индексированных значений с некоторыми специальными __getitem__ и __setitem__ методами для приема кортежей в качестве аргументов:

from collections import defaultdict

defdict3level = (lambda : defaultdict(lambda : 
                            defaultdict( lambda : 
                                defaultdict(tuple))))

class dict3level(object):
    def __init__(self):
        self.defdict = defdict3level()

    def __getitem__(self, key):
        if isinstance(key, tuple):
            if len(key)==3:
                return self.defdict[key[0]][key[1]][key[2]]
            elif len(key)==2:
                return self.defdict[key[0]][key[1]]
            elif len(key)==1:
                return self.defdict[key[0]]
        else:
            return self.defdict[key]

    def __setitem__(self, key, value):
        if isinstance(key, tuple) and len(key)==3:
            self.defdict[key[0]][key[1]][key[2]] = value
        else:
            self.defdict[key] = value

    def __getattr__(self, attr):
        return getattr(self.defdict, attr)

Теперь выполните все ваши задания, как раньше:

d = dict3level()
d[1,2,3] = (1,2,3,4)
d[1,2,7] = (3,4,5,6)
d[2,5,6] = (4,2,3,4,5,6)

Вы все еще можете получить определенную запись для определенного кортежа:

# get a specific entry
print d[1,2,3]

Но вы также можете перемещаться по уровням:

# get all different 0'th index values
print d.keys()

# get all sub values in d[1,2,*]
print d[1,2].keys()
for key in d[1,2]:
    print "d[1,2,%d] = %s" % (key, d[1,2][key])

# no such entry, return empty tuple
print d[1,2,0]

Дает:

print d[1,2,3] -> (1, 2, 3, 4)
print d.keys() -> [1, 2]
print d[1,2].keys() -> [3, 7]
for key in d[1,2]:... -> 
    d[1,2,3] = (1, 2, 3, 4)
    d[1,2,7] = (3, 4, 5, 6)
print d[1,2,0] -> ()

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

0 голосов
/ 30 июля 2011

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

def giantdict():
  d0 = {(1, 2): (3, 4), (3, 4): (5, 6), ...}  # first 1000 key/value pairs here
  d1 = {(1, 2): (3, 4), (3, 4): (5, 6), ...}  # next 1000 key/value pairs
  d2 = {(1, 2): (3, 4), (3, 4): (5, 6), ...}  # next 1000 key/value pairs
  d3 = {(1, 2): (3, 4), (3, 4): (5, 6), ...}  # next 1000 key/value pairs
  # ... until you're done
  bigd = d0
  bigd.update(d1)
  del d1
  bigd.update(d2)
  del d2
  # ... continue updating with all the dN dictionaries
  return bigd

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

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

0 голосов
/ 30 июля 2011

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

...