为什么Python在尝试计算非常大的数字时会“先发制人”地挂起?

时间:2015-12-06 03:14:10

标签: python linux

我之前问过this question关于杀死使用过多内存的进程,我已经解决了大部分问题。

然而,有一个问题:计算大量数字似乎没有被我试图使用的方法所触及。下面的代码旨在为进程设置10秒的CPU时间限制。

import resource
import os
import signal

def timeRanOut(n, stack):
    raise SystemExit('ran out of time!')
signal.signal(signal.SIGXCPU, timeRanOut)

soft,hard = resource.getrlimit(resource.RLIMIT_CPU)
print(soft,hard)
resource.setrlimit(resource.RLIMIT_CPU, (10, 100))

y = 10**(10**10)

当我运行此脚本(在Unix计算机上)时,我期望看到的是:

-1 -1
ran out of time!

相反,我没有输出。我获得输出的唯一方法是使用 Ctrl + C ,如果我 Ctrl + C 之后我得到这个10秒:

^C-1 -1
ran out of time!
CPU time limit exceeded

如果在 10秒之前我 Ctrl + C ,那么我必须做两次,并且控制台输出如下所示:< / p>

^C-1 -1
^CTraceback (most recent call last):
  File "procLimitTest.py", line 18, in <module>
    y = 10**(10**10)
KeyboardInterrupt

在试验和尝试解决这个问题的过程中,我还在打印和大数计算之间放了time.sleep(2)。它似乎没有任何影响。如果我将y = 10**(10**10)更改为y = 10**10,则print和sleep语句将按预期工作。在print语句之后将flush=True添加到print语句或sys.stdout.flush()也不起作用。

为什么我不能限制CPU时间来计算一个非常大的数字?我该如何修复或至少减轻这个?

其他信息:

Python版本:3.3.5 (default, Jul 22 2014, 18:16:02) \n[GCC 4.4.7 20120313 (Red Hat 4.4.7-4)]

Linux信息:Linux web455.webfaction.com 2.6.32-431.29.2.el6.x86_64 #1 SMP Tue Sep 9 21:36:05 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux

2 个答案:

答案 0 :(得分:51)

TLDR: Python预先计算代码中的常量。如果使用至少一个中间步骤计算任何非常大的数字,则进程为CPU时间限制。

我花了很多时间进行搜索,但是我发现Python 3 确实预先计算了它在评估任何内容之前在代码中找到的常量文字的证据。其中一个是此网页: A Peephole Optimizer for Python 。我在下面引用了一些内容。

  

ConstantExpressionEvaluator

     

该类预先计算了许多常量表达式,并将它们存储在函数的常量列表中,包括明显的二进制和一元操作以及仅由常量组成的元组。特别值得注意的是,复杂的文字不是由编译器表示为常量而是表达式,因此2 + 3j显示为

     

LOAD_CONST n (2)   LOAD_CONST m (3j)   BINARY_ADD

     

此类将这些转换为

     

LOAD_CONST q (2+3j)

     

可以为使用复杂常量的代码带来相当大的性能提升。

2+3j为例非常强烈地表明,不仅小常量被预先计算和缓存,而且代码中的任何常量文字也是如此。我还在另一个Stack Overflow问题( this comment )上找到了Are constant computations cached in Python?

  

请注意,对于Python 3,窥孔优化器 预先计算1/3常量。 (特别是CPython。) - Mark Dickinson 10月7日19:40

替换

这一事实支持了这些
y = 10**(10**10)

这个挂起,即使我从不调用该函数!

def f():
    y = 10**(10**10)

好消息

幸运的是,我的代码中没有任何这样的巨大字面常量。此类常量的任何计算都将在以后发生,这可能会受到CPU时间限制的限制。我改变了

y = 10**(10**10)

到此,

x = 10
print(x)
y = 10**x
print(y)
z = 10**y
print(z)

并根据需要获得此输出!

-1 -1
10
10000000000
ran out of time!

故事的寓意:通过CPU时间或内存消耗(或其他一些方法)限制进程将起作用如果没有大的文字常量Python试图预先计算的代码。

答案 1 :(得分:12)

使用功能。

似乎Python试图预先计算整数文字(我只有经验证据;如果有人有来源请告诉我)。这通常是一种有用的优化,因为脚本中的绝大多数文字可能小到足以在预计算时不会出现明显的延迟。要解决这个问题,你需要使你的文字成为非常数计算的结果,比如带参数的函数调用。

示例:

import resource
import os
import signal

def timeRanOut(n, stack):
    raise SystemExit('ran out of time!')
signal.signal(signal.SIGXCPU, timeRanOut)

soft,hard = resource.getrlimit(resource.RLIMIT_CPU)
print(soft,hard)
resource.setrlimit(resource.RLIMIT_CPU, (10, 100))

f = lambda x=10:x**(x**x)
y = f()

这给出了预期的结果:

xubuntu@xubuntu-VirtualBox:~/Desktop$ time python3 hang.py
-1 -1
ran out of time!

real    0m10.027s
user    0m10.005s
sys     0m0.016s