Есть ли эффективный способ получить массив логических значений, которые находятся в n-й позиции в побитовом массиве в Python?
import numpy as np
array = np.array(
[
[1, 0, 1],
[1, 1, 1],
[0, 0, 1],
]
)
pack_array = np.packbits(array, axis=1)
array([0, 1, 0])
Я пробовал numba со следующей функцией. Он возвращает правильные результаты, но очень медленно:
import numpy as np
from numba import njit
@njit(nopython=True, fastmath=True)
def getVector(packed, j):
n = packed.shape[0]
res = np.zeros(n, dtype=np.int32)
for i in range(n):
res[i] = bool(packed[i, j//8] & (128>>(j%8)))
return res
Как это проверить?
import numpy as np
import time
from numba import njit
array = np.random.choice(a=[False, True], size=(100000000,15))
pack_array = np.packbits(array, axis=1)
start = time.time()
array[:,10]
print('np array')
print(time.time()-start)
@njit(nopython=True, fastmath=True)
def getVector(packed, j):
n = packed.shape[0]
res = np.zeros(n, dtype=np.int32)
for i in range(n):
res[i] = bool(packed[i, j//8] & (128>>(j%8)))
return res
# To initialize
getVector(pack_array, 10)
start = time.time()
getVector(pack_array, 10)
print('getVector')
print(time.time()-start)
Он возвращает:
np array
0.00010132789611816406
getVector
0.15648770332336426
Помимо оператора по модулю, я не могу представить, чтобы эта реализация была очень медленной в вычислительном отношении и не привязанной к памяти.
Подход numpy
- это O (1), вы не можете использовать его в качестве основы. Он просто возвращает представление с скорректированными шагами без каких-либо вычислений. Результат тайминга должен доминировать над вызовом print
.
@MichaelSzczesny Можно ли использовать шаги для индексации внутри байта? Из чтения исходного кода numpy кажется, что packbits — это просто цикл по массиву.
Удивительно, но LLVM
не оптимизирует очевидные константы внутри цикла (на моей машине). ~ в 3,5 раза быстрее после перемещения их за пределы цикла.
Помимо некоторых микрооптимизаций, я не думаю, что здесь можно многое оптимизировать. В вашем коде также есть несколько небольших ошибок:
Мой обновленный код (видя скудное увеличение производительности на 40% на моей машине):
import numba as nb
import numpy as np
np.random.seed(0)
array = np.random.choice(a=[False, True], size=(10000000,15))
pack_array = np.packbits(array, axis=1)
@nb.njit(locals = {'res': nb.boolean[:]})
def getVector(packed, j):
n = packed.shape[0]
res = np.zeros(n, dtype=nb.boolean)
byte = j//8
bit = 128>>(j%8)
for i in range(n):
res[i] = bool(packed[i, byte] & bit)
return res
getVector(pack_array, 10)
В вашем ответе «res» представляет собой список 32-битных целых чисел, предоставив np.zeros() логический тип данных numba (НЕ numpy), мы можем поменять его на более эффективные логические значения. Именно отсюда происходит большая часть улучшения производительности. На моей машине размещение j_mod и j_flr вне цикла не дало заметного эффекта. Но это оказало влияние на комментатора @Michael Szczesny, так что это может помочь и вам.
Я бы не стал использовать шаги, которые предлагает @Nick ODell, потому что они могут быть довольно опасными при неправильном использовании. (См. документацию numpy).
edit: я внес несколько небольших изменений, предложенных Майклом. (Спасибо)
Pure numpy
(pack_array[:, j // 8] & 128>>(j%8)).astype(bool)
примерно в 2 раза быстрее оригинальной реализации.
Я думаю, function2
следует называть getVector
, не так ли?
@ Жером Ричард, ты прав, я исправил это сейчас
Вы можете вычислить
j//8
и128>>(j%8)
один раз вне цикла, создав res какnp.empty
(используяdtype=np.bool
?). Но это только микрооптимизации, которые, возможно, уже были сделаны компилятором.