Как сократить время, затрачиваемое на загрузку файла pickle в python

Я создал словарь на python и сбрасывал в pickle. Его размер составил 300 МБ. Теперь, я хочу загрузить тот же рассол.

output = open('myfile.pkl', 'rb')
mydict = pickle.load(output)

Загрузка этого рассола занимает около 15 секунд. Как я могу уменьшить это время?

Спецификация оборудования: Ubuntu 14.04, RAM 4 ГБ

В приведенном ниже коде показано, сколько времени требуется для сброса или загрузки файла с помощью json, pickle, cPickle.

После демпинга размер файла составит около 300 МБ.

import json, pickle, cPickle
import os, timeit
import json

mydict= {all values to be added}

def dump_json():    
    output = open('myfile1.json', 'wb')
    json.dump(mydict, output)
    output.close()    

def dump_pickle():    
    output = open('myfile2.pkl', 'wb')
    pickle.dump(mydict, output,protocol=cPickle.HIGHEST_PROTOCOL)
    output.close()

def dump_cpickle():    
    output = open('myfile3.pkl', 'wb')
    cPickle.dump(mydict, output,protocol=cPickle.HIGHEST_PROTOCOL)
    output.close()

def load_json():
    output = open('myfile1.json', 'rb')
    mydict = json.load(output)
    output.close()

def load_pickle():
    output = open('myfile2.pkl', 'rb')
    mydict = pickle.load(output)
    output.close()

def load_cpickle():
    output = open('myfile3.pkl', 'rb')
    mydict = pickle.load(output)
    output.close()


if __name__ == '__main__':
    print "Json dump: "
    t = timeit.Timer(stmt="pickle_wr.dump_json()", setup="import pickle_wr")  
    print t.timeit(1),'\n'

    print "Pickle dump: "
    t = timeit.Timer(stmt="pickle_wr.dump_pickle()", setup="import pickle_wr")  
    print t.timeit(1),'\n'

    print "cPickle dump: "
    t = timeit.Timer(stmt="pickle_wr.dump_cpickle()", setup="import pickle_wr")  
    print t.timeit(1),'\n'

    print "Json load: "
    t = timeit.Timer(stmt="pickle_wr.load_json()", setup="import pickle_wr")  
    print t.timeit(1),'\n'

    print "pickle load: "
    t = timeit.Timer(stmt="pickle_wr.load_pickle()", setup="import pickle_wr")  
    print t.timeit(1),'\n'

    print "cPickle load: "
    t = timeit.Timer(stmt="pickle_wr.load_cpickle()", setup="import pickle_wr")  
    print t.timeit(1),'\n'

Выход:

Json dump: 
42.5809804916 

Pickle dump: 
52.87407804489 

cPickle dump: 
1.1903790187836 

Json load: 
12.240660209656 

pickle load: 
24.48748306274 

cPickle load: 
24.4888298893

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

Ответ 1

Попробуйте использовать json library вместо pickle. Это должно быть вариантом в вашем случае, потому что вы имеете дело со словарем, который является относительно простым объектом.

Согласно этот сайт,

JSON в 25 раз быстрее считывает (загружает) и в 15 раз быстрее (дампы).

Также см. этот вопрос: Что происходит быстрее - загрузка маринованного словарного объекта или загрузка файла JSON - в словарь?

Обновление Python или использование модуля marshal с фиксированной версией Python также помогает повысить скорость (код, адаптированный здесь):

try: import cPickle
except: import pickle as cPickle
import pickle
import json, marshal, random
from time import time
from hashlib import md5

test_runs = 1000

if __name__ == "__main__":
    payload = {
        "float": [(random.randrange(0, 99) + random.random()) for i in range(1000)],
        "int": [random.randrange(0, 9999) for i in range(1000)],
        "str": [md5(str(random.random()).encode('utf8')).hexdigest() for i in range(1000)]
    }
    modules = [json, pickle, cPickle, marshal]

    for payload_type in payload:
        data = payload[payload_type]
        for module in modules:
            start = time()
            if module.__name__ in ['pickle', 'cPickle']:
                for i in range(test_runs): serialized = module.dumps(data, protocol=-1)
            else:
                for i in range(test_runs): serialized = module.dumps(data)
            w = time() - start
            start = time()
            for i in range(test_runs):
                unserialized = module.loads(serialized)
            r = time() - start
            print("%s %s W %.3f R %.3f" % (module.__name__, payload_type, w, r))

Результаты:

C:\Python27\python.exe -u "serialization_benchmark.py"
json int W 0.125 R 0.156
pickle int W 2.808 R 1.139
cPickle int W 0.047 R 0.046
marshal int W 0.016 R 0.031
json float W 1.981 R 0.624
pickle float W 2.607 R 1.092
cPickle float W 0.063 R 0.062
marshal float W 0.047 R 0.031
json str W 0.172 R 0.437
pickle str W 5.149 R 2.309
cPickle str W 0.281 R 0.156
marshal str W 0.109 R 0.047

C:\pypy-1.6\pypy-c -u "serialization_benchmark.py"
json int W 0.515 R 0.452
pickle int W 0.546 R 0.219
cPickle int W 0.577 R 0.171
marshal int W 0.032 R 0.031
json float W 2.390 R 1.341
pickle float W 0.656 R 0.436
cPickle float W 0.593 R 0.406
marshal float W 0.327 R 0.203
json str W 1.141 R 1.186
pickle str W 0.702 R 0.546
cPickle str W 0.828 R 0.562
marshal str W 0.265 R 0.078

c:\Python34\python -u "serialization_benchmark.py"
json int W 0.203 R 0.140
pickle int W 0.047 R 0.062
pickle int W 0.031 R 0.062
marshal int W 0.031 R 0.047
json float W 1.935 R 0.749
pickle float W 0.047 R 0.062
pickle float W 0.047 R 0.062
marshal float W 0.047 R 0.047
json str W 0.281 R 0.187
pickle str W 0.125 R 0.140
pickle str W 0.125 R 0.140
marshal str W 0.094 R 0.078

Python 3.4 использует протокол pickle 3 по умолчанию, который не отличался по сравнению с протоколом 4. Python 2 имеет протокол 2 как самый высокий протокол pickle (выбрано, если для дампа дано отрицательное значение), что в два раза медленнее, чем протокол 3.

Ответ 2

Если вы пытаетесь сохранить словарь в одном файле, это время загрузки для большого файла, который замедляет вас. Одна из самых простых вещей, которую вы можете сделать, - это записать словарь в каталог на диске, причем каждая запись словаря является отдельным файлом. Затем вы можете иметь файлы, замаринованные и разбросанные по нескольким потокам (или с использованием многопроцессорности). Для очень большого словаря это должно быть намного быстрее, чем чтение в один файл и из него, независимо от выбранного вами сериализатора. Есть несколько пакетов, таких как klepto и joblib, которые уже много делают (если не все). Я проверил бы эти пакеты. (Примечание: Я автор klepto. См. https://github.com/uqfoundation/klepto).

Ответ 3

У меня были хорошие результаты при чтении огромных файлов (например: ~ 750 МБ igraph object - бинарный файл pickle) с использованием самой cPickle. Это было достигнуто путем простого обкатки вызова нагрузки на рассол, как указано здесь

Пример фрагмента в вашем случае будет примерно таким:

import timeit
import cPickle as pickle
import gc


def load_cpickle_gc():
    output = open('myfile3.pkl', 'rb')

    # disable garbage collector
    gc.disable()

    mydict = pickle.load(output)

    # enable garbage collector again
    gc.enable()
    output.close()


if __name__ == '__main__':
    print "cPickle load (with gc workaround): "
    t = timeit.Timer(stmt="pickle_wr.load_cpickle_gc()", setup="import pickle_wr")
    print t.timeit(1),'\n'

Конечно, могут быть более подходящие способы выполнения задачи, однако это обходное решение действительно сокращает время, требуемое радикально. (Для меня он уменьшился с 843,04 до 41,28 с, около 20 раз).