Как избежать вычислений каждый раз при перезагрузке модуля Python

StackOverflow https://stackoverflow.com/questions/195626

  •  10-07-2019
  •  | 
  •  

Вопрос

У меня есть модуль Python, который использует огромную глобальную переменную словаря, в настоящее время я помещаю код вычислений в верхний раздел, каждый первый импорт или перезагрузка модуля занимает более одной минуты, что совершенно неприемлемо.Как я могу сохранить где-нибудь результат вычисления, чтобы при следующем импорте/перезагрузке не приходилось его вычислять?Я попробовал cPickle, но загрузка словарной переменной из файла (1,3M) занимает примерно то же время, что и вычисление.

Чтобы дать больше информации о моей проблеме,

FD = FreqDist(word for word in brown.words()) # this line of code takes 1 min
Это было полезно?

Решение

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

Однако, если вашей проблемой является время, необходимое для первого импорта после запуска программы, вам, вероятно, придется использовать какой-то другой метод, кроме python dict. Вероятно, лучше всего было бы использовать форму на диске, например, базу данных sqlite, один из модулей dbm.

Для минимального изменения в вашем интерфейсе модуль shelve может быть вашим лучшим вариантом - это создает довольно прозрачный интерфейс между модулями dbm, который заставляет их действовать как произвольный диктон python, позволяя сохранять любое выбираемое значение. Вот пример:

# Create dict with a million items:
import shelve
d = shelve.open('path/to/my_persistant_dict')
d.update(('key%d' % x, x) for x in xrange(1000000))
d.close()

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

>>> d = shelve.open('path/to/my_persistant_dict')
>>> print d['key99999']
99999

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

Другие советы

Рассчитайте глобальную переменную при первом использовании.

class Proxy:
    @property
    def global_name(self):
        # calculate your global var here, enable cache if needed
        ...

_proxy_object = Proxy()
GLOBAL_NAME = _proxy_object.global_name

Или, еще лучше, получить доступ к необходимым данным через специальный объект данных.

class Data:
    GLOBAL_NAME = property(...)

data = Data()

Пример:

from some_module import data

print(data.GLOBAL_NAME)

Видеть Настройки Джанго.

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

Вы можете попробовать использовать маршал вместо c Мариновать один; это может быть быстрее. Этот модуль используется Python для хранения значений в двоичном формате. Обратите особое внимание на следующий абзац, чтобы узнать, подходит ли вам маршал:

  

Поддерживаются не все типы объектов Python; в общем, этот модуль может записывать и читать только объекты, значение которых не зависит от конкретного вызова Python. Поддерживаются следующие типы: Нет, целые числа, длинные целые числа, числа с плавающей запятой, строки, объекты Unicode, кортежи, списки, наборы, словари и объекты кода, где следует понимать, что кортежи, списки и словари поддерживаются только до тех пор, пока поскольку содержащиеся в нем значения сами поддерживаются; и рекурсивные списки и словари не должны быть написаны (они вызовут бесконечные циклы).

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

Если «полочное» решение окажется слишком медленным или неудобным, есть и другие возможности:

shelve очень медленно работает с большими наборами данных. Я использую redis довольно и написал обертку FreqDist . Это очень быстро, и к нему можно получить доступ одновременно.

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

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

Несколько вещей, которые помогут ускорить импорт:

<Ол>
  • Вы можете попробовать запустить python, используя флаг -OO при запуске python. Это позволит оптимизировать время импорта модулей.
  • Есть ли причина, по которой вы не можете разбить словарь на меньшие словари в отдельных модулях, которые можно загружать быстрее?
  • В крайнем случае вы можете выполнять вычисления асинхронно, чтобы они не задерживали вашу программу до тех пор, пока ей не потребуются результаты. Или, может быть, даже поместить словарь в отдельный процесс и передавать данные назад и вперед с помощью IPC, если вы хотите использовать преимущества многоядерных архитектур.
  • С учетом вышесказанного я согласен с тем, что вы не должны испытывать каких-либо задержек при импорте модулей после первого импорта. Вот несколько других общих мыслей:

    <Ол>
  • Импортируете ли вы модуль в функцию? Если это так, это может привести к проблемам с производительностью, так как он должен проверять и проверять, загружается ли модуль каждый раз при обращении к оператору импорта.
  • Ваша программа многопоточная? Я видел случаи, когда выполнение кода при импорте модуля в многопоточном приложении может вызывать некоторую нестабильность и нестабильность приложения (особенно это касается модуля cgitb).
  • Если это глобальная переменная, помните, что время поиска глобальной переменной может быть значительно больше времени поиска локальной переменной. В этом случае вы можете добиться значительного улучшения производительности, привязав словарь к локальной переменной, если вы используете его несколько раз в одном и том же контексте.
  • С учетом вышесказанного, довольно сложно дать вам какой-либо конкретный совет без некоторого дополнительного контекста. Точнее, куда вы его импортируете? А что за вычисления?

    <Ол>
  • Разложите вычислительно интенсивную часть в отдельный модуль. Тогда, по крайней мере, на перезагрузке, вам не придется ждать.

  • Попытайтесь вывести структуру данных с использованием протокола 2. Команда для попытки будет выглядеть так: cPickle.dump (FD, protocol = 2) . Из строки документации для cPickle.Pickler :

    Protocol 0 is the
    only protocol that can be written to a file opened in text
    mode and read back successfully.  When using a protocol higher
    than 0, make sure the file is opened in binary mode, both when
    pickling and unpickling. 
    
  • Я прохожу эту же проблему ... полки, базы данных и т. д. слишком медленные для такого рода проблем. Вам нужно будет принять удар один раз, вставить его в хранилище ключей / val памяти, например, Redis. Он просто будет жить в памяти (предупреждая, что он может занять достаточно много памяти, поэтому вам может понадобиться выделенная коробка). Вам никогда не придется его перезагружать, и вы просто будете искать в памяти ключи

    r = Redis()
    r.set(key, word)
    
    word = r.get(key)
    

    Расширяя идею отложенных вычислений, почему бы не превратить dict в класс, который предоставляет (и кэширует) элементы по мере необходимости?

    Вы также можете использовать psyco для ускорения общего исполнения ...

    ИЛИ вы можете просто использовать базу данных для хранения значений в? Взгляните на SQLObject, который позволяет очень просто хранить вещи в базе данных.

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

    Итак ... сделав что-то подобное, вы убедитесь, что этот код выполняется только один раз.

    try:
        FD
    except NameError:
        FD = FreqDist(word for word in brown.words())
    
    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top