Медленные побитовые операции

Я работаю над библиотекой Python, которая выполняет много побитовых операций над длинными битовыми строками, и я хочу найти битовый тип строки, который максимизирует его скорость. Я попробовал встроенный тип Python int, numpy, bitstring и bitarray, и удивительно, что Python int выигрывает, когда дело доходит до побитовых операций. Все, что я гуглил, говорит, что 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 сравнимы с векторными операциями над целыми битовыми массивами. 10 000-битное значение Python int/long, если рассматривать его как битовую маску (используя оператор & точно так же, как мы можем сделать с int или long в C/C++), напрямую сопоставимо с массивом numy bool длиной 10 000, потому что они оба содержат одинаковое количество битов, хотя и представлены двумя разными способами. То же самое верно и для других способов представления 10000 битов, которые я пробовал, включая использование массивов битовых упаковок, массивов int и типов битовых массивов / строк из других библиотек. Все они сравнимы, потому что все они вычисляют одну и ту же функцию на одних и тех же последовательностях битов. Все, что здесь имеет значение, это то, что я могу представить все 10000 бит и что я могу выполнять побитовые операции над ними. Если кто-то может предложить более эффективный способ представления длинных последовательностей битов фиксированной длины, который позволяет использовать побитовые операторы (&, | и ~), это то, что я ищу.

Если вы по-прежнему не понимаете, как значение Python int/long может хранить ту же информацию, что и массив numpy bool или массив int с двоичными значениями, обратитесь к int_to_bits функция в коде выше; он демонстрирует, как извлечь биты из Python int/long, что показывает, что выполнение операции & на двух 10 000-битных целых числах по сути аналогично выполнению его поэлементно в списке или массиве из 10 000 логических значений.

2 ответа

Решение

Насколько я могу судить, встроенный Python 3 int это единственный из протестированных вами вариантов, который вычисляет & кусками более одного байта за раз. (Я не совсем понял, что все в исходном коде NumPy для этой операции делает, но не похоже, что он имеет оптимизацию для вычисления этого в блоках больше, чем dtype.)

  • bitarray идет побайтово,
  • попытки bool и 1-bit-per-int NumPy идут постепенно,
  • упакованная попытка NumPy идет побайтово, и
  • bitstring Источник идет побайтово, а также делает некоторые вещи, которые мешают его попыткам набрать скорость через Cython, делая его, безусловно, самым медленным.

В отличие от int операция выполняется с использованием 15-битных или 30-битных цифр, в зависимости от значения параметра времени компиляции PYLONG_BITS_IN_DIGIT, Я не знаю, какая настройка по умолчанию.

Вы можете ускорить попытку NumPy, используя упакованное представление и больший тип d. Похоже, на моей машине 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 операции, и там победит простой python, потому что он не должен настраивать массивы или битовые массивы.

Как насчет попробовать следующее?

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, итерирование по ним быстрее, чем итерирование по пустым массивам... немного противоречит интуиции. Не уверен почему.

Точно так же вы можете попробовать для цепочек битов и битрейров

Это то, что вы смотрите?

Другие вопросы по тегам