Python 过滤NumPy数组:最佳方法是什么?
假设我有一个NumPy数组Python 过滤NumPy数组:最佳方法是什么?,python,numpy,cython,numba,Python,Numpy,Cython,Numba,假设我有一个NumPy数组arr,我想对它进行元素过滤,例如。 我只想得到低于某个阈值k的值 有几种方法,例如: 使用生成器:np.fromiter((如果x
arr
,我想对它进行元素过滤,例如。
我只想得到低于某个阈值k
的值
有几种方法,例如:
np.fromiter((如果x
arr[arr
np.where()
:arr[np.where(arr
np.nonzero()
:arr[np.nonzero(arr
(编辑:根据@ShadowRanger注释添加了
np.nonzero()
)定义
def过滤器屏蔽(arr,k):
返回arr[arr
np.where()
:def过滤器,其中(arr,k):
返回arr[np.where(arr
np.nonzero()
- 单程
filter\u cy()
- 两次通过
filter2\u cy()
时间基准 基于生成器的
filter\u fromiter()
方法比其他方法慢得多(大约2个数量级,因此在图表中省略)
计时将取决于输入数组大小和过滤项的百分比
作为输入大小的函数
第一个图表说明了作为输入大小函数的计时(对于约50%过滤掉的元素):
一般来说,基于Numba的方法始终是最快的,紧随其后的是Cython方法。其中,对于中等和较大的输入,双通道方法最快。在NumPy中,基于np.where()
的方法和基于np.nonzero()
的方法基本相同(除了非常小的输入,np.nonzero()
似乎稍微慢一点),它们都比布尔掩码切片快,除了非常小的输入(低于~100个元素)其中布尔掩码切片速度更快。
此外,对于非常小的输入,基于Cython的解决方案比基于NumPy的解决方案慢
作为填充的函数
第二个图表将计时作为通过过滤器的项目的函数(对于约100万个元素的固定输入大小):
第一个观察结果是,当接近50%填充时,所有方法都是最慢的,填充更少或更多时,它们的速度更快,而不填充的速度最快(滤出值的最高百分比,通过值的最低百分比,如图的x轴所示)。
同样,Numba和Cython版本通常都比基于NumPy的版本快,Numba几乎总是最快的,Cython在图的最右边部分胜过了Numba。
值得注意的例外是,当填充接近100%时,单通道Numba/Cython版本基本上复制了大约两次,布尔掩码切片解决方案最终优于它们。
对于较大的填充阀,两次通过的方法具有增加的边际速度增益。
在NumPy中,基于np.where()
的方法和基于np.nonzero()
的方法基本相同。
在比较基于NumPy的解决方案时,np.where()
/np.nonzero()
解决方案的性能几乎总是优于布尔掩码切片,图的最右外部分除外,布尔掩码切片变得最快
(完整代码可用)
内存注意事项 基于生成器的
filter\u fromiter()
方法只需要最小的临时存储,与输入大小无关。
内存方面,这是最有效的方法。
Cython/Numba两次传递方法具有相似的内存效率,因为输出的大小是在第一次传递期间确定的
在内存方面,Cython和Numba的单通道解决方案都需要输入大小的临时数组。
因此,这些是内存效率最低的方法
布尔掩码切片解决方案需要输入大小的临时数组,但类型为bool
,在NumPy中为1位,因此这比典型64位系统上NumPy数组的默认大小小约64倍
基于np.where()。因此,第二步根据过滤元素的数量具有可变的内存需求
评论
- 在指定不同的过滤条件时,生成器方法也是最灵活的
- Cython解决方案需要指定数据类型以使其快速
- 对于Numba和Cython,可以将过滤条件指定为通用函数(因此不需要硬编码),但必须在其各自的环境中指定过滤条件,并且必须注意确保正确编译过滤条件以提高速度,否则会出现严重的减速
- 单程解决方案在返回之前需要额外的
.copy()
,以避免浪费内存
- NumPy方法不返回输入视图,而是返回一个副本,这是由于:
arr=np.arange(100)
k=50
print('arr[arr>k]`是副本:',arr[arr>
%%cython -c-O3 -c-march=native -a
#cython: language_level=3, boundscheck=False, wraparound=False, initializedcheck=False, cdivision=True, infer_types=True
cimport numpy as cnp
cimport cython as ccy
import numpy as np
import cython as cy
cdef long NUM = 1048576
cdef long MAX_VAL = 1048576
cdef long K = 1048576 // 2
cdef int smaller_than_cy(long x, long k=K):
return x < k
cdef size_t _filter_cy(long[:] arr, long[:] result, size_t size, long k):
cdef size_t j = 0
for i in range(size):
if smaller_than_cy(arr[i]):
result[j] = arr[i]
j += 1
return j
cpdef filter_cy(arr, k):
result = np.empty_like(arr)
new_size = _filter_cy(arr, result, arr.size, k)
return result[:new_size].copy()
cdef size_t _filtered_size(long[:] arr, size_t size, long k):
cdef size_t j = 0
for i in range(size):
if smaller_than_cy(arr[i]):
j += 1
return j
cpdef filter2_cy(arr, k):
cdef size_t new_size = _filtered_size(arr, arr.size, k)
result = np.empty(new_size, dtype=arr.dtype)
new_size = _filter_cy(arr, result, arr.size, k)
return result