pickle: медленная десериализация диктовки - PullRequest
4 голосов
/ 06 мая 2020

Предположим, у меня есть «довольно большой» словарь, где ключи - это объекты с тяжелой __eq__ функцией

class MyObject():

     def __eq__(self, other):
         return <very heavy function call>

     def __hash__(self):
         return <not so heavy hash calculation>

mydict = {<MyObject>:<Int>}

Проблема в том, что когда я пытаюсь оторвать маринованный объект, это требует много времени . Я считаю, что это потому, что pickle не сохраняет внутреннюю таблицу ha sh, а при восстановлении пересчитывает словарь.

Я провел простой эксперимент:

import pickle 
import pickletools 
original = { 'a': 0, 'b': [1, 2, 3] } 
pickled = pickle.dumps(original) 
pickletools.dis(pickled)

приводит к:

    0: \x80 PROTO      3
    2: }    EMPTY_DICT
    3: q    BINPUT     0
    5: (    MARK
    6: X        BINUNICODE 'a'
   12: q        BINPUT     1
   14: K        BININT1    0
   16: X        BINUNICODE 'b'
   22: q        BINPUT     2
   24: ]        EMPTY_LIST
   25: q        BINPUT     3
   27: (        MARK
   28: K            BININT1    1
   30: K            BININT1    2
   32: K            BININT1    3
   34: e            APPENDS    (MARK at 27)
   35: u        SETITEMS   (MARK at 5)
   36: .    STOP
highest protocol among opcodes = 2

Нет никаких признаков хэш-дерева. Это означает, что Pickle Machine должен пересчитать хэш-дерево после десериализации. Но это действительно пустышка? Почему pickle не сохраняет внутреннее состояние словаря и как с этим бороться?

Ответы [ 3 ]

1 голос
/ 19 мая 2020
  • Метод __eq__ вызывается только при вставке в dict при столкновении ha sh (метод __hash__ для разных объектов возвращает один и тот же ответ); если это происходит достаточно часто, чтобы быть заметным замедлением, это также значительно замедлит все другие операции - в крайнем случае, если метод __hash__ всегда возвращает один и тот же ответ независимо от значения объекта, __eq__ метод будет вызываться ½n² раз только для unpickle.

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

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

0 голосов
/ 18 мая 2020

Используйте вместо этого маринованные объекты в качестве ключей словаря.

Используя маринованный объект в качестве ключа, мы можем полностью обойти методы MyObject.__hash__ и MyObject.__eq__ и потенциально ускорить процесс распаковки.

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

p1.py

Создайте словарь, в котором ключи имеют тип MyObject затем засолить их в файл. Создайте словарь, где ключи замаринованы байтами MyObject. Сравните время, необходимое для распаковки каждого словаря.

#!/usr/bin/env python3

import pickle
import pickletools
from pprint import pprint
import random
import string
import time

random.seed(19891225)


class MyObject(object):

    def __init__(self, value):
        self.value = value

    def __eq__(self, other):
        exit(1)
        return self.value == other.value

    def __hash__(self):
        time.sleep(10)
        return hash(tuple(self.value))


def key_value():
    value_len = random.randint(0,len(string.ascii_letters))
    value = random.sample(string.ascii_letters, value_len)
    return (MyObject(value), value_len)

def object_key_dict(size=10):
    return dict([key_value() for i in range(0, size)])

def pickle_key_dict(d):
    return {pickle.dumps(k):v for k, v in d.items()}

def pickle_unpickle(obj, filename):
    with open(filename, "wb") as pout:
        pickle.dump(obj, pout)

    print(f"Unpickle Start: {time.strftime('%H:%M:%s')}")
    with open(filename, "rb") as pin:
        newobj = pickle.load(pin)
    print(f"Unpickle complete: {time.strftime('%H:%M:%s')}")
    print("-"*20)

    return newobj


if __name__ == "__main__":
    d = object_key_dict(size=10)

    print("Use MyObject instances as dictionary keys")
    unpickled_d = pickle_unpickle(d, 'doc.p')

    print("\nUse pickled ojbects as dicitonary keys")
    pd = pickle_key_dict(unpickled_d)
    upd = pickle_unpickle(pd, 'docp.p')

Вывод

Use MyObject instances as dictionary keys
Unpickle Start: 09:25:1589808319
Unpickle complete: 09:26:1589808419
--------------------

Use pickled ojbects as dicitonary keys
Unpickle Start: 09:26:1589808419
Unpickle complete: 09:26:1589808419
--------------------
0 голосов
/ 15 мая 2020

Pickle Machine реализуется для различных Python Объектов, и некоторым объектам может не понравиться реализация ha sh, и они могут тормозить.

Если, все, что вы делаете, - это экономия Json Объекты, используйте объект json, поскольку он будет эффективным.

...