【问题标题】:Why does Python "preemptively" hang when trying to calculate a very large number?为什么 Python 在尝试计算一个非常大的数字时会“抢先”挂起?
【发布时间】:2015-12-06 03:14:09
【问题描述】:

我之前问过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

如果我 Ctrl + C before 10 秒,那么我必须这样做两次,控制台输出如下所示:

^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 语句中添加 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

【问题讨论】:

  • 我可以在 3.4.3 上重现,但不能在 2.7.9 上重现。这太吓人了。
  • 嗯...我总是在我的Arch Linux 4.2.5-1-ARCH 上使用 Python 3.5 在 10 秒之前或之后得到SystemError: PyEval_EvalFrameEx returned a result with an error set。如果我使用 Python 2.7 运行代码,它可以作为您的预期输出正常工作。
  • 猜测 Python 正试图通过预先计算常量来进行优化……结果适得其反。但我没有这方面的证据。
  • @El'endiaStarman:Python 确实 尝试在执行之前预先计算这些常量。在模块proof.py 中写入def f(): return 10**(10**10),然后尝试import proof,然后看着它吃掉你所有的CPU。
  • (相关,虽然没有一个答案真的令人满意:stackoverflow.com/q/24217053

标签: python linux


【解决方案1】:

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 溢出问题 (Are constant computations cached in Python?) 中找到了 this comment

请注意,对于 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 尝试预先计算的代码。

【讨论】:

  • 有趣的是(对我来说)在 windows 7 64Bit py 2.6.6 上它开始使用多个内核。
【解决方案2】:

使用函数。

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

【讨论】:

  • 这是一个很好的观察!但请注意,结束进程的不是timeRanOut 处理程序。未能按时计算f() 导致ValueError,在Python 的VM 代码中某处提出。
  • @9000 不幸的是,我对resource 模块不是很熟悉。我相信它会抛出ValueError,因为 CPU 资源限制被设置为高于我的正常系统限制的值。我会再做一些实验......
  • 有趣的是,在 Python 2.7 和 3.4 下,您的代码在我的 Ubuntu 14.04 上完全按预期工作(没有 ValueError)。我怀疑它与 Python 模块关系不大,更多的是与你的 shell 中设置的限制有关。
  • @9000 几乎可以肯定是这样——我在 Windows 中的 Cygwin 下运行它,很多与 POSIX 和资源限制相关的东西都不能正常工作(如果有的话)。我责怪没有阅读标签。
猜你喜欢
  • 2022-01-20
  • 2016-05-06
  • 1970-01-01
  • 1970-01-01
  • 2015-02-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-20
相关资源
最近更新 更多