31

我有一个小部件的树形结构,例如集合包含模型,模型包含小部件。我想复制整个集合,copy.deepcopy与“腌制和去腌制”对象相比速度更快,但是用 C 编写的 cPickle 更快,所以

  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
4

5 回答 5

35

问题是,pickle+unpickle 可以更快(在 C 实现中),因为它不如 deepcopy通用:许多对象可以深度复制但不能腌制。例如,假设您的课程A更改为...:

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

现在,copy1仍然可以正常工作(A 的复杂性会减慢它的速度,但绝对不会阻止它);copy2copy3打破,堆栈跟踪的末尾说......:

  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

即,pickling 总是假定类和函数是其模块中的顶级实体,因此“按名称”对它们进行 pickle——deepcopying 绝对没有这样的假设。

因此,如果您遇到“稍微深复制”的速度绝对至关重要的情况,那么每一毫秒都很重要,并且您想利用您知道适用于您正在复制的对象的特殊限制,例如进行酸洗的对象适用的,或者偏爱其他形式的序列化和其他快捷方式的,无论如何都要继续 - 但如果你这样做,你必须意识到你正在限制你的系统永远受这些限制,并非常清楚地记录设计决策和明确地为了未来的维护者的利益。

对于需要通用性的 NORMAL 情况,请使用deepcopy!-)

于 2009-09-11T14:27:53.363 回答
7

您应该使用 deepcopy,因为它使您的代码更具可读性。使用序列化机制复制内存中的对象至少会让另一个阅读您的代码的开发人员感到困惑。使用 deepcopy 还意味着您可以从 deepcopy 中的未来优化中获益。

优化的第一条规则:不要。

于 2009-09-11T13:18:11.707 回答
2

cPickle并不总是比 deepcopy() 快。虽然 cPickle 可能总是比 pickle 快,但它是否比 deepcopy 快取决于

  • 要复制的结构的大小和嵌套级别,
  • 包含对象的类型,以及
  • 腌制字符串表示的大小。

如果可以腌制,显然可以深拷贝,但反之则不然:为了腌制,需要完全序列化;深度复制不是这种情况。特别是,您可以__deepcopy__通过在内存中复制结构(考虑扩展类型)来非常有效地实现,而无需将所有内容保存到磁盘。(考虑暂停到 RAM 与暂停到磁盘。)

满足上述条件的著名扩展类型可能是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 之间的关系中讨论)。

于 2014-03-24T20:27:53.673 回答
1

更快的方法是首先避免复制。你提到你正在做渲染。为什么需要复制对象?

于 2009-09-11T14:41:01.557 回答
1

简短且有点晚:

  • 如果您无论如何都必须 cPickle 一个对象,您不妨使用 cPickle 方法进行深度复制(但文档)

例如,您可能会考虑:

def mydeepcopy(obj):
    try:
       return cPickle.loads(cPickle.dumps(obj, -1))
    except PicklingError:
       return deepcopy(obj)
于 2013-09-28T09:54:24.813 回答