python - numpy.cos 在某些数字上的工作时间明显更长
问题描述
TLDR:
numpy.cos()
在特定数字上的工作时间延长 30%(例如 24000.0)。添加一个小的增量 (+0.01) 会导致numpy.cos()
正常工作。
我不知道为什么。
在与numpy
. 我在检查缓存工作时不小心做出了错误的图表 -numpy.cos(X)
时间取决于X
. 这是我修改后的代码(从我的 Jupyter 笔记本复制):
import numpy as np
import timeit
st = 'import numpy as np'
cmp = []
cmp_list = []
left = 0
right = 50000
step = 1000
# Loop for additional average smoothing
for _ in range(10):
cmp_list = []
# Calculate np.cos depending on its argument
for i in range(left, right, step):
s=(timeit.timeit('np.cos({})'.format(i), number=15000, setup=st))
cmp_list.append(int(s*1000)/1000)
cmp.append(cmp_list)
# Calculate average times
av=[np.average([cmp[i][j] for i in range(len(cmp))]) for j in range(len(cmp[0]))]
# Draw the graph
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
plt.plot(range(left, right, step), av, marker='.')
plt.show()
图表如下所示:
首先,我认为这只是一个随机故障。我重新计算了我的单元格,但结果几乎相同。所以我开始使用step
参数,计算次数和平均列表长度。但一切都对这个数字没有影响:
更接近:
之后,range
没用(它不能使用浮点数)所以我np.cos
手动计算:
print(timeit.timeit('np.cos({})'.format(24000.01),number=5000000,setup=st))
print(timeit.timeit('np.cos({})'.format(24000.00),number=5000000,setup=st))
print(timeit.timeit('np.cos({})'.format(23999.99),number=5000000,setup=st))
结果是:
3.4297256958670914
4.337243619374931
3.4064380447380245
np.cos()
精确计算 24000.00比 24000.01 长30% !
还有另一个类似的奇怪数字(大约 500000,我不记得确切)。
我查看了numpy
文档,查看了它的源代码,但它对这种效果一无所知。我知道三角函数使用几种算法取决于值大小、精度等,但让我感到困惑的是,精确的数字可以计算得更长。
为什么np.cos()
会有这种奇怪的效果?它是某种处理器副作用(因为numpy.cos
使用依赖于处理器的 C 函数)?我安装了 Intel Core i5 和 Ubuntu,如果它对某人有帮助的话。
编辑 1:我试图在另一台装有 AMD Ryzen 5 的机器上重现它。结果只是不稳定。这是相同代码的三个连续运行的图表:
import numpy as np
import timeit
s = 'import numpy as np'
times = []
x_ranges = np.arange(23999, 24001, 0.01)
for x in x_ranges:
times.append(timeit.timeit('np.cos({})'.format(x), number=100000, setup=s))
# ---------------
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
plt.plot(x_ranges, times)
plt.show()
嗯,有一些模式(比如大部分一致的左侧部分和不一致的右侧部分),但它与英特尔处理器运行有很大不同。看起来它实际上只是处理器的特殊方面,而 AMD 的行为在其不确定性方面更容易预测 :)
PS @WarrenWeckesser 感谢 ``np.arange``` 功能。它确实很有用,但正如预期的那样,它不会改变任何结果。
解决方案
这些特殊数字计算结果的缓慢可能与精确舍入和制表者的困境有关。
为了说明,假设您正在制作一个包含 4 个位置的指数函数表。那么 exp(1.626) = 5.0835。应该四舍五入到 5.083 还是 5.084?如果更仔细地计算 exp(1.626),它将变为 5.08350。然后是 5.083500。然后是 5.0835000。由于 exp 是超越数,因此在区分 exp(1.626) 是 5.083500...0ddd 还是 5.0834999...9ddd 之前,这可能会持续很长时间。
虽然由于这个原因,IEEE 标准不要求对超越函数进行精确舍入,但math.cos
函数的实现可能会受到这个问题的影响,同时尽最大努力计算最准确的结果,然后找出效果不值得努力。
为了证明某些数字是这种情况,必须以高精度X
计算 的值并检查其二进制表示 - 尾数的可表示部分必须后跟以下模式之一:math.cos(X)
- 1 和一长串 0
- 0 和 1 的长期运行(当计算值的精度低于容纳运行中所有 1 所需的精度时,这种情况显示为第一个)
因此,一个数字将成为超越函数的慢参数的概率是 1/2 n,其中n
是算法看到的上述模式的最大长度,之后它放弃尝试获得精确舍入的结果。
演示突出显示 IEEE 754 双精度情况下尾数的可表示部分(其中尾数有 53 位):
In [1]: from mpmath import mp
In [2]: import math
In [3]: def show_mantissa_bits(x, n, k):
...: print(bin(int(mp.floor(abs(x) * 2**n)))[2:])
...: print('^'*k)
...:
In [4]: mp.prec = 100
In [5]: show_mantissa_bits(mp.cos(108), 64, 53)
110000000100001011001011010000110111110010100011000011000000000
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In [6]: show_mantissa_bits(mp.cos(108.01), 64, 53)
101110111000000110001101110001000010100111000010101100000100110
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In [7]: show_mantissa_bits(mp.cos(448), 64, 53)
101000101000100111000010111100001011111000001111110001000000000
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In [8]: show_mantissa_bits(mp.cos(448.01), 64, 53)
101001110110001010010100100000110001111100000001101110111010111
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In [9]: show_mantissa_bits(mp.cos(495), 64, 53)
11001010100101110110001100110101010011110010000000000011111111
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In [10]: show_mantissa_bits(mp.cos(495.01), 64, 53)
11010100100111100110000000011000110000001001101100010000001010
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In [11]: show_mantissa_bits(mp.cos(24000), 64, 53)
11001000100000001100110111011101001101101101000000110011111111
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In [12]: show_mantissa_bits(mp.cos(24000.01), 64, 53)
10111110011100111001010101100101110001011010101011001010110011
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
推荐阅读
- java - 单击主页按钮时,Android应用程序未按预期最小化
- modal-dialog - 在显示模式(基础)中显示具有多个标记的地图
- javascript - 当我点击动态按钮时的动态模式
- blockchain - 用户如何签署多重签名交易?
- java - Android MVP - 分享偏好
- django-rest-framework - Django REST 框架 FileField PUT 测试用例
- postgresql - 无法访问 postgres PSQL
- sql - 检查表中是否存在多个值
- java - 在 Java 中配置 HTTPS 代理
- database - 如何与 mongoDB 数据库共享网站文件