所以我用三种不同的方法写下了用于评估多项式的代码。霍纳的方法应该是最快的,而天真的方法应该是最慢的,对吗?但是,为什么计算它的时间不是我所期望的呢?对于itera和naive方法,计算时间有时会变得完全相同。怎么了?
import numpy.random as npr
import time
def Horner(c,x):
p=0
for i in c[-1::-1]:
p = p*x+i
return p
def naive(c,x):
n = len(c)
p = 0
for i in range(len(c)):
p += c[i]*x**i
return p
def itera(c,x):
p = 0
xi = 1
for i in range(len(c)):
p += c[i]*xi
xi *= x
return p
c=npr.uniform(size=(500,1))
x=-1.34
start_time=time.time()
print Horner(c,x)
print time.time()-start_time
start_time=time.time()
print itera(c,x)
print time.time()-start_time
start_time=time.time()
print naive(c,x)
print time.time()-start_time
以下是一些结果:
[ 2.58646959e+69]
0.00699996948242
[ 2.58646959e+69]
0.00600004196167
[ 2.58646959e+69]
0.00600004196167
[ -3.30717922e+69]
0.00899982452393
[ -3.30717922e+69]
0.00600004196167
[ -3.30717922e+69]
0.00600004196167
[ -2.83469309e+69]
0.00999999046326
[ -2.83469309e+69]
0.00999999046326
[ -2.83469309e+69]
0.0120000839233
答案 0 :(得分:16)
您的分析可以大大改进。另外,我们可以让您的代码运行速度提高200-500倍。
(1)冲洗并重复
由于两个原因,您无法仅运行一次性能测试迭代。
你不需要大量的跑步(当然,这并不会造成伤害),但是你估计并调整迭代次数,直到方差在你的目的可以接受的水平之内。
timeit
是一个很好的小模块,用于分析Python代码。
我将此添加到您的脚本底部。
import timeit
n = 1000
print 'Horner', timeit.timeit(
number = n,
setup='from __main__ import Horner, c, x',
stmt='Horner(c,x)'
)
print 'naive', timeit.timeit(
number = n,
setup='from __main__ import naive, c, x',
stmt='naive(c,x)',
)
print 'itera', timeit.timeit(
number = n,
setup='from __main__ import itera, c, x',
stmt='itera(c,x)',
)
哪个产生
Horner 1.8656351566314697
naive 2.2408010959625244
itera 1.9751169681549072
霍纳是最快的,但它并没有完全打破其他两个人。
(2)看看发生了什么......非常仔细
Python有运算符重载,所以很容易错过这个。
npr.uniform(size=(500,1))
给你一个500 x 1 numpy的随机数结构。
那又怎样?
嗯,c[i]
不是数字。 这是一个带有一个元素的numpy数组。 Numpy会重载运算符,所以你可以做一些事情,比如用数组乘以数组。
没关系,但是为每个元素使用一个数组很多的开销,所以很难看出算法之间的区别。
相反,让我们尝试一个简单的Python列表:
import random
c = [random.random() for _ in range(500)]
现在,
Horner 0.034661054611206055
naive 0.12771987915039062
itera 0.07331395149230957
哇! 所有时间加快(10-60x)。按比例,霍纳的实施比其他两个更快。我们删除了所有三个的开销,现在可以看到“裸骨”的差异。
Horner比天真快4倍,比itera快2倍。
(3)备用运行时
你正在使用Python 2.我假设是2.7。
让我们看看Python 3.4如何运作。 (语法调整:您需要在参数列表周围添加括号print
。)
Horner 0.03298933599944576
naive 0.13706714100044337
itera 0.06771054599812487
大致相同。
让我们试试PyPy,一个Python的JIT实现。 (“普通”Python实现称为CPython。)
Horner 0.006507158279418945
naive 0.07541298866271973
itera 0.005059003829956055
尼斯!现在每个实现的运行速度提高了2-5倍。霍纳现在的速度是天真的10倍,但比itera略慢。
JIT运行时比解释器更难以分析。让我们将迭代次数增加到50000,并尝试确保。
Horner 0.12749004364013672
naive 3.2823100090026855
itera 0.06546688079833984
(请注意,我们有50倍的迭代次数,但只有20倍的时间...... JIT在前1000次运行中的许多次都没有完全生效。)相同的结论,但差异更加明显。
当然,JIT的想法是在运行时分析,分析和重写程序,所以如果你的目标是比较算法,这将增加许多非显而易见的实现细节。
尽管如此,比较运行时可能有助于提供更广阔的视角。
还有一些事情。例如,您的天真实现计算它从未使用过的变量。您使用range
代替xrange
。您可以尝试使用索引而不是反向切片向后迭代。等
这些都没有改变我的结果,但是值得考虑。
答案 1 :(得分:6)
通过测量这样的事情,你无法获得准确的结果:
start_time=time.time()
print Horner(c,x)
print time.time()-start_time
据推测,大部分时间都花费在print
函数所涉及的IO函数上。此外,为了获得重要的东西,您应该在大量迭代中执行度量以平滑错误。在一般情况下,您可能还希望对各种输入数据执行测试 - 根据您的算法,某些情况可能会巧合地解决,而不是其他情况。
您应该明确地查看timeit
模块。这样的事情,也许:
import timeit
print 'Horner',timeit.timeit(stmt='Horner(c,x)',
setup='from __main__ import Horner, c, x',
number = 10000)
# ^^^^^
# probably not enough. Increase that once you will
# be confident
print 'naive',timeit.timeit(stmt='naive(c,x)',
setup='from __main__ import naive, c, x',
number = 10000)
print 'itera',timeit.timeit(stmt='itera(c,x)',
setup='from __main__ import itera, c, x',
number = 10000)
在我的系统上制作:
Horner 23.3317809105
naive 28.305519104
itera 24.385917902
但是仍然有来自另一个的可变结果:
Horner 21.1151690483
naive 23.4374330044
itera 21.305426836
正如我之前所说,要获得更有意义的结果,您应该明确增加测试次数,并在几个测试用例中运行,以便平滑结果。
答案 2 :(得分:3)
如果您正在进行大量的基准测试,科学计算,与numpy相关的工作以及使用ipython的更多内容将是一个非常有用的工具。
要进行基准测试,您可以使用ipython magic计算代码的时间,每次运行时您将获得更一致的结果,这只是使用timeit
然后使用函数或代码来计算时间:
In [28]: timeit Horner(c,x)
1000 loops, best of 3: 670 µs per loop
In [29]: timeit naive(c,x)
1000 loops, best of 3: 983 µs per loop
In [30]: timeit itera(c,x)
1000 loops, best of 3: 804 µs per loop
要跨越多行的时间码,您只需使用%%timeit
:
In [35]: %%timeit
....: for i in range(100):
....: i ** i
....:
10000 loops, best of 3: 110 µs per loop
ipython可以compile cython code,f2py代码,并使用不同的插件和ipython魔术命令执行许多其他非常有用的任务。
使用cython和一些非常基本的改进,我们可以将Horner的效率提高约25%:
In [166]: %%cython
import numpy as np
cimport numpy as np
cimport cython
ctypedef np.float_t DTYPE_t
def C_Horner(c, DTYPE_t x):
cdef DTYPE_t p
for i in reversed(c):
p = p * x + i
return p
In [28]: c=npr.uniform(size=(2000,1))
In [29]: timeit Horner(c,-1.34)
100 loops, best of 3: 3.93 ms per loop
In [30]: timeit C_Horner(c,-1.34)
100 loops, best of 3: 2.21 ms per loop
In [31]: timeit itera(c,x)
100 loops, best of 3: 4.10 ms per loop
In [32]: timeit naive(c,x)
100 loops, best of 3: 4.95 ms per loop
使用@Paul drapers中的列表回答我们的cythonised版本运行速度是原始函数的两倍,比ietra和naive快得多:
In [214]: import random
In [215]: c = [random.random() for _ in range(500)]
In [44]: timeit C_Horner(c, -1.34)
10000 loops, best of 3: 18.9 µs per loop
In [45]: timeit Horner(c, -1.34)
10000 loops, best of 3: 44.6 µs per loop
In [46]: timeit naive(c, -1.34)
10000 loops, best of 3: 167 µs per loop
In [47]: timeit itera(c,-1.34)
10000 loops, best of 3: 75.8 µs per loop