给定一个 NumPy 数组 A,将 same 函数 f 应用到 every 细胞?
假设我们将 f(A(i,j)) 分配给 A(i,j)。函数 f 没有二进制输出,因此 mask(ing) 操作无济于事。
“明显的”双循环迭代(通过每个单元)是最佳解决方案吗?
您可以只 vectorize 该函数,然后在每次需要时将其直接应用于 Numpy 数组:
import numpy as np
def f(x):
return x * x + 3 * x - 2 if x > 0 else x * 5 + 8
f = np.vectorize(f) # or use a different name if you want to keep the original f
result_array = f(A) # if A is your Numpy array
在矢量化时直接指定显式输出类型可能会更好:
f = np.vectorize(f, otypes=[np.float])
我相信我找到了更好的解决方案。将函数更改为 python 通用函数的想法(参见 documentation),它可以在后台执行并行计算。
可以用 C 编写自己定制的 ufunc
,这肯定更有效,或者通过调用 np.frompyfunc
,这是内置的工厂方法。经过测试,这比 np.vectorize
更有效:
f = lambda x, y: x * y
f_arr = np.frompyfunc(f, 2, 1)
vf = np.vectorize(f)
arr = np.linspace(0, 1, 10000)
%timeit f_arr(arr, arr) # 307ms
%timeit f_arr(arr, arr) # 450ms
我还测试了更大的样本,并且改进是成比例的。有关其他方法的性能比较,请参阅this post
当 2d 数组(或 nd 数组)是 C 或 F 连续时,将函数映射到 2d 数组的任务实际上与将函数映射到 1d 数组的任务相同——我们只是必须以这种方式查看,例如通过 np.ravel(A,'K')
。
例如 here 讨论了一维阵列的可能解决方案。
但是,当二维数组的内存不连续时,情况会稍微复杂一些,因为如果以错误的顺序处理轴,则希望避免可能的缓存未命中。
Numpy 已经有一台机器可以以最佳顺序处理轴。使用这种机器的一种可能性是np.vectorize
。但是,numpy 在 np.vectorize
上的文档指出它“主要是为了方便,而不是为了性能” - 一个慢速 Python 函数仍然是一个慢速 Python 函数,伴随着整个相关的开销!另一个问题是其巨大的内存消耗 - 例如,请参见 SO-post。
当一个人想要一个 C 函数的性能但使用 numpy 的机器时,一个好的解决方案是使用 numba 来创建 ufunc,例如:
# runtime generated C-function as ufunc
import numba as nb
@nb.vectorize(target="cpu")
def nb_vf(x):
return x+2*x*x+4*x*x*x
它很容易击败 np.vectorize
,但同样的功能也可以作为 numpy-array 乘法/加法执行,即
# numpy-functionality
def f(x):
return x+2*x*x+4*x*x*x
# python-function as ufunc
import numpy as np
vf=np.vectorize(f)
vf.__name__="vf"
有关时间测量代码,请参阅此答案的附录:
https://i.stack.imgur.com/M32DI.png
Numba 的版本(绿色)比 python 函数(即 np.vectorize
)快大约 100 倍,这并不奇怪。但它也比 numpy 功能快 10 倍左右,因为 numbas 版本不需要中间数组,因此更有效地使用缓存。
虽然 numba 的 ufunc 方法在可用性和性能之间取得了很好的平衡,但它仍然不是我们能做的最好的。然而,没有灵丹妙药或最适合任何任务的方法 - 人们必须了解限制是什么以及如何减轻限制。
例如,对于超越函数(例如 exp
、sin
、cos
),numba 与 numpy 的 np.exp
相比没有任何优势(没有创建临时数组 - 加速的主要来源)。但是,我的 Anaconda 安装使用英特尔的 VML 来处理向量 bigger than 8192 - 如果内存不连续,它就无法做到这一点。所以最好将元素复制到一个连续的内存,以便能够使用英特尔的 VML:
import numba as nb
@nb.vectorize(target="cpu")
def nb_vexp(x):
return np.exp(x)
def np_copy_exp(x):
copy = np.ravel(x, 'K')
return np.exp(copy).reshape(x.shape)
为了比较的公平,我关闭了 VML 的并行化(见附录中的代码):
https://i.stack.imgur.com/CxNNv.png
可以看出,一旦 VML 启动,复制的开销就得到了补偿。然而,一旦数据变得对于 L3 缓存来说太大,优势就会变得很小,因为任务再次成为内存带宽限制。
另一方面,numba 也可以使用 Intel 的 SVML,如 this post 中所述:
from llvmlite import binding
# set before import
binding.set_option('SVML', '-vector-library=SVML')
import numba as nb
@nb.vectorize(target="cpu")
def nb_vexp_svml(x):
return np.exp(x)
并使用具有并行化的 VML 产生:
https://i.stack.imgur.com/VaxWB.png
numba 的版本开销较少,但对于某些大小,即使有额外的复制开销,VML 也能胜过 SVML——这并不奇怪,因为 numba 的 ufunc 没有并行化。
清单:
A.多项式函数的比较:
import perfplot
perfplot.show(
setup=lambda n: np.random.rand(n,n)[::2,::2],
n_range=[2**k for k in range(0,12)],
kernels=[
f,
vf,
nb_vf
],
logx=True,
logy=True,
xlabel='len(x)'
)
B. exp
的比较:
import perfplot
import numexpr as ne # using ne is the easiest way to set vml_num_threads
ne.set_vml_num_threads(1)
perfplot.show(
setup=lambda n: np.random.rand(n,n)[::2,::2],
n_range=[2**k for k in range(0,12)],
kernels=[
nb_vexp,
np.exp,
np_copy_exp,
],
logx=True,
logy=True,
xlabel='len(x)',
)
以上所有答案都比较好,但是如果您需要使用自定义函数进行映射,并且您有numpy.ndarray
,则需要保留数组的形状。
我只比较了两个,但它会保留 ndarray
的形状。我使用了包含 100 万个条目的数组进行比较。这里我使用平方函数。我正在介绍 n 维数组的一般情况。对于二维,只需将 iter
设为 2D。
import numpy, time
def A(e):
return e * e
def timeit():
y = numpy.arange(1000000)
now = time.time()
numpy.array([A(x) for x in y.reshape(-1)]).reshape(y.shape)
print(time.time() - now)
now = time.time()
numpy.fromiter((A(x) for x in y.reshape(-1)), y.dtype).reshape(y.shape)
print(time.time() - now)
now = time.time()
numpy.square(y)
print(time.time() - now)
输出
>>> timeit()
1.162431240081787 # list comprehension and then building numpy array
1.0775556564331055 # from numpy.fromiter
0.002948284149169922 # using inbuilt function
在这里您可以清楚地看到 numpy.fromiter
用户方功能,使用您的任何选择。如果您的函数依赖于作为数组索引的 i, j
,则迭代数组的大小,例如 for ind in range(arr.size)
,使用 numpy.unravel_index
根据您的一维索引和数组 numpy.unravel_index 的形状来获取 i, j, ..
这个答案的灵感来自我对其他问题 here 的回答
不定期副业成功案例分享
vectorize
函数描述中给出的警告:提供矢量化函数主要是为了方便,而不是为了性能。该实现本质上是一个 for 循环。 所以这很可能根本不会加速这个过程。vectorize
如何确定返回类型。这产生了错误。frompyfunc
快一点,但返回一个 dtype 对象数组。两者都提供标量,而不是行或列。np.vectorize
即可使我的速度提高约 20 倍。