打印号码和号码清单的执行时间

时间:2018-08-24 17:43:22

标签: python range

我正在尝试打印1到1000之间的数字。

print(*range(1,1001))比打印列表print([*range(1,1001)])花费更多的时间。

为什么打印列表比打印数字行要快得多?

3 个答案:

答案 0 :(得分:2)

在这里您可能要问两件事。


由于诸如此类的事情,第二个会更快:

  • 传递单个参数而不是1000。(在CPython的最新版本中,这实际上并不意味着在堆栈上传递1000个值,但这确实意味着您必须通过CALL_FUNCTION_EX而不是通过更简单的CALL_FUNCTION,实际上是经过优化的快速路径。)
  • 在优化的list.__repr__内遍历列表元素,而不是使用通用的迭代器循环。
  • print在每个元素的__str__之间执行的操作比list.__repr__在每个元素的__repr__之间执行的操作稍微复杂(有条件地打印局部变量) ,而不是始终使用固定的字符串)。

这应该是几微秒的时间-足以测量 ,但不足以发出 notice


但是第二个也会更快,因为它减少了I / O调用。这可能会淹没所有这些小的差异。而且,如果您的终端运行缓慢,例如Windows cmd或IDLE伪终端,这很容易引起注意。


首先让我们尝试调用一个绝对不做任何事情的函数:

In [765]: def dummy(*args): pass    
In [766]: %timeit dummy(*range(1, 1001))
19 µs ± 71.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)    
In [767]: %timeit dummy([*range(1, 1001)])
13 µs ± 609 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

因此,第二个仅由于参数传递而快了50%,但这仅是6us。


如果以有效的方式print迭代其参数怎么办?

In [768]: def dummy(*args):
     ...:     for _ in args: pass
In [769]: %timeit dummy(*range(1, 1001))
22.8 µs ± 1.31 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)    
In [770]: %timeit dummy([*range(1, 1001)])
13.1 µs ± 148 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

现在它越来越接近2:1,但仍然只有9us的差异。

当然,我在这里有点作弊,因为print是C函数,但是无论哪种方式,*args都变成一个元组,它们必须以某种方式循环。 >


为了更公平地比较,如果它也调用每个参数的__str__怎么办?

In [776]: def dummy(*args):
     ...:     for arg in args: str(arg)
In [776]: %timeit dummy(*range(1, 1001))
185 µs ± 1.67 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)    
In [777]: %timeit dummy([*range(1, 1001)])
86.3 µs ± 826 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

即使试图使事情对第一个更加公平,仍然约为2:1。


让我们实际调用print,但打印到一个文件对象,该对象将丢弃其输入:

In [747]: class Nully:
     ...:     def write(self, *args): pass
In [749]: null = Nully()    
In [750]: %timeit print(*range(1, 1001), file=null)
390 µs ± 7.44 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)   
In [751]: %timeit print([*range(1, 1001)], file=null)
88.4 µs ± 2.35 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

现在,第二个的速度大约是它的4倍,但是我们只聊了几分之一毫秒。


现在让我们尝试连接实际的I / O,但连接到空设备:

In [745]: %timeit with open(os.devnull, 'w') as null: print(*range(1, 1001), file=null)
436 µs ± 13.3 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)    
In [746]: %timeit with open(os.devnull, 'w') as null: print([*range(1, 1001)], file=null)
140 µs ± 1.74 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

它们的速度都降低了同样的小幅度。


现在,让我们尝试向每次写入花费较长时间(例如10毫秒)的文件进行写入:

In [767]: class Slowy:
     ...:     def write(self, *args): time.sleep(0.01)
In [768]: null = Slowy()
In [770]: %timeit print(*range(1, 1001), file=null)
26.8 s ± 15.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [771]: %timeit print([*range(1, 1001)], file=null)
28.2 ms ± 39.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

最初的300us差异可能仍然存在,但是谁在乎呢?在这里重要的是由1000次写入而不是1次引起的3阶27秒差异。

当然,即使cmd.exe和IDLE也不慢。但是它们非常慢。

所以,我的猜测是,最后一部分就是您要问的问题。


实际上,从后来添加的评论中可以找到

  

这是人类明显的延迟。

timeit.timeit("print(*range(1,1001))",number=1) => 10 s 
timeit.timeit("print([*range(1,1001)])",number=1) => 95 ms
  

顺便说一句,我正在使用Windows,并在Python IDLE上运行此代码。

所以我错了:IDLE 差不多完全这么慢。 (哇!)

答案 1 :(得分:0)

您似乎是对的,这是我的计算机(Mac OSX)上的输出

import timeit

# 1
timeit.timeit('print(*range(1,1001))',number=10000)
7.32013652799651

# 2
timeit.timeit('print([*range(1,1001)])',number=10000)
3.6037830549757928

我要说的原因在于,通过使用第二种方法,您将向print函数传递一个列表,而使用第一种方法,将print函数与该列表一起调用项目作为单独的参数。

查看dis()

的输出
 dis.dis('print(*range(1,10))')

          0 LOAD_NAME                0 (print)
          2 LOAD_NAME                1 (range)
          4 LOAD_CONST               0 (1)
          6 LOAD_CONST               1 (10)
          8 CALL_FUNCTION            2
         10 CALL_FUNCTION_EX         0
         12 RETURN_VALUE


 dis.dis('print([*range(1,10)])')
          0 LOAD_NAME                0 (print)
          2 LOAD_NAME                1 (range)
          4 LOAD_CONST               0 (1)
          6 LOAD_CONST               1 (10)
          8 CALL_FUNCTION            2
         10 BUILD_LIST_UNPACK        1
         12 CALL_FUNCTION            1
         14 RETURN_VALUE

在第二种情况下,可能会看到在调用打印函数之前,列表的各项已连接(BUILD_LIST_UNPACK)。

答案 2 :(得分:-1)

打印数字将一次获得一个值 打印列表将一次添加整个列表。