为什么对于相同大小的列表,深拷贝比浅拷贝慢得多?

Why is a deep copy so much slower than a shallow copy for lists of the same size?

我一直在开发一个性能关键型应用程序,它经常需要制作二维整数列表的副本并修改副本(我正在实施 minimax 算法)。

我注意到在元素数量相同的列表上,副本和深度复制之间的性能存在 巨大 差异,我想了解是否我的想法是正确的。

要重现我的问题,运行 以下代码:

import numpy as np

np.random.seed(0)
lst1 = np.random.randint(100, size=1000 * 1000).tolist()
lst2 = np.random.randint(100, size=(1000, 1000)).tolist()

现在,对下面的语句进行计时,您应该会看到与我类似的计时。

%timeit copy.copy(lst1)
%timeit lst1.copy()
%timeit copy.deepcopy(lst2)

5 ms ± 49.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
5.47 ms ± 551 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
1.61 s ± 112 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

lst1lst2 都有一百万个元素,但可靠地复制前者比具有相同数量元素的嵌套列表快 200 倍。我认为这与制作嵌套列表的深层副本可能需要一些缓慢的递归实现有关,所以我尝试了

%timeit copy.deepcopy(lst1)
1.43 s ± 90.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) 

而且时间仍然显示出大幅放缓。我检查了 docs 但没有提供太多解释。然而,从时间上看,我怀疑 deepcopy 也在复制每个 int,创建新的整数。但这似乎是一件浪费的事情。

我的想法对吗? list.copy 和浅拷贝不做的 deepcopy 在这里做了什么?

我看过 deepcopy() is extremely slow 但似乎这个问题是在寻求替代方案而不是解释(我不清楚)。

deepcopy 没有复制整数。无论如何它都做不到。

deepcopy 很慢,因为它需要处理深拷贝的全部复杂性,即使事实证明这是不必要的。这包括为它找到的每个对象分派到适当的复制器,即使复制器结果是 basically just be lambda x: x. That includes maintaining a memo dict and keeping track of every object copied, to handle duplicate references to the same objects, even if there are none. That includes special copy handling for data structures like lists and dicts,因此在尝试复制具有递归引用的数据结构时它不会进入无限递归。

无论是否有回报,所有这些都必须完成。都是贵的。

此外,deepcopy 是纯-Python。那没有帮助。将 deepcopy 与执行非常相似的工作的 pickle.loads(pickle.dumps(whatever)) 进行比较,由于 C 实现,pickle 轻松获胜。 (在 Python 2 上,将 pickle 替换为 cPickle。)pickle 仍然很难输给利用已知输入结构的实现,但是:

In [15]: x = [[0]*1000 for i in range(1000)]

In [16]: %timeit copy.deepcopy(x)
1.05 s ± 5.14 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [17]: %timeit pickle.loads(pickle.dumps(x))
78 ms ± 4.03 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [18]: %timeit [l[:] for l in x]
4.56 ms ± 108 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

在编程中,深拷贝相当于某物的物理拷贝。它是原始对象的实际副本。在大多数编程工具中,您可以随意使用它,修改它而不影响原始对象。 然而,另一方面,浅拷贝是对原始对象的引用。如果更改它,它也会影响原始对象。 总之,由于深拷贝是原对象的实际拷贝,所以比仅仅指向原对象的浅拷贝重

浅拷贝:您可以拍一张您的新家具的照片,并了解它的真实外观。您可以轻松携带图片。

深拷贝:你可以去家具店,看看真正的家具。您可能不太容易随身携带,您可能需要一些帮助才能把它带回家。

https://docs.python.org/2/library/copy.html

浅复制和深复制之间的区别仅与复合对象(包含其他对象的对象,如列表或 class 实例)有关:

  1. 浅拷贝构造一个新的复合对象,然后(在可能的范围内)向其中插入对在原始对象中找到的对象的引用。
  2. 深拷贝构造一个新的复合对象,然后递归地将在原始对象中找到的对象的副本插入其中

如此有效,浅表副本将创建一个新列表并使用对原始列表中每个元素的引用填充它。因为原始列表中的每个元素本身就是一个列表,所以只存储对此的引用比创建一个新副本要快得多。 Deepcopy 在如何复制每个元素以避免错误方面做了一些巧妙的事情。但本质上你不需要理解这一点就可以知道为什么一个浅拷贝比深拷贝更快....