copy.deepcopy против рассола - PullRequest
       17

copy.deepcopy против рассола

28 голосов
/ 11 сентября 2009

У меня есть древовидная структура виджетов, например Коллекция содержит модели, а модель содержит виджеты. Я хочу скопировать всю коллекцию, copy.deepcopy быстрее по сравнению с 'pickle и de-pickle' 'объектом, но cPickle, как написано на C, намного быстрее, поэтому

  1. Почему я (мы) не должны всегда использовать cPickle вместо deepcopy?
  2. Есть ли другой вариант копирования? потому что pickle медленнее, чем deepcopy, но cPickle быстрее, так что, возможно, реализация Deepcopy на C будет победителем

Пример тестового кода:

import copy
import pickle
import cPickle

class A(object): pass

d = {}
for i in range(1000):
    d[i] = A()

def copy1():
    return copy.deepcopy(d)

def copy2():
    return pickle.loads(pickle.dumps(d, -1))

def copy3():
    return cPickle.loads(cPickle.dumps(d, -1))

Тайминги:

>python -m timeit -s "import c" "c.copy1()"
10 loops, best of 3: 46.3 msec per loop

>python -m timeit -s "import c" "c.copy2()"
10 loops, best of 3: 93.3 msec per loop

>python -m timeit -s "import c" "c.copy3()"
100 loops, best of 3: 17.1 msec per loop

Ответы [ 5 ]

31 голосов
/ 11 сентября 2009

Проблема в том, что pickle + unpickle может быть быстрее (в реализации C), потому что он менее общий , чем Deepcopy: многие объекты могут быть глубоко скопированы, но не зарезаны. Предположим, например, что ваш класс A был изменен на ...:

class A(object):
  class B(object): pass
  def __init__(self): self.b = self.B()

сейчас, copy1 все еще работает отлично (сложность А замедляет его, но абсолютно не останавливает); copy2 и copy3 break, конец трассировки стека говорит ...:

  File "./c.py", line 20, in copy3
    return cPickle.loads(cPickle.dumps(d, -1))
PicklingError: Can't pickle <class 'c.B'>: attribute lookup c.B failed

Т.е., травление всегда предполагает, что классы и функции являются объектами верхнего уровня в своих модулях, и поэтому их выбирают "по имени" - глубокое копирование не делает абсолютно таких предположений.

Так что, если у вас возникла ситуация, когда скорость «несколько глубокого копирования» абсолютно необходима, важна каждая миллисекунда, и вы хотите воспользоваться особыми ограничениями, которые вы ЗНАЕТЕ применять к дублирующимся объектам, таким как которые делают применение травления или предпочтение других форм сериализации и других ярлыков, во что бы то ни стало, продолжайте - но если вы это сделаете, вы ДОЛЖНЫ знать, что вы обязываете свою систему жить с этими ограничениями навсегда, и документировать это дизайнерское решение очень четко и недвусмысленно в интересах будущих сопровождающих.

Для нормального случая, где вы хотите обобщить, используйте deepcopy! -)

6 голосов
/ 11 сентября 2009

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

Первое правило оптимизации: не надо.

2 голосов
/ 25 марта 2014

Это , а не , всегда так, что cPickle быстрее, чем deepcopy (). Хотя cPickle, вероятно, всегда быстрее, чем pickle, зависит от того, быстрее ли это, чем Deepcopy,

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

Если что-то можно мариновать, оно, очевидно, может быть глубоко скопировано, но дело обстоит не так: Для того, чтобы что-то мариновать, оно должно быть полностью сериализовано ; это не относится к глубокому копированию. В частности, вы можете очень эффективно реализовать __deepcopy__, скопировав структуру в памяти (подумайте о типах расширений), не имея возможности сохранить все на диск. (Подумайте о приостановке на ОЗУ против приостановки на диск.)

Хорошо известный тип расширения, который удовлетворяет указанным выше условиям, может быть ndarray, и, действительно, он служит хорошим контрпримером к вашему наблюдению: с d = numpy.arange(100000000) ваш код дает другое время выполнения:

In [1]: import copy, pickle, cPickle, numpy

In [2]: d = numpy.arange(100000000)

In [3]: %timeit pickle.loads(pickle.dumps(d, -1))
1 loops, best of 3: 2.95 s per loop

In [4]: %timeit cPickle.loads(cPickle.dumps(d, -1))
1 loops, best of 3: 2.37 s per loop

In [5]: %timeit copy.deepcopy(d)
1 loops, best of 3: 459 ms per loop

Если __deepcopy__ не реализовано, copy и pickle совместно используют общую инфраструктуру (см. copy_reg модуль, обсуждается в Взаимосвязь между pickle и deepcopy ).

1 голос
/ 11 сентября 2009

Еще быстрее было бы избежать копирования в первую очередь. Вы упоминаете, что делаете рендеринг. Зачем ему нужно копировать объекты?

0 голосов
/ 28 сентября 2013

Коротко и несколько поздно:

  • Если вам все равно придется выбирать объект cPickle, вы также можете использовать метод cPickle для глубокой копии (кроме документа)

например. Вы можете рассмотреть:

def mydeepcopy(obj):
    try:
       return cPickle.loads(cPickle.dumps(obj, -1))
    except PicklingError:
       return deepcopy(obj)
...