正弦计算比余弦慢几个数量级

26

总结

在同一个numpy数组中,计算np.cos需要3.2秒,而在Linux Mint上运行np.sin需要548秒(九分钟)。

查看此存储库获取完整代码。


我有一个脉冲信号(见下图),需要调制到高频载波上,模拟激光多普勒测振仪。因此,需要对信号及其时间基准进行重新采样以匹配载波的更高采样率。

pulse signal to be modulated onto HF-carrier

在以下解调过程中,需要用到相位差载波sin(omega * t)和同相载波cos(omega * t)。奇怪的是,计算这些函数的时间高度依赖于计算时间向量的方式。

时间向量t1直接使用np.linspace计算,t2使用scipy.signal.resample中实现的方法

pulse = np.load('data/pulse.npy')  # 768 samples

pulse_samples = len(pulse)
pulse_samplerate = 960  # 960 Hz
pulse_duration = pulse_samples / pulse_samplerate  # here: 0.8 s
pulse_time = np.linspace(0, pulse_duration, pulse_samples,
                         endpoint=False)

carrier_freq = 40e6  # 40 MHz
carrier_samplerate = 100e6  # 100 MHz
carrier_samples = pulse_duration * carrier_samplerate  # 80 million

t1 = np.linspace(0, pulse_duration, carrier_samples)

# method used in scipy.signal.resample
# https://github.com/scipy/scipy/blob/v0.17.0/scipy/signal/signaltools.py#L1754
t2 = np.arange(0, carrier_samples) * (pulse_time[1] - pulse_time[0]) \
        * pulse_samples / float(carrier_samples) + pulse_time[0]

如下图所示,时间向量不完全相同。在8000万个样本时,差异t1-t2达到1e-8。 difference between time vectors <code>t1</code> and <code>t2</code> 在我的电脑上,计算t1的同相和移位载波每个需要3.2秒。然而,使用t2计算移位载波需要540秒,也就是9分钟。这几乎是相同的8000万个值。
omega_t1 = 2 * np.pi * carrier_frequency * t1
np.cos(omega_t1)  # 3.2 seconds
np.sin(omega_t1)  # 3.3 seconds

omega_t2 = 2 * np.pi * carrier_frequency * t2
np.cos(omega_t2)  # 3.2 seconds
np.sin(omega_t2)  # 9 minutes

我在我的32位笔记本和64位台式机上都可以重现这个错误,两者都运行着 Linux Mint 17。然而,在我室友的MacBook上,“慢正弦”计算所需的时间与其他三个计算一样短。


我在64位AMD处理器上运行Linux Mint 17.03,在32位Intel处理器上运行Linux Mint 17.2


1
如果您交换调用它们的顺序,结果是否一致?(只是假设这可能是由于某种内存/缓存问题 - 每个都会产生一个640MB向量。) - Oliver Charlesworth
计算机之间连接的numpy库是否不同? - MSeifert
它们似乎不同,但我对此无能为力:https://gitlab.com/Finwood/numpy-sine/blob/master/np-config.txt - Finwood
@ali_m,假数据在链接的git仓库中。克隆后,请运行make test - Finwood
@ali_m - 我认为一些库可以提高三角函数的性能(小角度近似等),例如mkl。 - MSeifert
显示剩余9条评论
2个回答

18

我认为numpy与此无关:我认为您的系统遇到了C math库中的性能错误,这个问题会影响sin在pi的大倍数附近的计算。(在这里,“错误”是一个相当广泛的概念——我不知道,由于大浮点数的正弦函数定义不清晰,“错误”实际上可能是该库正确处理特殊情况的行为!)

在Linux上,我的结果是:

>>> %timeit -n 10000 math.sin(6e7*math.pi)
10000 loops, best of 3: 191 µs per loop
>>> %timeit -n 10000 math.sin(6e7*math.pi+0.12)
10000 loops, best of 3: 428 ns per loop

来自Python聊天室的其他Linux使用者报告

10000 loops, best of 3: 49.4 µs per loop 
10000 loops, best of 3: 206 ns per loop

In [3]: %timeit -n 10000 math.sin(6e7*math.pi)
10000 loops, best of 3: 116 µs per loop

In [4]: %timeit -n 10000 math.sin(6e7*math.pi+0.12)
10000 loops, best of 3: 428 ns per loop

但是一位Mac用户报告说

In [3]: timeit -n 10000 math.sin(6e7*math.pi)
10000 loops, best of 3: 300 ns per loop

In [4]: %timeit -n 10000 math.sin(6e7*math.pi+0.12)
10000 loops, best of 3: 361 ns per loop

如果没有数量级的差别,为了解决问题,您可以尝试先对 2pi 取模:

>>> new = np.sin(omega_t2[-1000:] % (2*np.pi))
>>> old = np.sin(omega_t2[-1000:])
>>> abs(new - old).max()
7.83773902468434e-09

哪个具有更好的性能:

>>> %timeit -n 1000 new = np.sin(omega_t2[-1000:] % (2*np.pi))
1000 loops, best of 3: 63.8 µs per loop
>>> %timeit -n 1000 old = np.sin(omega_t2[-1000:])
1000 loops, best of 3: 6.82 ms per loop
请确认下面的翻译是否符合您的要求:

请注意,与预期相同,cos 函数也有类似的效果,只是相位被移动了:

>>> %timeit -n 1000 np.cos(6e7*np.pi + np.pi/2)
1000 loops, best of 3: 37.6 µs per loop
>>> %timeit -n 1000 np.cos(6e7*np.pi + np.pi/2 + 0.12)
1000 loops, best of 3: 2.46 µs per loop

仅供完整性参考:我在Windows上运行了“ %timeit -n 1000000 math.sin(6e7 * math.pi + 0.12)”和“%timeit -n 1000000 math.sin(6e7 * math .pi)”,结果分别为“1000000个循环,3次中最好的:每个循环461纳秒 ”和“1000000个循环,3次中最好的:每个循环425纳秒”。 - MSeifert
1
这可能与非规范化数有关吗?我记得当涉及非常小但非零的数字时,我写的一些浮点代码变得非常缓慢。 - cfh
但是这个“bug”只会在大数值的情况下出现,而不是接近零的情况下... - Finwood
@Finwood 不是解释,但如果问题是大数字,你可以将其取模2pi吗? - Paul
@Paul 是的,这就是我为避免这个问题而做的。 - Finwood

4

这些巨大性能差异的一个可能原因是数学库如何创建或处理IEEE浮点下溢(或denorm),这可能是由于在超越函数逼近期间一些更小的尾数位之间的差异产生的。而且,您的t1和t2向量可能会因这些较小的尾数位而有所不同,以及计算超越函数的算法在您链接的任何库中以及每个特定操作系统上的IEEE算术denorms或下溢处理程序。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接