Python—高效复制列表子范围

Python—高效复制列表子范围,python,list,performance,copy,range,Python,List,Performance,Copy,Range,我有一个列表l和非负整数n,start0,start1 我需要最有效(最快、最高速)的方式将l[start0:start0+n]复制(移动)到l[start1:start1+n] 我需要一个浅拷贝(只拷贝引用)为我目前的情况,但将有趣的是如何使快速的范围深拷贝了 next是最有效和最短的方法吗 l[start1 : start1 + n] = l[start0 : start0 + n] 由于左侧和右侧具有相同的范围长度n,理论上Python可以通过复制这些数据来进行优化,而无需创建任何中间列

我有一个列表
l
和非负整数
n
start0
start1

我需要最有效(最快、最高速)的方式将
l[start0:start0+n]
复制(移动)到
l[start1:start1+n]

我需要一个浅拷贝(只拷贝引用)为我目前的情况,但将有趣的是如何使快速的范围深拷贝了

next是最有效和最短的方法吗

l[start1 : start1 + n] = l[start0 : start0 + n]
由于左侧和右侧具有相同的范围长度
n
,理论上Python可以通过复制这些数据来进行优化,而无需创建任何中间列表或调整/重新分配原始列表的大小。但是python会做这样的优化吗

我试着做下一步的测量。我正在创建不同长度的列表,同时只复制较短的子范围,还做了三个变体-复制相同长度的子范围、较短的子范围和较长的子范围。理论上,对于相同的长度范围,python只能复制较短的数据,这是非常有效的,而对于较短或较长的子范围,python应该移动和/或重新分配整个大数组

正如我们所看到的,对于长列表的相同长度(10)范围,复制需要微秒,而较短(9)/较长(11)范围的复制需要毫秒。同样,复制相同长度的非常短和非常长的列表所需的时间相差不大(这意味着不会像案例9和案例11那样相差数千倍)。另外,用较短的范围替换范围要比用较长的范围替换范围快得多,因为前者只需要复制整个阵列的数据,而后者还需要重新分配阵列,因为它需要一个较大的阵列来存储新数据

我在上面所做的测量只是为了检查Python是否足够聪明,在复制相同长度到相同长度时不会重新创建整个数组。因为Python列表复制的下一个非聪明的简单实现也是可能的-1)使用
l[0:start0]
的内容副本创建新的临时数组,2)使用
l[start1:start1+n1]
的内容副本创建新的临时数组,3)使用
l[start0:start0+n0]的内容副本创建新的临时数组
,4)将这些1)-3)连接到新的大数组中。当然,我几乎可以肯定python不会这样做,但为了再次检查,我做了测量。当然,只有当右侧的切片表达式与左侧的切片表达式长度相同时,才会复制
n
元素。如果左侧和右侧的大小不同,则应复制一半(平均)或整个阵列数据,如案例9和11的测量结果所示

看起来Python实际上对相同的长度范围进行了优化(这意味着整个原始列表不会从头重新分配,只复制范围值(引用)。有文件证明吗


也许还有一些更优化的内置函数用于复制相同的长度范围?像
l.copy\u范围(start0,n,start1)
。与
C++
中的相同存在
std::copy(vec.begin()+start0,vec.begin()+start0+n,vec.begin()+start1)
同样,Python可能有内置的基于
C
的方法来复制引用

还有
步骤如何
?用于复制
[start0:start0+n:step]

下一步也是复制范围的方法:

for i in range(n):
    l[sart1 + i] = l[sart0 + i]
但可能不如第一种方法有效

那么,将范围从列表中的一个位置复制到同一列表中的另一个位置的最有效方法是什么


PS。顺便说一句,对于我的特定任务,我甚至不需要复制范围,而是需要将范围从一个地方移动到另一个地方,即旧区域可以不填充。如果Python中存在用于移动区域的内置函数,那么对于Python来说,使用
C
-函数(如
memmove()
)并将旧区域归零就足够了,无需复制或增加/减少条目的引用计数。

TL;DR:CPython解释器创建l切片的浅拷贝,然后执行浅拷贝

列表基本上是对象引用的数组。 列表的浅层副本是一个内存数组,只包含对象引用(例如指针),指向与其他列表共享的对象(例如,至少在创建切片之后复制的对象)。列表的深度副本是一个内存数组,其中包含对从源列表新创建的对象副本的对象引用。 您可以找到有关Python浅/深拷贝的更多信息,或者

这意味着当您写入
l[0:c]
时,将在内存中创建一个新数组,引用列表
l
中的
c
对象。通过构建庞大的列表并查看内存使用情况和执行时间,您可以看到:

\CPython进程内存:<50个月
a=[0代表范围内的i(256*1024*1024)]#时间:5.45秒
#CPython进程内存:~2 Go
#请注意,所有引用都引用同一个不可变对象0,在我的64位计算机上,每个引用(内存指针)占用8字节。
tmp=a[0:128*1024*1024]#时间:372毫秒
#CPython进程内存:~3 Go
#由于128*1024*1024个8字节的引用,tmp在这里取1
del tmp时间:219毫秒
#CPython进程内存:~2 Go
#tmp的内部参考内存阵列已被释放
a[0:128*1024*1024]=a[128*1024*1024:256*1024*1024]#时间:1.30秒
#CPython进程内存:~2 Go
#CPython在一小部分时间内分配1 Go额外内存,然后在浅拷贝完成后释放它。
玩许多不可变对象也很有趣(注意,这里使用的内存量不是很精确):

\CPython进程内存:<50个月
%时间a=[i代表范围内的i(64*1024*1024)]
#CPython进程内存:~2.6 Go
#除了引用之外,还必须创建许多整数对象(比引用重得多)
%时间tmp=a[0:32*1024*1024]
#CPython进程内存:~2.8go
#无整数o
size =       32, n = 10: Timed best=1.026 µs, mean=1.464 ± 0.2 µs
size =       32, n =  9: Timed best=1.026 µs, mean=1.528 ± 0.2 µs
size =       32, n = 11: Timed best=1.539 µs, mean=1.544 ± 0.1 µs
size =      256, n = 10: Timed best=1.026 µs, mean=1.532 ± 0.1 µs
size =      256, n =  9: Timed best=1.539 µs, mean=1.546 ± 0.1 µs
size =      256, n = 11: Timed best=1.539 µs, mean=2.172 ± 0.2 µs
size =     2048, n = 10: Timed best=1.539 µs, mean=1.585 ± 0.1 µs
size =     2048, n =  9: Timed best=2.565 µs, mean=2.580 ± 0.1 µs
size =     2048, n = 11: Timed best=4.105 µs, mean=4.375 ± 0.3 µs
size =    16384, n = 10: Timed best=2.052 µs, mean=2.125 ± 0.2 µs
size =    16384, n =  9: Timed best=11.802 µs, mean=12.107 ± 0.3 µs
size =    16384, n = 11: Timed best=12.315 µs, mean=12.874 ± 0.7 µs
size =   131072, n = 10: Timed best=3.592 µs, mean=3.907 ± 0.2 µs
size =   131072, n =  9: Timed best=96.988 µs, mean=100.387 ± 1.4 µs
size =   131072, n = 11: Timed best=851.852 µs, mean=873.135 ± 13.3 µs
size =  1048576, n = 10: Timed best=9.750 µs, mean=10.434 ± 0.3 µs
size =  1048576, n =  9: Timed best=1.119 ms, mean=1.127 ± 0.0 ms
size =  1048576, n = 11: Timed best=6.986 ms, mean=7.054 ± 0.0 ms
size =  8388608, n = 10: Timed best=10.263 µs, mean=10.862 ± 0.4 µs
size =  8388608, n =  9: Timed best=9.752 ms, mean=9.813 ± 0.0 ms
size =  8388608, n = 11: Timed best=58.158 ms, mean=58.692 ± 0.4 ms
size = 67108864, n = 10: Timed best=12.829 µs, mean=13.548 ± 0.4 µs
size = 67108864, n =  9: Timed best=78.876 ms, mean=79.061 ± 0.1 ms
size = 67108864, n = 11: Timed best=471.014 ms, mean=473.688 ± 1.9 ms
for i in range(n):
    l[sart1 + i] = l[sart0 + i]