给定NumPy数组 A ,应用相同函数 f 的最快/最有效方法是什么? 每个单元格?
假设我们将分配给 A(i,j) f(A(i,j))。
函数 f 没有二进制输出,因此掩码(ing)操作无效。
“明显的”双循环迭代(通过每个单元格)是最优解吗?
答案 0 :(得分:159)
您可以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])
答案 1 :(得分:5)
类似的问题是:Mapping a NumPy array in place。 如果你能为你的f()找到ufunc,那么你应该使用out参数。
答案 2 :(得分:1)
如果您正在处理数字和f(A(i,j)) = f(A(j,i))
,则可以使用scipy.spatial.distance.cdist将f定义为A(i)
和A(j)
之间的距离。
答案 3 :(得分:0)
我相信我找到了更好的解决方案。将函数更改为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
答案 4 :(得分:0)
当2d数组(或nd数组)为C或F连续时,将函数映射到2d数组的任务实际上与将函数映射到1d数组的任务相同-我们只需要这样看,例如通过np.ravel(A,'K')
。
例如here中已经讨论了1维阵列的可能解决方案。
但是,当2d数组的内存不连续时,情况会稍微复杂一些,因为如果轴处理顺序错误,则希望避免可能发生的高速缓存未命中。
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"
有关时间测量代码,请参见此答案的附录:
Numba的版本(绿色)比python函数(即np.vectorize
)快约100倍,这并不奇怪。但这也比numpy功能快10倍,因为numbas版本不需要中间数组,因此可以更有效地使用缓存。
虽然numba的ufunc方法是可用性和性能之间的良好折衷,但这仍然不是我们能做的最好的选择。然而,没有灵丹妙药或最佳方法来完成任何任务-人们必须了解什么是局限性以及如何缓解这些局限性。
例如,对于先验函数(例如exp
,sin
,cos
),numba与numpy的np.exp
相比没有任何优势(没有创建临时数组) -提速的主要来源)。但是,我的Anaconda安装使用的是向量bigger than 8192的Intel VML-如果内存不连续,则无法执行。因此,最好将元素复制到连续内存中,以便能够使用英特尔的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的并行化功能(请参见附录中的代码):
正如人们所看到的那样,一旦VML启动,复制的开销将得到补偿。但是,一旦数据对于L3高速缓存而言变得太大,则优势就变得微不足道了,因为任务再次变得与内存带宽绑定。
另一方面,numba也可以使用英特尔的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:
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)',
)
答案 5 :(得分:0)
以上所有答案都比较好,但是如果您需要使用自定义函数进行映射,并且您有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
来获取i, j, ..
数组numpy.unravel_index
此答案是由我对其他问题here的回答所启发