numpy.all和按位'&'之间的二进制序列比较

时间:2018-09-19 20:08:46

标签: python numpy bit-manipulation bitwise-operators

我在两个相同长度的大二进制序列之间执行按位'&'时遇到问题,我需要找到出现1的索引。

我用numpy做到了,这是我的代码:

>>> c = numpy.array([[0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1],[0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1]]) #initialize 2d array
>>> c = c.all(axis=0)
>>> d = numpy.where(c)[False] #returns indices

我检查了时间。

>>> print("Time taken to perform 'numpy.all' : ",timeit.timeit(lambda :c.all(axis=0),number=10000))
>>> Time taken to perform 'numpy.all' :  0.01454929300234653

此操作比我预期的要慢。

然后,作为比较,我执行了一个基本的按位'&'操作:

>>> print("Time taken to perform bitwise & :",timeit.timeit('a = 0b0000000001111111111100000001111111111; b = 0b0000000001111111111100000001111111111; c = a&b',number=10000))

>>> Time taken to perform bitwise & : 0.0004252859980624635

这比numpy快

我使用numpy是因为它允许在具有1的位置查找索引,但是numpy.all运算符要慢得多。

与第一种情况一样,我的原始数据将是数组列表。如果我将此列表转换为二进制数,然后像第二种情况一样执行计算,会不会有任何用途?

3 个答案:

答案 0 :(得分:1)

我认为您无法超越a&b的速度(实际计算只是一堆基本的CPU操作,我很确定您的timeit的结果> 99%高架)。例如:

>>> from timeit import timeit
>>> import numpy as np
>>> import random
>>> 
>>> k = 2**17-2
>>> a = random.randint(0, 2**k-1) + 2**k
>>> b = random.randint(0, 2**k-1) + 2**k
>>> timeit('a & b', globals=globals())
2.520026927930303

这是> 100k位,只需要约2.5 us。

无论如何,&的成本将与生成索引列表或索引数组的成本相形见。

numpy本身就具有大量开销,因此对于像您这样的简单操作,需要检查它是否值得。

所以让我们先尝试一个纯python解决方案:

>>> c = a & b
>>> timeit("[x for x, y in enumerate(bin(c), -2) if y=='1']", globals=globals(), number=1000)
7.905808186973445

大约8毫秒,并且比&操作要高几个数量级。

numpy怎么样?

让我们首先移动列表理解:

>>> timeit("np.where(np.fromstring(bin(c), np.uint8)[2:] - ord('0'))[0]", globals=globals(), number=1000)
1.0363857130287215

因此,在这种情况下,我们的速度提高了约8倍。如果我们要求结果为列表,则缩小到约4倍:

>>> timeit("np.where(np.fromstring(bin(c), np.uint8)[2:] - ord('0'))[0].tolist()", globals=globals(), number=1000)
1.9008758360287175

我们还可以让numpy进行二进制转换,这会带来另一个小的加速:

>>> timeit("np.where(np.unpackbits(np.frombuffer(c.to_bytes(k//8+1, 'big'), np.uint8))[1:])[0]", globals=globals(), number=1000)
0.869781385990791

总结:

  • numpy并不总是更快,最好将&留给纯Python
  • 在numpy中定位非零位似乎足够快,可以抵消列表和数组之间的转换成本

请注意,所有这些都带有警告,我的纯Python代码不一定是最佳的。例如,使用查找表,我们可以更快一些:

>>> lookup = [(np.where(i)[0]-1).tolist() for i in np.ndindex(*8*[2])]
>>> timeit("[(x<<3) + z for x, y in enumerate(c.to_bytes(k//8+1, 'big')) for z in lookup[y]]", globals=globals(), number=1000)
4.687953414046206

答案 1 :(得分:0)

>>> c = numpy.random.randint(2, size=(2, 40)) #initialize
>>> c
array([[1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1,
        0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0],
       [1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
        0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1]])

访问此选项会给您两个速度下降:

  • 您必须访问两行,而按位测试的常数很容易在寄存器中使用。
  • 您正在执行一系列40次and操作,其中可能包括从完整整数到布尔值的转换。

您严重阻碍了all测试;结果就不足为奇了。

答案 2 :(得分:0)

观察到的因素是以下事实的直接结果:c=numpy.array([[0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1],[0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1]])int上的数组,而int是以32位编码的

因此,当您使用c.all()时,您正在对37 * 32 = 1184位进行操作

但是a = 0b0000000001111111111100000001111111111由37位组成,因此当您进行a&b时,操作是在37位上。

因此,使用numpy数组的成本要高32倍。

让我们测试一下

import timeit
import numpy as np
print("Time taken to perform bitwise & :",timeit.timeit('a=0b0000000001111111111100000001111111111; b = 0b0000000001111111111100000001111111111; c = a&b',number=320000))
a = 0b0000000001111111111100000001111111111
b = 0b0000000001111111111100000001111111111
c=np.array([a,b])
print("Time taken to perform 'numpy.all' : ",timeit.timeit(lambda :c.all(axis=0),number=10000))

我执行&操作320000次,all()操作10000次。

Time taken to perform bitwise & : 0.01527938833025152
Time taken to perform 'numpy.all' :  0.01583387375572265

是同一回事!

现在回到您最初的问题,您想知道大二进制数中位为1的索引。

也许您可以尝试bitarray模块提供的功能

a = bitarray.bitarray('0000000001111111111100000001111111111')
b = bitarray.bitarray('0000000001111111111100000001111111111')
i=0
data = list()
for c in a&b:
    if(c):
        data.append(i)
    i=i+1
print (data)

输出

[9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36]