2015-12-06 8 views
48

Ich habe gefragt, this question vor über das Töten eines Prozesses, der zu viel Speicher verwendet, und ich habe die meisten einer Lösung ausgearbeitet.Warum hängt Python "präemptiv" beim Versuch, eine sehr große Zahl zu berechnen?

Es gibt jedoch ein Problem: Die Berechnung massiver Zahlen scheint von der Methode, die ich verwenden möchte, nicht betroffen zu sein. Dieser Code soll ein 10-Sekunden-CPU-Zeitlimit für den Prozess festlegen.

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) 

Was erwarte ich zu sehen, wenn ich dieses Skript ausführen (auf einer Unix-Maschine), ist dies:

-1 -1 
ran out of time! 

Stattdessen habe ich keine Ausgabe erhalten. Der einzige Weg, ich Ausgang bekommen, ist mit Ctrl + C, und ich dies, wenn ich Ctrl + C nach 10 Sekunden:

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

Wenn ich Ctrl + Cvor 10 Sekunden, dann muss ich es zweimal tun, und die Konsolenausgabe sieht so aus:

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

Im Laufe des Experimentierens und versuchen, dies herauszufinden, habe ich auch time.sleep(2) zwischen der Druck-und große Zahl Berechnung gesetzt. Es scheint keine Wirkung zu haben. Wenn ich y = 10**(10**10) zu y = 10**10 ändere, funktionieren die Print- und die Sleep-Anweisung wie erwartet. Das Hinzufügen von flush=True zu der print-Anweisung oder sys.stdout.flush() nach der print-Anweisung funktioniert auch nicht.

Warum kann ich die CPU-Zeit für die Berechnung einer sehr großen Zahl nicht begrenzen? Wie kann ich das beheben oder zumindest mildern?


Zusätzliche Informationen:

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

Linux Informationen: 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

Ich kann auf 3.4.3 aber nicht 2.7.9 reproduzieren. Das ist gruselig. – senshin

+0

Hmm ... Ich bekomme immer 'SystemError: PyEval_EvalFrameEx hat ein Ergebnis mit einer Fehlermenge zurückgegeben 'was auch immer vor oder nach 10 Sekunden auf meinem' Arch Linux 4.2.5-1-ARCH' mit Python 3.5. Und wenn ich Python 2.7 benutze den Code, funktioniert es gut als Ihre erwartete Ausgabe. –

+2

Ich rate *, dass Python versucht, durch die Berechnung von Konstanten im voraus zu optimieren ... was zu Fehlzündungen führt. Aber ich habe keinen Beweis dafür. –

Antwort

12

eine Funktion verwenden.

Es scheint, dass Python versucht, ganzzahlige Literale vorzuberechnen (ich habe nur empirische Beweise; wenn jemand eine Quelle hat, lass es mich wissen). Dies wäre normalerweise eine hilfreiche Optimierung, da die meisten Literale in Skripts wahrscheinlich klein genug sind, um beim Vorberechnen keine merklichen Verzögerungen zu verursachen. Um dies zu umgehen, muss Ihr Literal das Ergebnis einer nicht konstanten Berechnung sein, z. B. eines Funktionsaufrufs mit Parametern.

Beispiel:

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() 

Dies gibt das erwartete Ergebnis:

[email protected]:~/Desktop$ time python3 hang.py 
-1 -1 
ran out of time! 

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

Dies ist eine ausgezeichnete Beobachtung! Beachten Sie jedoch, dass es nicht der 'timeRanOut'-Handler ist, der den Prozess beendet. Es ist der Fehler, 'f()' rechtzeitig zu berechnen, was zu einem 'ValueError' führt, der irgendwo in Pythons VM-Code ausgelöst wird. – 9000

+0

@ 9000 Leider bin ich mit dem 'resource' Modul nicht sehr vertraut. Ich glaube, es wirft einen "ValueError", weil das CPU-Ressourcenlimit auf einen höheren Wert als mein normales Systemlimit gesetzt ist. Ich werde etwas mehr experimentieren ... – Mego

+0

Interessanterweise funktioniert Ihr Code auf meinem Ubuntu 14.04 wie erwartet (kein 'ValueError'), sowohl unter Python 2.7 als auch 3.4. Ich vermute, dass es wenig mit dem Python-Modul und mehr mit Limits in der Shell zu tun hat. – 9000

51

TLDR: Python vorberechnet Konstanten im Code.Wenn eine sehr große Anzahl mit mindestens einem Zwischenschritt berechnet wird, wird der Prozess CPU-zeitlich begrenzt sein.


Es dauerte ein ganz bisschen suchen, aber ich habe Beweise entdeckt, dass Python 3 Precompute konstante Literale tut, die es vor der Bewertung etwas im Code findet. Eine davon ist diese Webseite: A Peephole Optimizer for Python. Ich habe unten einige davon zitiert.

ConstantExpressionEvaluator

This class precomputes a number of constant expressions and stores them in the function's constants list, including obvious binary and unary operations and tuples consisting of just constants. Of particular note is the fact that complex literals are not represented by the compiler as constants but as expressions, so 2+3j appears as

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

This class converts those to

LOAD_CONST q (2+3j)

which can result in a fairly large performance boost for code that uses complex constants.

Die Tatsache, dass 2+3j als Beispiel legt nahe, sehr verwendet wird, stark, dass nicht nur kleine Konstanten vorberechnet und im Cache gespeichert werden, sondern auch alle konstanten Literale im Code. Ich fand auch this comment auf einem anderen Stapel   Überlauf Frage (Are constant computations cached in Python?):

Note that for Python 3, the peephole optimizer does precompute the 1/3 constant. (CPython specific, of course.) – Mark Dickinson Oct 7 at 19:40

Diese durch die Tatsache unterstützt, dass

y = 10**(10**10) 

mit diesem auch hängt zu ersetzen, obwohl ich nie nennen die Funktion!

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

Die gute Nachricht

Zum Glück für mich, ich habe keine solche riesigen wörtlichen Konstanten in meinem Code. Irgendeine Berechnung solcher Konstanten wird später passieren, was durch das CPU-Zeitlimit begrenzt sein kann und ist. Ich änderte

y = 10**(10**10) 

dazu

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

und bekam diese Ausgabe, wie gewünscht!

-1 -1 
10 
10000000000 
ran out of time! 

Die Moral der Geschichte: einen Prozess durch CPU-Zeit oder Speicherverbrauch (oder ein anderes Verfahren) Die Begrenzung wird funktionieren, wenn es nicht eine große literale Konstante ist zu precompute in dem Code, der Python versucht, .

+3

Interessant (für mich), dass auf Windows 7 64Bit py 2.6.6 begann, mehrere Kerne zu verwenden. – Marichyasana

Verwandte Themen