使用 numpy 数组进行代码优化。有更好的解决方案吗?

Posted

技术标签:

【中文标题】使用 numpy 数组进行代码优化。有更好的解决方案吗?【英文标题】:Code optimisation using numpy array. Is there a better solution? 【发布时间】:2022-01-16 12:49:06 【问题描述】:

我正在尝试优化一个计算函数:a*exp(b*x)+c

我使用 numpy 数组测试了三种方法:

def model(a,b,c,x):
    return a*np.exp(b*x)+c
def myFoo1(modelParam,x):
    return([model(*i,x) for i in modelParam])

def myFoo2(modelParam,x):
    return([i[0]*np.exp(i[1]*x)+i[2] for i in modelParam])

def myFoo3(modelParam,x):
    return(np.exp(np.outer(modelParam[:,1],x))*params[:,0][:,None]+params[:,2][:,None])

当测量运行时间时:

x=np.array(np.arange(0,100,0.1))
params=np.array([[10,0.1,2],[20,0.3,4],[30,0.2,6],[15,0.2,4],[16,0.5,7]])
%time myFoo1(params,x)
%time myFoo2(params,x)
%time myFoo3(params,x)

输出是:

CPU times: user 3.58 ms, sys: 0 ns, total: 3.58 ms
Wall time: 2 ms
CPU times: user 855 µs, sys: 0 ns, total: 855 µs
Wall time: 703 µs
CPU times: user 690 µs, sys: 0 ns, total: 690 µs
Wall time: 564 µs

第一个是我的原始代码,因为它最容易编程。但是,第三个要快 4 倍。我可以再改进一下吗?

并使用 %timeit(按照评论中的建议编辑问题):

211 µs ± 663 ns per loop (mean ± std. dev. of 7 runs, 1000 loops each)
199 µs ± 199 ns per loop (mean ± std. dev. of 7 runs, 1000 loops each)
164 µs ± 56.1 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

【问题讨论】:

能否请您展示一个modelParam 的小样本? @user17242583。它是 np.array params。 a、b、c 参数有五个示例。 我的意思是,你能展示一下它的内容样本吗?如果我看到它是什么,那么试验它会更容易。或者也许只是它和它包含的数字范围? @usrer17242588。我正在尝试优化对为许多 (a,b,c) 参数集返回 a.exp(b.x)+c 的函数的调用。在此示例中,x 输入始终是从 0 到 100 的相同范围。 好的,谢谢。请把print(params.shape)的结果发过来好吗? 【参考方案1】:

通过使用略有不同的广播方式,我得到了一些小改进

def myFoo4(modelParam,x):
    return modelParam[:, 0:1] * np.exp(modelParam[:, 1:2] * x) + modelParam[:, 2:3]

另一个小改进是切换到np.float32

    x_float32 = np.array(np.arange(0, 100, 0.1), dtype=np.float32)
    params_float32 = np.array([[10, 0.1, 2], [20, 0.3, 4], [30, 0.2, 6], [15, 0.2, 4], [16, 0.5, 7]],
                      dtype=np.float32)
    41       100      17042.0    170.4     19.7          myFoo3(params, x)
    42       100      15282.0    152.8     17.6          myFoo4(params, x)
    43       100      11322.0    113.2     13.1          myFoo4(params_float32, x_float32)

【讨论】:

看来np.exp 调用是我机器上计算的瓶颈,因为它占用了 80% 的时间。所以我怀疑是否有更快的解决方案(除非 OP 接受使用近似值)。仅供参考:np.exp 函数尚未在大多数机器上使用 SIMD 指令。 AFAIK,只有支持 AVX-512 的处理器(例如 Intel Icelake 处理器)才真正使用 SIMD 指令进行此操作,从而实现了巨大的加速(根据 Intel 大约是 x10)。 @JérômeRichard。你是如何描述它的?使用 %prun ?我无法做到这一点。 @JérômeRichard 你说得对,在我的机器上np.exp 也是瓶颈。谢谢你关于 SIMD 和 np.exp 的信息,不知道。至于更快的解决方案 - 我虽然可能一些并行性(numba/multiprocessing)可以加快速度。 @dankal444 如果有足够的数据需要计算,但当前输入太小而无法使用线程(创建线程的时间可能会大大增加),那么多处理可能是一个好主意。尽管如此,OP 实际上可能需要计算更大的输入,所以它可能是值得的。 @Stef1611 好的。谢谢你。性能差距的部分原因是我的机器上的频率高出 2 倍,而且该处理器似乎不支持指数计算通常使用的融合乘加指令。加快这一速度的一种方法是使用英特尔版本的 Anaconda 与 SVML 库一起可能对 AMD 的 Opterons 调用 np.exp 进行矢量化(但我不确定它是否免费)。否则,使用更大输入的更多内核或使用近似值似乎是加快速度的唯一方法。

以上是关于使用 numpy 数组进行代码优化。有更好的解决方案吗?的主要内容,如果未能解决你的问题,请参考以下文章

使用函数方法优化numpy循环

Numpy库使用

如何规范化 4D numpy 数组?

如何让 VC 编译器使用 SIMD 更好地优化我的代码?

python学习之numpy

python学习之numpy