首页 > 解决方案 > 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``` 功能。它确实很有用,但正如预期的那样,它不会改变任何结果。

标签: pythonnumpybenchmarking

解决方案


这些特殊数字计算结果的缓慢可能与精确舍入和制表者的困境有关。

为了说明,假设您正在制作一个包含 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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

推荐阅读