假设我有一个数组
from array import array
myarr = array('l', [1, 2, 3])
和变量:
myvar = 4
什么是创建新阵列的最快方法:
newarray = array('l', [1, 2, 3, 4])
您可以假设所有元素都是'long'类型
我尝试创建一个新数组并使用array.append()
不确定它是否最快。我在考虑使用memoryview
之类的:
malloc(4*sizeof(long))
但我不知道如何将较短的数组复制到memoryview的一部分。然后将最后一个元素插入到最后位置。
我是Cython的新手。谢谢你的帮助!
更新: 我比较了以下三种方法:
Cython: [100000次循环,每次循环最佳3:5.94μs]
from libc.stdlib cimport malloc
def cappend(long[:] arr, long var, size_t N):
cdef long[:] result = <long[:(N+1)]>malloc((N+1)*sizeof(long))
result.base[:N] = arr
result.base[N] = var
return result
数组: [1000000循环,最佳3:每循环1.21μs]
from array import array
import copy
def pyappend(arr, x):
result = copy.copy(arr)
result.append(x)
return result
列表追加: [1000000次循环,最佳3:480 ns /循环]
def pylistappend(lst, x):
result = lst[:]
result.append(x)
return result
有希望改进cython部分并击败阵列吗?
答案 0 :(得分:4)
Cython让我们更多地访问array.array
的内部而不是&#34;正常&#34; python,所以我们可以利用它来加速代码:
7
为您的小例子(通过消除大部分开销)。2
获得更大的输入。继续阅读以获取更多详情。
尝试针对如此小的输入优化函数有点不寻常,但并非没有(至少是理论上的)兴趣。
因此,让我们以您的职能作为基线开始:
a=array('l', [1,2,3])
%timeit pyappend(a, 8)
1.03 µs ± 10.4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
lst=[1,2,3]
%timeit pylistappend(lst, 8)
279 ns ± 6.03 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
我们必须意识到:我们测量的不是复制的成本,而是开销的成本(python解释器,调用函数等),例如a
是否有3或5没有区别元素:
a=array('l', range(5))
%timeit pyappend(a, 8)
1.03 µs ± 6.76 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
在数组版本中,我们有更多的开销,因为我们通过copy
模块进行间接,我们可以尝试消除它:
def pyappend2(arr, x):
result = array('l',arr)
result.append(x)
return result
%timeit pyappend2(a, 8)
496 ns ± 5.04 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
那更快。现在让我们使用cython - 这将消除翻译成本:
%%cython
def cylistappend(lst, x):
result = lst[:]
result.append(x)
return result
%%cython
from cpython cimport array
def cyappend(array.array arr, long long int x):
cdef array.array res = array.array('l', arr)
res.append(x)
return res
%timeit cylistappend(lst, 8)
193 ns ± 12.4 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
%%timeit cyappend(a, 8)
421 ns ± 8.08 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
list
的cython版本快约33%,array
快约10%。构造函数array.array()
需要一个可迭代的,但我们已经有array.array
,因此我们使用cpython
中的功能来访问array.array
对象的内部并改善情况一点:
%%cython
from cpython cimport array
def cyappend2(array.array arr, long long int x):
cdef array.array res = array.copy(arr)
res.append(x)
return res
%timeit cyappend2(a, 8)
305 ns ± 7.25 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
对于下一步,我们需要了解array.array
如何追加元素:通常it over-allocates,append()
已分摊费用O(1)
,但array.copy
之后新数组正好是所需的元素数,下一个append
调用重新分配。我们需要对其进行更改(有关所用函数的说明,请参阅here):
%%cython
from cpython cimport array
from libc.string cimport memcpy
def cyappend3(array.array arr, long long int x):
cdef Py_ssize_t n=len(arr)
cdef array.array res = array.clone(arr,n+1,False)
memcpy(res.data.as_voidptr, arr.data.as_voidptr, 8*n)#that is pretty sloppy..
res.data.as_longlongs[n]=x
return res
%timeit cyappend3(a, 8)
154 ns ± 1.34 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
与您的功能类似,内存过度分配,因此我们不再需要再拨打resize()
。现在我们比list
更快,几乎比原始python版本快7倍。
让我们比较更大数组大小的时间(a=array('l',range(1000))
,lst=list(range(1000))
,其中数据复制占用大部分时间:
pyappend 1.84 µs #copy-module is slow!
pyappend2 1.02 µs
cyappend 0.94 µs #cython no big help - we are copying twice
cyappend2 0.90 µs #still copying twice
cyappend3 0.43 µs #copying only once -> twice as fast!
pylistappend 4.09 µs # needs to increment refs of integers
cylistappend 3.85 µs # the same as above
现在,消除array.array
不必要的副本会给我们预期的因素2.
对于更大的数组(10000
元素),我们看到以下内容:
pyappend 6.9 µs #copy-module is slow!
pyappend2 4.8 µs
cyappend2 4.4 µs
cyappend3 4.4 µs
版本之间不再存在差异(如果丢弃慢速复制模块)。这样做的原因是array.array
对这么多元素的行为发生了变化:当复制它时会过度分配,从而避免在第一个append()
之后重新分配。
我们可以很容易地检查它:
b=array('l', array('l', range(10**3)))#emulate our functions
b.buffer_info()
[] (94481422849232, 1000)
b.append(1)
b.buffer_info()
[] (94481422860352, 1001) # another pointer address -> reallocated
...
b=array('l', array('l', range(10**4)))
b.buffer_info()
[](94481426290064, 10000)
b.append(33)
b.buffer_info()
[](94481426290064, 10001) # the same pointer address -> no reallocation!