numpy.cos在某些数字上的工作时间明显更长

时间:2019-04-05 13:55:47

标签: python numpy

TLDR:

numpy.cos()在特定数字(例如24000.0)上的工作时间延长了30%。添加较小的增量(+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()

图形如下:

enter image description here

首先,我认为这只是一个随机故障。我重新计算了细胞,但结果几乎相同。因此,我开始使用step参数,包括计算数量和平均列表长度。但是一切都不会影响这个数字:

enter image description here

甚至更近:

enter image description here

在此之后,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的时间长于 30%,而不是24000.01!

还有另一个奇怪的数字(大约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()

enter image description here

enter image description here

enter image description here

嗯,有一些模式(例如大部分一致的左部分和不一致的右部分),但是它与Intel处理器的运行有很大不同。看来,这实际上只是处理器的特殊方面,而AMD的不确定性行为更容易预测:)

P.S。 @WarrenWeckesser感谢``np.arange`''功能。它确实很有用,但是却没有改变结果。

1 个答案:

答案 0 :(得分:9)

这些特殊数字的结果计算速度较慢,可能与精确舍入和table maker's dilemma有关。

  

为了说明,假设您正在制作一个指数表   功能到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's
  • 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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^