为什么IDLE 3.4在这个程序上这么慢?

9 投票
2 回答
719 浏览
提问于 2025-04-18 09:46

编辑:我完全重新提问了。这个问题和time.time()没有关系。

这是一个程序:

import time
start=time.time()
a=9<<(1<<26)             # The line that makes it take a while
print(time.time()-start)

这个程序在保存为文件后,用Python 3.4的IDLE运行时,大约需要10秒钟,尽管从time.time()输出的是0.0。问题很明显出在IDLE上,因为在命令行中运行这个程序几乎不需要时间。

另一个有相同效果的程序,senshin发现的,是:

def f(): 
    a = 9<<(1<<26)

我确认这个程序在Python 2.7的IDLE中运行,或者在Python 2.7或3.4的命令行中运行时,几乎是瞬间完成的。

那么,Python 3.4的IDLE到底在做什么,让它运行得这么慢呢?我知道计算这个数字并把它存到内存里是比较耗磁盘的,但我想知道的是,为什么Python 3.4的IDLE会进行这个计算和写入,而Python 2.7的IDLE和命令行中的Python似乎没有这样做。

2 个回答

0

我真的很困惑。这里有一些关于3.4.1版本的结果。在3.4.1或3.3.5中,从编辑器运行前两行代码,得到的对比结果是一样的。

>>> a = 1 << 26; b = 9 << a  # fast, , .5 sec
>>> c = 9 << (1 << 26)  # slow, about 3 sec
>>> b == c  # fast
True
>>> exec('d=9<<(1<<26)', globals())  # fast
>>> c == d  # fast
True

正常执行和Idle执行的区别在于,Idle在执行代码时使用的是像上面那样的exec调用,不过传给exec的'globals'不是globals(),而是一个看起来像globals()的字典。我不知道在这个方面2.7和3.4的Idle有什么区别,除了exec从语句变成了函数。为什么执行一个exec会比单独执行一个exec更快呢?增加一个中间绑定怎么会更快呢?

1

我会仔细分析这一行代码。你看到的是:

9 << (1 << 26)

这里的(1 << 26)是第一个被计算的部分,它会产生一个非常大的数字。简单来说,这行代码的意思是你要把数字1乘以2的26次方,这样在内存中就会产生一个值为2 ** 26的数字。不过,这并不是问题的关键。接下来,你把9向左移动26次,这样会产生一个大约有5000万位数的数字(我甚至无法准确计算!),因为左移的次数实在太多了。以后要小心,因为看似小的移动次数实际上会迅速增大。如果移动的次数再大一点,你的程序可能根本就无法运行。总之,如果你好奇的话,这个表达式在数学上可以理解为9 * 2 ** (2 ** 26)

评论区的模糊之处可能实际上是关于Python如何在后台处理这么大一块内存,而不是IDLE本身。

编辑1:

我认为发生的事情是,数学表达式会计算出结果,即使它放在一个还没有被调用的函数里,前提是这个表达式是自给自足的。这意味着如果在方程中使用了变量,那么这个方程在字节码中不会被触动,直到真正执行的时候才会被计算。函数需要被解释,而在这个过程中,我觉得你的值实际上是被计算出来的,这就导致了较慢的执行时间。我不太确定,但我强烈怀疑这种行为是根本原因。即使不是这样,你也得承认9<<(1<<26)确实让计算机受到了很大的压力,那里没有太多优化的空间。

In[73]: def create_number():
            return 9<<(1<<26)
In[74]: #Note that this seems instantaneous, but try calling the function!
In[75]: %timeit create_number()
#Python environment crashes because task is too hard

不过,这种测试有一点误导。当我用常规的timeit测试时,我得到了:

In[3]: from timeit import timeit
In[4]: timeit(setup = 'from __main__ import create_number', stmt = 'create_number()', number = 1)
Out[4]: .004942887388800443

另外要记住,打印这个值是不可行的,所以像这样的操作:

In[102]: 9<<(1<<26)

根本就不应该尝试。

为了更进一步的支持:

我感觉有点叛逆,于是决定看看如果我直接测试这个方程的执行时间会发生什么:

In[107]: %timeit 9<<(1<<26)
10000000 loops, best of 3: 22.8 ns per loop

In[108]: def empty(): pass
In[109]: %timeit empty()
10000000 loops, best of 3: 96.3 ns per loop

这真的很奇怪,因为显然这个计算比Python调用一个空函数的时间还要快,这显然是不可能的。我再说一遍,这并不是瞬间完成的,而可能与从内存中获取一个已经计算好的对象有关,并且重用这个值来计算表达式。总之,问题问得很好。

撰写回答