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

Fin*_*ood 26 python signal-processing numpy scipy

TL;博士

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

有关完整代码,请参阅此repo.


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

脉冲信号被调制到HF载波上

在随后的解调过程中,需要同相载波cos(omega * t)和相移载波sin(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]
Run Code Online (Sandbox Code Playgroud)

从下图中可以看出,时间向量并不相同.差距t1 - t2达到8000万个样本1e-8.

时间向量<code>t1</code>需要<em>3.2秒</em>.<br>
<strong>用<code>t2</code>,但是,在计算偏移载波取<em>540秒</em>.九分钟 对于几乎相同的8000万个值.</strong></p>

<pre><code>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
</code></pre><a target=Run Code Online (Sandbox Code Playgroud)

我可以在我的32位笔记本电脑和我的64位塔上重现这个错误,两者都运行Linux Mint 17.然而,在我的平板伴侣的MacBook上,"慢速正弦"花费的时间与其他三个计算时间相同.


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

DSM*_*DSM 18

我不认为numpy与此有任何关系:我认为你正在绊倒系统中C数学库中的性能错误,这会影响接近pi的大倍数的sin.(我在这里广泛使用"bug" - 据我所知,由于大浮点数的正弦定义不明确,"bug"实际上是库正确处理极端情况的行为!)

在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
Run Code Online (Sandbox Code Playgroud)

以及Python聊天室报告中使用的其他Linux类型

10000 loops, best of 3: 49.4 µs per loop 
10000 loops, best of 3: 206 ns per loop
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

但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
Run Code Online (Sandbox Code Playgroud)

没有数量级的差异.作为一种解决方法,您可以先尝试使用mod 2 pi:

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

哪个有更好的表现:

>>> %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
Run Code Online (Sandbox Code Playgroud)

请注意,正如预期的那样,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
Run Code Online (Sandbox Code Playgroud)