Сериализация списка Python - самый быстрый метод

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

Какой способ самый быстрый и почему?

  1. Использование import в файле .py, который просто содержит список, назначенный переменной
  2. Используя cPickle's load
  3. Другой метод (возможно numpy?)

Кроме того, как можно надежно протестировать такие вещи?

Приложение: надежно измерить это затруднительно, потому что import кэшируется, поэтому его нельзя выполнить несколько раз в тесте. Загрузка с помощью pickle также ускоряется после первого раза, вероятно, из-за предварительного кеширования страниц ОС. Загрузка 1 миллиона номеров с cPickle занимает 1,1 секунды при первом запуске и 0,2 секунды при последующих запусках скрипта.

Интуитивно я чувствую, что cPickle должен быть быстрее, но я был бы признателен за цифры (я думаю, это довольно сложно измерить).

И да, для меня важно, чтобы это работало быстро.

Спасибо


person Eli Bendersky    schedule 17.02.2009    source источник
comment
Это действительно самая медленная часть вашего кода? Как часто вы собираетесь загружать файл?   -  person Douglas Leeder    schedule 17.02.2009
comment
Вы пробовали что-нибудь из этого? Какие показатели у вас есть прямо сейчас?   -  person S.Lott    schedule 17.02.2009
comment
Как бы то ни было, вы можете избежать проблем с импортом, используя execfile () ...   -  person gahooa    schedule 19.02.2009


Ответы (6)


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

Если вы можете использовать массив, который является встроенным типом последовательности, я рассчитал время это за четверть секунды для 1 миллиона целых чисел:

from array import array
from datetime import datetime

def WriteInts(theArray,filename):
    f = file(filename,"wb")
    theArray.tofile(f)
    f.close()

def ReadInts(filename):
    d = datetime.utcnow()
    theArray = array('i')
    f = file(filename,"rb")
    try:
        theArray.fromfile(f,1000000000)
    except EOFError:
        pass
    print "Read %d ints in %s" % (len(theArray),datetime.utcnow() - d)
    return theArray

if __name__ == "__main__":
    a = array('i')
    a.extend(range(0,1000000))
    filename = "a_million_ints.dat"
    WriteInts(a,filename)
    r = ReadInts(filename)
    print "The 5th element is %d" % (r[4])
person Carlos A. Ibarra    schedule 17.02.2009
comment
«Прочитать 1000000 ints за 0: 00: 03.500000», а у вас это заняло 1/4 секунды? - person Eli Bendersky; 17.02.2009
comment
однако вы правы, array.fromfile намного быстрее, чем cpickle !! - person Eli Bendersky; 17.02.2009
comment
@eliben - возможно, вы захотите выбрать это как лучший ответ. Уроки по использованию модуля timeit популярны, но они не дают прямого ответа на ваш вопрос! - person Greg Ball; 19.02.2009

Для тестирования см. Модуль timeit в стандартной библиотеке Python. Чтобы увидеть самый быстрый способ, реализуйте все возможные способы и измеряйте их с помощью timeit.

Случайная мысль: в зависимости от того, что именно вы делаете, вы можете быстрее всего хранить «наборы целых чисел» в стиле, используемом в файлах .newsrc:

1, 3-1024, 11000-1200000

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

person Community    schedule 17.02.2009

Чтобы помочь вам с расчетом времени, библиотека Python предоставляет модуль timeit:

Этот модуль предоставляет простой способ отследить небольшие фрагменты кода Python. Он имеет как командную строку, так и вызываемые интерфейсы. Это позволяет избежать ряда распространенных ловушек для измерения времени выполнения.

Пример (из руководства), в котором сравнивается стоимость использования hasattr() и try/except для проверки отсутствующих и присутствующих атрибутов объекта:

% timeit.py 'try:' '  str.__nonzero__' 'except AttributeError:' '  pass'
100000 loops, best of 3: 15.7 usec per loop
% timeit.py 'if hasattr(str, "__nonzero__"): pass'
100000 loops, best of 3: 4.26 usec per loop
% timeit.py 'try:' '  int.__nonzero__' 'except AttributeError:' '  pass'
1000000 loops, best of 3: 1.43 usec per loop
% timeit.py 'if hasattr(int, "__nonzero__"): pass'
100000 loops, best of 3: 2.23 usec per loop
person gimel    schedule 17.02.2009

"как можно надежно измерить такие вещи?"

Я не понимаю вопроса.

Вы пишете кучу маленьких функций для создания и сохранения вашего списка в различных формах.

Вы пишете кучу маленьких функций для загрузки ваших списков в их различных формах.

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

Вы суммируете свои данные в небольшом отчете.

Что в этом ненадежного?

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

Преобразовать список целых чисел в одно число?

Конкатенация строк против подстановки строк в Python

person S.Lott    schedule 17.02.2009
comment
Как я могу выполнить import ‹filename› несколько раз в цикле, если импорт кэширован? - person Eli Bendersky; 17.02.2009
comment
Если ваш набор данных достаточно велик, вам может хватить одного измерения. Если нет, вы можете запустить из командной строки в цикле оболочки и вместо этого рассчитать время. Также посмотрите imp.load_module. - person S.Lott; 17.02.2009

Вам нужно всегда загружать весь файл? В противном случае лучшим решением может быть upack_from (). Предположим, у вас есть 1000000 целых чисел, но вы хотите загрузить только числа от 50000 до 50099, вы бы сделали:

import struct
intSize = struct.calcsize('i') #this value would be constant for a given arch
intFile = open('/your/file.of.integers')
intTuple5K100 = struct.unpack_from('i'*100,intFile,50000*intSize)
person vartec    schedule 24.02.2009

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

Другим преимуществом является то, что он более безопасен (поскольку он не выполняет команды), и у вас нет проблем с правильной настройкой $PYTHONPATH.

person Johannes Weiss    schedule 17.02.2009