Python中复制数组/列表的有效方法

Python中复制数组/列表的有效方法,python,ruby,arrays,Python,Ruby,Arrays,注意:我是一名Ruby开发人员,试图在Python中找到自己的方法 当我想弄清楚为什么有些脚本使用mylist[:]而不是list(mylist)来复制列表时,我对复制range(10)的各种方法进行了快速基准测试(见下面的代码) EDIT:我更新了测试,以使用Python的timeit,如下所示。这使得无法直接将其与Ruby进行比较,因为timeit不考虑循环,而Ruby的基准测试则考虑循环,因此Ruby代码仅供参考 Python 2.7.2 Array duplicating. Tests

注意:我是一名Ruby开发人员,试图在Python中找到自己的方法

当我想弄清楚为什么有些脚本使用
mylist[:]
而不是
list(mylist)
来复制列表时,我对复制
range(10)
的各种方法进行了快速基准测试(见下面的代码)

EDIT:我更新了测试,以使用Python的
timeit
,如下所示。这使得无法直接将其与Ruby进行比较,因为timeit不考虑循环,而Ruby的
基准测试则考虑循环,因此Ruby代码仅供参考

Python 2.7.2

Array duplicating. Tests run 50000000 times
list(a)     18.7599430084
copy(a)     59.1787488461
a[:]         9.58828091621
a[0:len(a)] 14.9832749367
作为参考,我也用Ruby编写了相同的脚本:

Ruby 1.9.2p0

Array duplicating. Tests 50000000 times
                      user     system      total        real
Array.new(a)     14.590000   0.030000  14.620000 ( 14.693033)
Array[*a]        18.840000   0.060000  18.900000 ( 19.156352)
a.take(a.size)    8.780000   0.020000   8.800000 (  8.805700)
a.clone          16.310000   0.040000  16.350000 ( 16.384711)
a[0,a.size]       8.950000   0.020000   8.970000 (  8.990514)
问题1:什么是
mylist[:]
做得不同,它甚至比
mylist[0:len(mylist)]快25%
。它是直接复制到内存中还是什么

问题2:编辑:更新的基准测试不再显示Python和Ruby的巨大差异was:我是否以某种明显低效的方式实现了测试,因此Ruby代码比Python快得多

现在,代码清单如下:

Python:

import timeit

COUNT = 50000000

print "Array duplicating. Tests run", COUNT, "times"

setup = 'a = range(10); import copy'

print "list(a)\t\t", timeit.timeit(stmt='list(a)', setup=setup, number=COUNT)
print "copy(a)\t\t", timeit.timeit(stmt='copy.copy(a)', setup=setup, number=COUNT)
print "a[:]\t\t", timeit.timeit(stmt='a[:]', setup=setup, number=COUNT)
print "a[0:len(a)]\t", timeit.timeit(stmt='a[0:len(a)]', setup=setup, number=COUNT)
红宝石:

使用python中的模块测试计时

from copy import *

a=range(1000)

def cop():
    b=copy(a)

def func1():
    b=list(a)

def slice():
    b=a[:]

def slice_len():
    b=a[0:len(a)]



if __name__=="__main__":
    import timeit
    print "copy(a)",timeit.timeit("cop()", setup="from __main__ import cop")
    print "list(a)",timeit.timeit("func1()", setup="from __main__ import func1")
    print "a[:]",timeit.timeit("slice()", setup="from __main__ import slice")
    print "a[0:len(a)]",timeit.timeit("slice_len()", setup="from __main__ import slice_len")
结果:

copy(a) 3.98940896988
list(a) 2.54542589188
a[:] 1.96630120277                   #winner
a[0:len(a)] 10.5431251526
当然,
a[0:len(a)]
中涉及的额外步骤是其速度缓慢的原因

下面是这两种方法的字节码比较:

In [19]: dis.dis(func1)
  2           0 LOAD_GLOBAL              0 (range)
              3 LOAD_CONST               1 (100000)
              6 CALL_FUNCTION            1
              9 STORE_FAST               0 (a)

  3          12 LOAD_FAST                0 (a)
             15 SLICE+0             
             16 STORE_FAST               1 (b)
             19 LOAD_CONST               0 (None)
             22 RETURN_VALUE        

In [20]: dis.dis(func2)
  2           0 LOAD_GLOBAL              0 (range)
              3 LOAD_CONST               1 (100000)
              6 CALL_FUNCTION            1
              9 STORE_FAST               0 (a)

  3          12 LOAD_FAST                0 (a)    #same up to here
             15 LOAD_CONST               2 (0)    #loads 0
             18 LOAD_GLOBAL              1 (len) # loads the builtin len(),
                                                 # so it might take some lookup time
             21 LOAD_FAST                0 (a)
             24 CALL_FUNCTION            1         
             27 SLICE+3             
             28 STORE_FAST               1 (b)
             31 LOAD_CONST               0 (None)
             34 RETURN_VALUE        

我不能评论ruby计时和python计时。但是我可以对
列表
切片
进行评论。下面是字节码的快速检查:

>>> import dis
>>> a = range(10)
>>> def func(a):
...     return a[:]
... 
>>> def func2(a):
...     return list(a)
... 
>>> dis.dis(func)
  2           0 LOAD_FAST                0 (a)
              3 SLICE+0             
              4 RETURN_VALUE        
>>> dis.dis(func2)
  2           0 LOAD_GLOBAL              0 (list)
              3 LOAD_FAST                0 (a)
              6 CALL_FUNCTION            1
              9 RETURN_VALUE 
请注意,
list
需要一个
LOAD\u GLOBAL
来查找函数
list
。在python中查找全局函数(和调用函数)相对较慢。这就解释了为什么
a[0:len(a)]
也较慢。还要记住,
list
需要能够处理任意迭代器,而切片则不能。这意味着
list
需要分配一个新的列表,在列表迭代时将元素打包到该列表中,并在必要时调整其大小。这里有一些代价高昂的事情——必要时调整大小和迭代(在python中有效,而不是在C中)。使用切片方法,您可以计算所需内存的大小,这样就可以避免调整大小,并且迭代可以完全用C语言完成(可能使用
memcpy
或其他语言)

免责声明:我不是python开发人员,所以我不知道
list()
的内部是如何实现的。我只是根据我对规范的了解进行推测

EDIT——我已经查看了源代码(有Martijn的一些指导)。相关代码在中。
list
调用
list\u init
,然后在第799行调用
listextend
。该函数进行一些检查,看看如果对象是列表或元组,它是否可以使用快速分支(第812行).最后,从第834行开始进行起重:

 src = PySequence_Fast_ITEMS(b);
 dest = self->ob_item + m;
 for (i = 0; i < n; i++) {
     PyObject *o = src[i];
     Py_INCREF(o);
     dest[i] = o;
 }
它们几乎是相同的代码,因此对于大型列表,性能几乎相同也就不足为奇了(在大型列表中,解包切片、查找全局变量等小任务的开销变得不那么重要)


下面是我将如何运行python测试(以及我的Ubuntu系统的测试结果):


使用python来测量python执行时间。我怀疑它会使事情(更快)但它会避免所有常见的计时陷阱。至于
alist[:]
alist[0:len(alist)]之间的时差
;后者创建python
int
对象,这是前者方法不需要处理的。@MartijnPieters——后者还需要在每次
数组(a)时查找全局
len
(并调用它)
不复制数组。当给定一个数组时,它只调用
到数组上的
,返回
self
。你也应该使用,而不是手动计时。尝试Ruby和benchmark中的
obj.dup
。奇怪,但
列表(a)
在我的系统中是最快的。@AshwiniChaudhary——什么系统?这很奇怪。在我的OS-X系统和Ubuntu linux系统中,它是最慢的。@AshwiniChaudhary:你的查找是本地的,而在mgilson的情况下是全局的。如果mgilson在他的函数中添加
len=\uu builtins\uuuuu.len
,我打赌它也会更快。@MartijnPieters@MartijnPieters--Ashwini的查找是本地的吗?仅仅因为
a
是本地的,它不会使
len
local…(尽管你说得对,本地变量的查找速度比全局变量快…)。我怀疑他的测试的问题在于计时中包含了
range
。这可能会使他的结果对各种系统波动更加敏感…@mgilson:它在iPython外壳中,所以
locals()是globals()
True
range()
不包括在他的计时中,只包括在反汇编示例中。这肯定回答了我的问题,并表明n00b有多种方法可以编写低效的代码-即使在后一种时间,它的变体我的
复制
比您的实现慢得多。谢谢!;-@Laas很高兴能帮上忙:),其中哪一个是你系统中速度最快的?@Laas你是对的
copy()不是最快的,我的代码中有一个错误(忘了调用
cop`function in timeit)是的,我也得到了
a[:]
。我用我的时间更新了这个问题。
 src = PySequence_Fast_ITEMS(b);
 dest = self->ob_item + m;
 for (i = 0; i < n; i++) {
     PyObject *o = src[i];
     Py_INCREF(o);
     dest[i] = o;
 }
 src = a->ob_item + ilow;
 dest = np->ob_item;
 for (i = 0; i < len; i++) {
     PyObject *v = src[i];
     Py_INCREF(v);
     dest[i] = v;
 }
$ python -m timeit -s 'a=range(30)' 'list(a)'
1000000 loops, best of 3: 0.39 usec per loop
$ python -m timeit -s 'a=range(30)' 'a[:]'
10000000 loops, best of 3: 0.183 usec per loop
$ python -m timeit -s 'a=range(30)' 'a[0:len(a)]'
1000000 loops, best of 3: 0.254 usec per loop