python - 使用 numba 的对数除法和对数减法之间的性能差异
问题描述
我正在尝试优化一些使用日志的代码(数学类型,而不是时间戳记录类型:)),但我发现了一些奇怪的东西,我无法在网上找到任何答案。我们有 log(a/b) = log(a) - log(b),所以我写了一些代码来比较这两种方法的性能。
import numpy as np
import numba as nb
# create some large random walk data
x = np.random.normal(0, 0.1, int(1e7))
x = abs(x.min()) + 100 + x # make all values >= 100
@nb.njit
def subtract_log(arr, tau):
"""arr is a numpy array, tau is an int"""
for t in range(tau, arr.shape[0]):
a = np.log(arr[t]) - np.log(arr[t - tau])
return None
@nb.njit
def divide_log(arr, tau):
"""arr is a numpy array, tau is an int"""
for t in range(tau, arr.shape[0]):
a = np.log(arr[t] / arr[t - tau])
return None
%timeit subtract_log(x, 100)
>>> 252 ns ± 0.319 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit divide_log(x, 100)
>>> 5.57 ms ± 48.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
所以我们看到减去对数比除以对数快约 20,000 倍。我觉得这很奇怪,因为我会认为在减去对数时,必须计算对数序列近似值两次。但也许这与 numpy 广播操作的方式有关?
上面的例子很简单,因为我们没有对计算结果做任何事情。下面是一个更现实的例子,我们返回计算结果。
@nb.njit
def subtract_log(arr, tau):
"""arr is a numpy array, tau is an int"""
out = np.empty(arr.shape[0] - tau)
for t in range(tau, arr.shape[0]):
f = t - tau
out[f] = np.log(arr[t]) - np.log(arr[f])
return out
@nb.njit
def divide_log(arr, tau):
"""arr is a numpy array, tau is an int"""
out = np.empty(arr.shape[0] - tau)
for t in range(tau, arr.shape[0]):
f = t - tau
out[f] = np.log(arr[t] / arr[f])
return out
out1 = subtract_log(x, 100)
out2 = divide_log(x, 100)
np.testing.assert_allclose(out1, out2, atol=1e-8) # True
%timeit subtract_log(x, 100)
>>> 129 ms ± 783 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit divide_log(x, 100)
>>> 93.4 ms ± 257 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
现在我们看到时间是相同的数量级,但减去对数比除法慢 40%。
谁能解释这些差异?
为什么在平凡的情况下减去日志比除日志快得多?
当我们将值存储在数组中时,为什么减去日志比除日志慢 40%?我知道在初始化一个数组时有很大的设置成本
np.empty()
——subtract_log()
在简单的情况下初始化一个数组,但是如果不将值存储在其中会使时间从 252ns 增加到 311us。
解决方案
不要测量“无用”的东西,编译器可能会完全优化它
如果您关闭除零检查 (error_model="numpy"),这两个函数都需要大约 280ns。不是因为计算速度快,而是因为他们实际上什么也没做。期望优化掉无用的计算,但有时 LLVM 无法检测到所有这些。
在第二种情况下,您将 2 个对数的运行时间与 1 个对数和一个除法进行比较。(减法/加法以及乘法要快得多)。计算时间可能会有所不同,具体取决于日志实现和处理器。但也看看结果,它们并不完全相同。
至少对于 floa64 部门 (FDIV),您可以查看 Agner Fog 的指令表 。
推荐阅读
- websocket - JSF 2.3 Websockets 在使用 PrimeFaces MenuItem 和 ajax=true 时抛出 NullPointerException
- javascript - 为什么我得到的功能没有定义?
- tensorflow - 使用 tensorflow 的 DNN 估计器无法在 GCP 的 AI 平台上获得可重现的结果
- javascript - 尝试使用 URL 用 JQuery/Javascript 完成表单
- apache-flink - 是否可以编写一个使用 flink statefun Harness 终止的单元测试?
- java - Android Q及以上版本如何保存和读取文本文件或MHT格式文件
- python - Jinja Python Flask - 如果 name == x 那么 name 应该是别的东西
- python - 尝试在 Dockerfile 中安装 opencv3 时找不到 Conda
- regex - 在.htaccess中将大写字母转换为小写
- python - django 分组后加入