布尔数组上 numpy.bitwise_and 的 numba 较慢
numba slower for numpy.bitwise_and on boolean arrays
我正在这段代码片段中尝试 numba
from numba import jit
import numpy as np
from time import time
db = np.array(np.random.randint(2, size=(400e3, 4)), dtype=bool)
out = np.zeros((int(400e3), 1))
@jit()
def check_mask(db, out, mask=[1, 0, 1]):
for idx, line in enumerate(db):
target, vector = line[0], line[1:]
if (mask == np.bitwise_and(mask, vector)).all():
if target == 1:
out[idx] = 1
return out
st = time()
res = check_mask(db, out, [1, 0, 1])
print 'with jit: {:.4} sec'.format(time() - st)
使用 numba @jit() 装饰器此代码 运行 更慢!
- 没有 jit:3.16 秒
- 使用 jit:3.81 秒
只是为了帮助更好地理解这段代码的用途:
db = np.array([ # out value for mask = [1, 0, 1]
# target, vector #
[1, 1, 0, 1], # 1
[0, 1, 1, 1], # 0 (fit to mask but target == 0)
[0, 0, 1, 0], # 0
[1, 1, 0, 1], # 1
[0, 1, 1, 0], # 0
[1, 0, 0, 0], # 0
])
我建议从内部循环中删除对 array_equal 的 numpy 调用。 numba 不一定聪明到可以将它变成一段内联 C;如果它无法替换此调用,您的函数的主要成本仍然具有可比性,这将解释您的结果。
虽然 numba 可以推断出相当数量的 numpy 结构,但只有 C-style 作用于 numpy 数组的代码可能依赖于加速。
Numba 为jit
提供了两种编译模式:nopython 模式和对象模式。 Nopython 模式(默认)仅支持一组有限的 Python 和 Numpy 功能,请参阅 the docs for your version。如果 jitted 函数包含不受支持的代码,Numba 必须退回到对象模式,这要慢得多。
我不确定与纯 Python 相比,objcet 模式是否应该提供加速,但无论如何您总是希望使用 nopython 模式。为确保使用 nopython 模式,请指定 nopython=True
并坚持非常基本的代码(经验法则:写出所有循环并仅使用标量和 Numpy 数组):
@jit(nopython=True)
def check_mask_2(db, out, mask=np.array([1, 0, 1])):
for idx in range(db.shape[0]):
if db[idx,0] != 1:
continue
check = 1
for j in range(db.shape[1]):
if mask[j] and not db[idx,j+1]:
check = 0
break
out[idx] = check
return out
显式写出内循环还有一个好处,就是一旦条件不成立,我们就可以跳出。
时间安排:
%time _ = check_mask(db, out, np.array([1, 0, 1]))
# Wall time: 1.91 s
%time _ = check_mask_2(db, out, np.array([1, 0, 1]))
# Wall time: 310 ms # slow because of compilation
%time _ = check_mask_2(db, out, np.array([1, 0, 1]))
# Wall time: 3 ms
顺便说一句,这个函数也很容易用 Numpy 向量化,这提供了不错的速度:
def check_mask_vectorized(db, mask=[1, 0, 1]):
check = (db[:,1:] == mask).all(axis=1)
out = (db[:,0] == 1) & check
return out
%time _ = check_mask_vectorized(db, [1, 0, 1])
# Wall time: 14 ms
或者,您可以尝试 Pythran
(免责声明:我是 Pythran 的开发人员)。
使用一个注解,编译如下代码
#pythran export check_mask(bool[][], bool[])
import numpy as np
def check_mask(db, out, mask=[1, 0, 1]):
for idx, line in enumerate(db):
target, vector = line[0], line[1:]
if (mask == np.bitwise_and(mask, vector)).all():
if target == 1:
out[idx] = 1
return out
呼叫 pythran check_call.py
。
并且根据 timeit
,生成的本机模块运行得相当快:
python -m timeit -s 'n=1e4; import numpy as np; db = np.array(np.random.randint(2, size=(n, 4)), dtype=bool); out = np.zeros(int(n), dtype=bool); from eq import check_mask' 'check_mask(db, out)'
告诉我 CPython 版本在 136ms
中运行,而 Pythran-compiled 版本在 450us
中运行。
我正在这段代码片段中尝试 numba
from numba import jit
import numpy as np
from time import time
db = np.array(np.random.randint(2, size=(400e3, 4)), dtype=bool)
out = np.zeros((int(400e3), 1))
@jit()
def check_mask(db, out, mask=[1, 0, 1]):
for idx, line in enumerate(db):
target, vector = line[0], line[1:]
if (mask == np.bitwise_and(mask, vector)).all():
if target == 1:
out[idx] = 1
return out
st = time()
res = check_mask(db, out, [1, 0, 1])
print 'with jit: {:.4} sec'.format(time() - st)
使用 numba @jit() 装饰器此代码 运行 更慢!
- 没有 jit:3.16 秒
- 使用 jit:3.81 秒
只是为了帮助更好地理解这段代码的用途:
db = np.array([ # out value for mask = [1, 0, 1]
# target, vector #
[1, 1, 0, 1], # 1
[0, 1, 1, 1], # 0 (fit to mask but target == 0)
[0, 0, 1, 0], # 0
[1, 1, 0, 1], # 1
[0, 1, 1, 0], # 0
[1, 0, 0, 0], # 0
])
我建议从内部循环中删除对 array_equal 的 numpy 调用。 numba 不一定聪明到可以将它变成一段内联 C;如果它无法替换此调用,您的函数的主要成本仍然具有可比性,这将解释您的结果。
虽然 numba 可以推断出相当数量的 numpy 结构,但只有 C-style 作用于 numpy 数组的代码可能依赖于加速。
Numba 为jit
提供了两种编译模式:nopython 模式和对象模式。 Nopython 模式(默认)仅支持一组有限的 Python 和 Numpy 功能,请参阅 the docs for your version。如果 jitted 函数包含不受支持的代码,Numba 必须退回到对象模式,这要慢得多。
我不确定与纯 Python 相比,objcet 模式是否应该提供加速,但无论如何您总是希望使用 nopython 模式。为确保使用 nopython 模式,请指定 nopython=True
并坚持非常基本的代码(经验法则:写出所有循环并仅使用标量和 Numpy 数组):
@jit(nopython=True)
def check_mask_2(db, out, mask=np.array([1, 0, 1])):
for idx in range(db.shape[0]):
if db[idx,0] != 1:
continue
check = 1
for j in range(db.shape[1]):
if mask[j] and not db[idx,j+1]:
check = 0
break
out[idx] = check
return out
显式写出内循环还有一个好处,就是一旦条件不成立,我们就可以跳出。
时间安排:
%time _ = check_mask(db, out, np.array([1, 0, 1]))
# Wall time: 1.91 s
%time _ = check_mask_2(db, out, np.array([1, 0, 1]))
# Wall time: 310 ms # slow because of compilation
%time _ = check_mask_2(db, out, np.array([1, 0, 1]))
# Wall time: 3 ms
顺便说一句,这个函数也很容易用 Numpy 向量化,这提供了不错的速度:
def check_mask_vectorized(db, mask=[1, 0, 1]):
check = (db[:,1:] == mask).all(axis=1)
out = (db[:,0] == 1) & check
return out
%time _ = check_mask_vectorized(db, [1, 0, 1])
# Wall time: 14 ms
或者,您可以尝试 Pythran (免责声明:我是 Pythran 的开发人员)。
使用一个注解,编译如下代码
#pythran export check_mask(bool[][], bool[])
import numpy as np
def check_mask(db, out, mask=[1, 0, 1]):
for idx, line in enumerate(db):
target, vector = line[0], line[1:]
if (mask == np.bitwise_and(mask, vector)).all():
if target == 1:
out[idx] = 1
return out
呼叫 pythran check_call.py
。
并且根据 timeit
,生成的本机模块运行得相当快:
python -m timeit -s 'n=1e4; import numpy as np; db = np.array(np.random.randint(2, size=(n, 4)), dtype=bool); out = np.zeros(int(n), dtype=bool); from eq import check_mask' 'check_mask(db, out)'
告诉我 CPython 版本在 136ms
中运行,而 Pythran-compiled 版本在 450us
中运行。