I am working on a Python library that performs a lot of bitwise operations on long bit strings, and I want to find a bit string type that will maximize its speed. I have tried the built-in Python int type, numpy, bitstring, and bitarray, and suprisingly, the Python ints seem to win hands down when it comes to bitwise operations. Everything I have googled says numpy should be much faster for vectorized operations like this. Am I using numpy wrong somehow? Is there another Python library I can use that actually improves on Python's built-in int type?
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)
Results:
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
EDIT:
There seems to be a lot of confusion about how single operations on Python ints/longs are comparable to vector operations on entire numpy bit arrays. A 10,000-bit Python int/long value, when treated as a bit mask (using the & operator just like we can do with ints or longs in C/C++) is directly comparable to a numpy bool array of length 10,000, because they both contain the same number of bits, albeit represented in 2 different ways. The same is true for the other ways of representing 10,000 bits that I tried, including using numpy packed bit arrays, numpy int arrays, and bit array/string types from other libraries. They are all comparable because they are all computing the same function on the same sequences of bits. All that matters here is that I can represent all 10,000 bits and that I can perform bitwise operations on them. If anyone can suggest a more efficient way to represent long, fixed-length sequences of bits that allows bitwise operators (&, |, and ~) to be used, that is what I am looking for.
If you're still confused as to how a Python int/long value can store the same information as a numpy bool array or a numpy binary-valued int array, please refer to the int_to_bits
function in the code above; it demonstrates how to extract the bits out of a Python int/long, which shows that performing the & operation on two 10,000-bit ints is fundamentally the same as performing it element-by-element on a list or array of 10,000 Boolean values.