慢按位操作

时间:2015-06-06 05:07:38

标签: python numpy bitwise-operators bitarray bitstring

我正在研究一个对长字符串执行大量按位操作的Python库,我想找到一个能够最大化其速度的位字符串类型。我已经尝试了内置的Python int类型,numpy,bitstringbitarray,令人惊讶的是,当涉及到按位操作时,Python int似乎赢了。我用google搜索的所有内容都说numpy对于像这样的矢量化操作要快得多。我是不是以某种方式使用了numpy错误?我可以使用另一个Python库,它实际上改进了Python的内置int类型吗?

from timeit import timeit
import random


size = 10000


def int_to_bits(i):
    result = []
    for _ in range(size):
        result.append(i % 2)
        i >>= 1
    return result



x = random.randrange(2**size)
y = random.randrange(2**size)

print(x.bit_length(), y.bit_length())

x_bits = int_to_bits(x)
y_bits = int_to_bits(y)

t = timeit(
    stmt='a & b',
    setup='a = %d; b = %d' % (x, y)
)
print("raw ints:", t)

t = timeit(
    stmt='a & b',
    setup=('import numpy;'
           'a = numpy.array(%r, dtype=int);'
           'b = numpy.array(%r, dtype=int)') % (x_bits, y_bits)
)
print('numpy int array:', t)

t = timeit(
    stmt='a & b',
    setup=('import numpy;'
           'a = numpy.array(%r, dtype=bool);'
           'b = numpy.array(%r, dtype=bool)') % (x_bits, y_bits)
)
print('numpy bool array:', t)

t = timeit(
    stmt='a & b',
    setup=('import numpy;'
           'a = numpy.packbits(%r);'
           'b = numpy.packbits(%r)') % (x_bits, y_bits)
)
print('numpy packed bits:', t)

t = timeit(
    stmt='a & b',
    setup=('import bitstring;'
           'a = bitstring.BitString(%r);'
           'b = bitstring.BitString(%r)') % (x_bits, y_bits)
)
print('bitstring:', t)

t = timeit(
    stmt='a & b',
    setup=('import bitarray;'
           'a = bitarray.bitarray(%r);'
           'b = bitarray.bitarray(%r)') % (x_bits, y_bits)
)
print('bitarray:', t)

结果:

10000 10000
raw ints: 0.29606562735373115
numpy int array: 7.400762747057885
numpy bool array: 1.1108355715984288
numpy packed bits: 1.3064737574273284
bitstring: 380.9796937642803
bitarray: 1.4451143449501842

修改

对于Python ints / longs上的单个操作如何与整个numpy位数组上的向量操作相比,似乎存在很多混淆。一个10,000位的Python int / long值,当被视为一个位掩码时(使用&运算符就像我们可以用int或C / C ++中的long一样)可以直接比作长度为10,000的numpy bool数组,因为它们两者都包含相同数量的位,尽管以2种不同的方式表示。对于我尝试的表示10,000位的其他方式也是如此,包括使用numpy压缩位数组,numpy int数组和其他库中的位数组/字符串类型。它们都是可比较的,因为它们都在相同的位序列上计算相同的功能。这里最重要的是我可以表示所有10,000位,并且我可以对它们执行按位操作。如果任何人都可以建议一种更有效的方式来表示允许使用按位运算符(&,|和〜)的长,固定长度的位序列,那就是我正在寻找的。

如果您仍然对Python int / long值如何存储与numpy bool数组或numpy二进制值int数组相同的信息感到困惑,请参阅int_to_bits函数in上面的代码;它演示了如何从Python int / long中提取位,这表明执行&对两个10,000位整数的操作基本上与在10,000个布尔值的列表或数组上逐个元素执行操作相同。

2 个答案:

答案 0 :(得分:7)

据我所知,内置的Python 3 int是您测试的唯一一个选项,它一次计算多个字节的块中的&。 (我还没有完全弄明白这个操作NumPy source中的所有内容是什么,但它看起来没有优化来计算这个比dtype更大的块。)

  • bitarray按字节逐行,
  • bool和1-bit-per-int NumPy尝试一点一点地进行,
  • 打包的NumPy尝试逐字节地进行,
  • bitstring源逐字节,以及做一些搞砸了通过Cython获得速度的尝试,使它成为最慢的。

相反,int操作由15位或30位数字组成,具体取决于the compile-time parameter PYLONG_BITS_IN_DIGIT的值。我不知道哪个设置是默认设置。

您可以使用压缩表示和更大的dtype来加速NumPy尝试。看起来在我的机器上,一个32位的dtype工作速度最快,击败Python整数;我不知道你的设置是什么样的。使用每种格式的10240位值进行测试,我得到

>>> timeit.timeit('a & b', 'import numpy; a = b = numpy.array([0]*160, dtype=num
py.uint64)')
1.3918750826524047
>>> timeit.timeit('a & b', 'import numpy; a = b = numpy.array([0]*160*8, dtype=n
umpy.uint8)')
1.9460716604953632
>>> timeit.timeit('a & b', 'import numpy; a = b = numpy.array([0]*160*2, dtype=n
umpy.uint32)')
1.1728465435917315
>>> timeit.timeit('a & b', 'a = b = 2**10240-1')
1.5999407862400403

答案 1 :(得分:0)

您要测试的是这些矢量操作吗?你只是试图比较1个操作的速度,并且普通的python将赢得'因为它不需要设置numpy数组或bitarrays。

尝试以下怎么样?

x = np.array([random.randrange(2**31)]*1000) 
y = np.array([random.randrange(2**31)]*1000) 

%timeit x & y # in ipython

%timeit [ a & b for (a,b) in zip(x,y)] # even though x and y are numpy arrays, we are iterating over them - and not doing any vector operations

有趣的是,如果

xxx = [random.randrange(2**31)] * 1000
yyy = [random.randrange(2**31)] * 1000 

然后

%timeit [a & b for (a,b) in zip(xxx,yyy)]

纯python列表,迭代它们比迭代numpy数组更快..有点反直觉。不知道为什么。

类似地,您可以尝试位串和位阵列

这是你在看什么?