为什么时间会错误地报告如此快的时间?

3

我在玩大数值时,写了以下代码:

import time

def ispow2(n):
    return not n & n - 1

start = time.clock()
ispow2(2**100000000)
end = time.clock()

print(end - start)

令人惊讶的是,这输出了0.016864107385627148,时间非常短。但实际上,它需要大约8秒而不是0.02

为什么时间模块报告如此快的时间,当明显运行代码需要更长时间呢?


根据 timeclock() 已经被弃用,因此我将其替换为 process_time()。我得到了几乎相同的结果。perf_counter() 也是如此。
注意:这是从IDLE运行的。当我从命令行运行时,时间似乎被准确报告了。也许pythonw.exe与此有关,但是为什么呢?
然而,当我在2**10...末尾添加另一个0时,在命令行上需要大约7秒钟,但报告的是0.1781140373572865

甚至在我的机器上更少。但它没有花费8秒钟。 - thefourtheye
@thefourtheye 大约花了8秒钟,其中大部分时间IDLE没有响应。现在尝试使用CMD。 - Justin
1
在将您的测试封装到一个函数中后,我在Python 2.7交互式控制台中看到了大约80毫秒的时间。有趣的是,在最后一行函数后按下回车键后,控制台会挂起一两秒钟...即使没有执行任何操作。 - Jonathon Reinhart
@JonathonReinhart 正是我所看到的:控制台挂起了约8秒钟。 - Justin
为了性能测量,您可以使用from timeit import default_timer as timer:它会选择适合您平台和Python版本的最佳计时器。 - jfs
3个回答

8

python.exepythonw.exe在运行前会对代码进行优化。看起来2**100000000被预先计算了。对代码进行以下小修改:

import time

print("program entered")

def ispow2(n):
    return not n & n - 1

start = time.perf_counter()
ispow2(2**100000000)
end = time.perf_counter()

print(end - start)

在等待之后,将完全产生以下输出:
program entered
0.01701506924359556

因此,在等待大部分时间过后,程序才开始运行。

有数据表明这是使用2**...部分(从命令行运行)的情况:

power of two|approximate wait time|reported time
1000000000  | 6  seconds          |0.1637752267742188
10000000000 | 62 seconds          |1.6400543291627092

在最后一次运行中,program entered1.6400543291627092 的输出之间有一个明显的 ~1.5 秒的等待时间。

4
PyCode_Optimize 检测字节码模式 LOAD_CONST, LOAD_CONST, BINOP,并调用 fold_binops_on_constants 函数来计算操作结果,并将其替换为单个 LOAD_CONST。如果您导入脚本,则模块的代码对象将被缓存到 __pycache__ 目录中,您会发现它很大,大小与 sys.getsizeof(2**100000000) 相当。 - Eryk Sun

3
常量是预先计算好的:
>>> import dis
>>> dis.dis(lambda: 2**100)
1           0 LOAD_CONST               3 (1267650600228229401496703205376)
            3 RETURN_VALUE

比较:

$ ./python -mtimeit "2**1000"
10000000 loops, best of 3: 0.0601 usec per loop
$ ./python -mtimeit "2**10000"
10000000 loops, best of 3: 0.0584 usec per loop

vs.:

$ ./python -mtimeit -s "p=1000" "2**p"
100000 loops, best of 3: 6.89 usec per loop
$ ./python -mtimeit -s "p=10000" "2**p"
10000 loops, best of 3: 94.2 usec per loop

在第一种情况下,当功率增加10倍后,时间不会改变。而在第二种情况下,功率是可变的,因此时间会相应地改变。


0

计时小代码片段的最佳方法是使用timeit

如果我在我的电脑上使用timeit并与您进行比较,我会得到一个类似的数字,使用Python 3.4:

import time
import timeit

def ispow2(n):
    return not n & n - 1

n=10

start = time.time()
for i in range(n):
    ispow2(2**100000000)
end = time.time()

print(end - start)

print(timeit.Timer('ispow2(2**100000000)', setup="from __main__ import ispow2").timeit(number=n))

输出:

0.1257798671722412
0.12608672981150448

我使用了time.time()time.clock(),但两者似乎都可以工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接