为什么np.compress比布尔索引更快?

时间:2017-06-11 19:29:15

标签: python arrays numpy

内部np.compress做什么使得它比布尔索引更快?

在此示例中,compress的速度提高约20%,但节省的时间因a的大小和布尔数组True中的b值的数量而异},但在我的机器上compress总是更快。

import numpy as np

a = np.random.rand(1000000,4)
b = (a[:,0]>0.5)

%timeit a[b]
#>>> 10 loops, best of 3: 24.7 ms per loop
%timeit a.compress(b, axis=0)
#>>> 10 loops, best of 3: 20 ms per loop

documentation for boolean indexing

  

返回的是数据的副本,而不是用切片获取的视图

相反,compress docs

  

沿给定的轴返回选定的阵列切片"。

但是,使用method provided here来确定两个数组是否共享同一个数据缓冲区,这表明两个方法都不与其父a共享数据,我认为这两个方法都不返回实际的切片。 / p>

def get_data_base(arr):
    base = arr
    while isinstance(base.base, np.ndarray):
        base = base.base
    return base

def arrays_share_data(x, y):
    return get_data_base(x) is get_data_base(y) 

arrays_share_data(a, a.compress(b, axis=0))
#>>> False
arrays_share_data(a, a[b])
#>>> False

我只是好奇,因为我经常在工作中执行这些操作。我运行通过Anaconda安装的python 3.5.2,numpy v 1.11.1。

2 个答案:

答案 0 :(得分:1)

当沿一个轴选择的索引由布尔掩码的向量指定时,函数compress是花式索引的替代方法,
明显的速度增益是由于轴选择是预先指定的,而花式索引可用于对阵列进行任意选择,因此会导致性能损失。 这也是您所经历的变速增益的原因。

i = np.random.random_sample(n) < .5

b1 = a[i]
b2 = np.compress(i, a, axis=0)

%timeit a[i]
10 loops, best of 3: 59.8 ms per loop
%timeit np.compress(i, a, axis=0)
10 loops, best of 3: 24.1 ms per loop

答案 1 :(得分:1)

通过a.compress numpy上的多层函数调用跟踪github我到达

/numpy/core/src/multiarray/item_selection.c
PyArray_Compress(PyArrayObject *self, PyObject *condition, int axis,
             PyArrayObject *out)
    # various checks
    res = PyArray_Nonzero(cond);
    ret = PyArray_TakeFrom(self, PyTuple_GET_ITEM(res, 0), axis,
                       out, NPY_RAISE);

使用示例数组,compress与执行where获取索引数组相同,然后take

In [135]: a.shape
Out[135]: (1000000, 4)
In [136]: b.shape
Out[136]: (1000000,)
In [137]: a.compress(b, axis=0).shape
Out[137]: (499780, 4)
In [138]: a.take(np.nonzero(b)[0], axis=0).shape
Out[138]: (499780, 4)
In [139]: timeit a.compress(b, axis=0).shape
100 loops, best of 3: 14.3 ms per loop
In [140]: timeit a.take(np.nonzero(b)[0], axis=0).shape
100 loops, best of 3: 14.3 ms per loop

事实上,如果我在[]索引中使用这个索引数组,我会得到相似的时间:

In [141]: idx=np.where(b)[0]
In [142]: idx.shape
Out[142]: (499780,)
In [143]: timeit a[idx,:].shape
100 loops, best of 3: 14.6 ms per loop
In [144]: timeit np.take(a,idx, axis=0).shape
100 loops, best of 3: 9.9 ms per loop

np.take代码涉及更多,因为它包含clipwrap模式。

[]索引转换为__getitem__调用,并通过各种层。我没有追溯到代码变化很大,但我认为compress(或更确切地说take)只是采取更直接的方式来完成任务是安全的,因此获得适度的速度提升。速度差异为30-50%表明编译代码详细信息存在差异,而不是像views vs copies这样的主要内容,或者是针对编译的解释。