来自timeit的奇怪结果

时间:2016-09-16 23:54:07

标签: python debugging time ipython

我试图重复IPython%time的功能,但由于某些奇怪的原因,测试某些功能的结果是可怕的。

IPython的:

In [11]: from random import shuffle
   ....: import numpy as np
   ....: def numpy_seq_el_rank(seq, el):
   ....:     return sum(seq < el)
   ....:
   ....: seq = np.array(xrange(10000))
   ....: shuffle(seq)
   ....:

In [12]: %timeit numpy_seq_el_rank(seq, 10000//2)
10000 loops, best of 3: 46.1 µs per loop

的Python:

from timeit import timeit, repeat

def my_timeit(code, setup, rep, loops):
    result = repeat(code, setup=setup, repeat=rep, number=loops)
    return '%d loops, best of %d: %0.9f sec per loop'%(loops, rep, min(result))

np_setup = '''
from random import shuffle
import numpy as np
def numpy_seq_el_rank(seq, el):
    return sum(seq < el)

seq = np.array(xrange(10000))
shuffle(seq)
'''
np_code = 'numpy_seq_el_rank(seq, 10000//2)'

print 'Numpy seq_el_rank:\n\t%s'%my_timeit(code=np_code, setup=np_setup, rep=3, loops=100)

及其输出:

Numpy seq_el_rank:
    100 loops, best of 3: 1.655324947 sec per loop

正如你所看到的,在python中我创建了100个循环而不是10000(并且得到慢于35000倍的结果),就像在ipython中一样,因为它需要很长时间。任何人都可以解释为什么python中的结果如此缓慢?

UPD: 这是cProfile.run('my_timeit(code=np_code, setup=np_setup, rep=3, loops=10000)')输出:

         30650 function calls in 4.987 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    4.987    4.987 <string>:1(<module>)
        1    0.000    0.000    0.000    0.000 <timeit-src>:2(<module>)
        3    0.001    0.000    4.985    1.662 <timeit-src>:2(inner)
      300    0.006    0.000    4.961    0.017 <timeit-src>:7(numpy_seq_el_rank)
        1    0.000    0.000    4.987    4.987 Lab10.py:47(my_timeit)
        3    0.019    0.006    0.021    0.007 random.py:277(shuffle)
        1    0.000    0.000    0.002    0.002 timeit.py:121(__init__)
        3    0.000    0.000    4.985    1.662 timeit.py:185(timeit)
        1    0.000    0.000    4.985    4.985 timeit.py:208(repeat)
        1    0.000    0.000    4.987    4.987 timeit.py:239(repeat)
        2    0.000    0.000    0.000    0.000 timeit.py:90(reindent)
        3    0.002    0.001    0.002    0.001 {compile}
        3    0.000    0.000    0.000    0.000 {gc.disable}
        3    0.000    0.000    0.000    0.000 {gc.enable}
        3    0.000    0.000    0.000    0.000 {gc.isenabled}
        1    0.000    0.000    0.000    0.000 {globals}
        3    0.000    0.000    0.000    0.000 {isinstance}
        3    0.000    0.000    0.000    0.000 {len}
        3    0.000    0.000    0.000    0.000 {method 'append' of 'list' objects}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
    29997    0.001    0.000    0.001    0.000 {method 'random' of '_random.Random' objects}
        2    0.000    0.000    0.000    0.000 {method 'replace' of 'str' objects}
        1    0.000    0.000    0.000    0.000 {min}
        3    0.003    0.001    0.003    0.001 {numpy.core.multiarray.array}
        1    0.000    0.000    0.000    0.000 {range}
      300    4.955    0.017    4.955    0.017 {sum}
        6    0.000    0.000    0.000    0.000 {time.clock}

2 个答案:

答案 0 :(得分:3)

嗯,有一个问题是你误读了结果。 ipython告诉您10,000次迭代中每次迭代花费多长时间,总时间最短。 timeit.repeat模块报告了整轮100次迭代所花费的时间(再次,最短的三次)。因此,真正的差异是每个循环(ipython)为46.1μs,而每个循环(python)为16.5 ms,仍然是差异的350倍,但不是35,000x。

您没有显示ipython的分析结果。您的ipython会话中是否可能from numpy import sumfrom numpy import *?如果是这样的话,那么您的numpy.sum(针对numpy数组进行了优化,并run several orders of magnitude faster}进行了优化,而python代码(隔离了全局数据) ipython没有运行正常sum的方式(必须将所有值转换为Python int并将它们相加)。

如果您检查分析输出,几乎所有工作都在sum完成;如果你的代码中的那部分加快了几个数量级,那么总时间也会同样减少。这可以解释“真实”的差异;在上面链接的测试用例中,它是一个40倍的差异,这是一个较小的数组(数组越小,numpy可以“炫耀”越少)具有更复杂的值(相对于0和1的总和)我相信。)

其余部分(如果有的话)可能是代码如何eval略有不同的问题,或者可能是random shuffle的奇怪问题(对于一致的测试,你想要播种random使用一致的种子使“随机性”可重复)但我怀疑这是一个超过百分之几的差异。

答案 1 :(得分:1)

在python的一个实现中,此代码运行速度可能比另一个更慢。一个可以与另一个不同地进行优化,可以预编译某些部分而另一个部分被完全解释。找出原因的唯一方法是分析您的代码。

https://docs.python.org/2/library/profile.html

import cProfile

cProfile.run('repeat(code, setup=setup, repeat=rep, number=loops)')

会给出类似于

的结果
ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     1    0.000    0.000    0.000    0.000 <stdin>:1(testing)
     1    0.000    0.000    0.000    0.000 <string>:1(<module>)
     1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
     1    0.000    0.000    0.000    0.000 {method 'upper' of 'str' objects}

在显示功能调用,制作完成次数以及拍摄时间时,会显示。