【问题标题】:copy.deepcopy vs picklecopy.deepcopy 与 pickle
【发布时间】:2010-11-27 11:49:04
【问题描述】:

我有一个小部件的树形结构,例如集合包含模型,模型包含小部件。我想复制整个集合,copy.deepcopy 与 'pickle and 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

【问题讨论】:

  • 这是一个非常有用的观察结果。
  • hm,您不应该将picklecopy.copy 进行比较吗?
  • 它是否完全取决于您正在复制的内容的结构?例如,如果分配给复制对象的内存是不连续的,或者底层指针有一条长链要遵循,这会有所不同吗?
  • @SilentGhost,pickle 做了一个深拷贝

标签: python pickle deep-copy


【解决方案1】:

并非总是 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__ 未实现,copypickle 共享公共基础架构(参见copy_reg 模块,在Relationship between pickle and deepcopy 中讨论)。

【讨论】:

    【解决方案2】:

    短且有点晚:

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

    例如您可以考虑:

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

    【讨论】:

    • 为什么我的换行符消失了,stov 中的错误?
    • 但这不是问题,我已经在以类似的方式使用 cPickle
    • 我能想到的最快的方法,除了编写自己的 python c 扩展
    【解决方案3】:

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

    【讨论】:

    • 是的,理想情况下它不需要复制,因为视图(渲染)和模型将被解耦,但在我的情况下,渲染确实会修改模型,因此我需要在渲染之前复制模型所以原始的不需要'不被修改
    • 我不是要打死马,但是解决渲染修改模型的问题会让你很开心。
    • 我同意,但是更改这么多代码会很昂贵,因为这里无法讨论我已经添加了一个问题stackoverflow.com/questions/1414246/…
    【解决方案4】:

    问题是,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!-)

    【讨论】:

      【解决方案5】:

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

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

      【讨论】:

      • 优化的第二条规则:还没有
      • 忘记过时的规则,用 cpickle 替换 deepcopy,让我的项目渲染速度提高 25%,让我的客户满意 :)
      • @wds,我也不认为复制对象的包装函数被命名为 deepcopyObject 并且注释很好
      • @anurag 如果你遇到这样的速度问题,无论如何。虽然它在我看来更像是一种创可贴。
      • 我建议不要使用cPickle 进行优化,而是实施__deepcopy__(请参阅我的单独答案)。
      猜你喜欢
      • 2012-05-03
      • 1970-01-01
      • 2023-04-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-11-05
      • 2013-09-12
      相关资源
      最近更新 更多