我的函数计算洛伦兹给定频率,fwhm,amp。我想对它进行矢量化,以便它计算频率,fwhm和amps的列表:
def lorz1(freq_series, freq, fwhm, amp):
numerator = fwhm
denominator = (2*np.pi) * ((freq_series[:,None] - freq)**2 + fwhm**2/4)
lor = numerator / denominator
main_peak = amp*(lor/np.linalg.norm(lor, axis=0))
return np.sum(main_peak, axis=1)
def lorz2(freq_series, freq, fwhm, amp):
numerator = fwhm[:,None]
denominator = (2*np.pi) * ((freq_series - freq[:,None])**2 + fwhm[:,None]**2/4)
lor = numerator / denominator
main_peak = amp[:,None]*(lor/np.linalg.norm(lor, axis=1)[:,None])
return np.sum(main_peak, axis=0)
def lorz3(freq_series, freq, fwhm, amp):
numerator = fwhm
denominator = (2*np.pi) * ((freq_series - freq)**2 + fwhm**2/4)
lor = numerator / denominator
main_peak = amp*(lor/np.linalg.norm(lor))
return main_peak
series = np.linspace(0,100,50000)
freq = np.random.uniform(5,50,50)
fwhm = np.random.uniform(0.01,0.05,50)
amps = np.random.uniform(5,500,50)
时间:
%timeit lorz1(series, freq, fwhm, amps)
38.4 ms ± 1.7 ms/循环(平均值±标准差)运行7次,每次循环10次)
%timeit lorz2(series, freq, fwhm, amps)
29.8 ms ± 1.8 ms/循环(平均值±标准差)运行7次,每次循环10次)
%timeit np.sum(np.array([lorz3(series, item1, item2, item3)
for (item1,item2,item3) in zip(freq, fwhm, amps)]), axis=0)
24.1 ms ± 5.02 ms/循环(平均值±标准差)运行7次,每次循环10次)
我在lorz1
和lorz2
中的向量化哪里出错了?不是应该比lorz3
快吗?
1条答案
按热度按时间6ljaweal1#
我使用两个版本做了一些进一步的分析:
版本1:
版本2:
请注意,我是如何将
lorz3
的求和调整为普通的Pythonsum
的。这在我的测试中更快,因为它避免了临时数组的构造。以下是我做的一些分析的结果:
这里是更快的版本:
请注意,在更快的代码中,指令的数量实际上略高,这是有道理的,因为它的向量化程度较低,但每个周期的指令数量更高,使其整体速度更快。在较慢的版本中有两倍多的LLC加载,其中大多数未命中,而这里几乎所有命中。我不知道如何解释
topdown-bad-spec
计数器。也许其他人可以对此发表评论。CPU甚至时钟下降(这是可重复的),这支持了它只是在等待内存的想法。
此外,请注意最后一行中的
sys
时间。lorz2
将28%的运行时间花在内核空间上。因为它不做任何与IO相关的事情,所以这是所有的内存分配和释放开销。我们可以再进一步看一下失速的原因:
因此,
lorz2
版本只是在2级或3级缓存未命中时不断停顿。我们可以进一步看一个简单的
perf report
嗯,有意思。点积从何而来?我假设这就是
linalg.norm
对简单向量的实现方式。顺便说一句,我们可以通过3种措施稍微加快
lorz2
和lorz3
版本的速度:1.将乘法和求和合并为一个矩阵乘法
1.重新排序一些操作以在较小的数组(或标量)上执行它们
1.将除法替换为乘法,
但这并没有改变整体趋势。
总结
Numpy向量化主要有助于减少每次调用的开销。一旦数组足够大,我们就不会从中获得太多好处,因为与计算本身相比,剩余的解释器开销很小。同时,较大的阵列导致存储器效率降低。通常,在L2或L3缓存大小附近有一个最佳点。
lorz3
实现比其他实现更好地达到了这一点。对于较小的系列大小和较大的其他阵列大小,我们可以预期
lorz2
的性能更好。例如,这个数据集使我的lorz2a
比我的lorz3a
快:Numpy的简单、急切的评估方案将调优的责任放在了用户身上。像NumExpr这样的其他库试图避免这种情况。