Verständnissfrage: Nuitka-Compiler

Alles, was nicht direkt mit Python-Problemen zu tun hat. Dies ist auch der perfekte Platz für Jobangebote.
Antworten
Benutzeravatar
microkernel
User
Beiträge: 271
Registriert: Mittwoch 10. Juni 2009, 17:27
Wohnort: Frankfurt
Kontaktdaten:

Moin,

ich habe letztens über die PyCon-Videos auf Youtube von den Nuitka Compiler erfahren. Als ich testweise ein paar meiner Python Skripte kompilierte sah ich, dass die kompilierten .exe Dateien nur einige wenige Kb groß waren (was für mich eigentlich ungewöhnlich für "kompilierte" Python Dateien ist, da normalerweise ja immer die ganzen DLLs und die Standardlib hinzugepackt wird). Auch in der Schnelligkeit ist eine deutliche Steigerung bemerkbar gewesen. Als ich das .exe Datei dann aber auf meinen Notebook ausführen wollte (Python ist dort nicht installiert) kam eine Python-ImportFehlermeldung.
Nun würde ich gerne wissen, was der Nuitka Compiler eigentlich wirklich macht und wozu er dient. Übersetzt er Python-Programme wirklich in C++?

V.G.,
microkernel
Benutzeravatar
jbs
User
Beiträge: 953
Registriert: Mittwoch 24. Juni 2009, 13:13
Wohnort: Postdam

Dir fehlen wahrscheinlich die shared libraries.
[url=http://wiki.python-forum.de/PEP%208%20%28%C3%9Cbersetzung%29]PEP 8[/url] - Quak!
[url=http://tutorial.pocoo.org/index.html]Tutorial in Deutsch[/url]
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

microkernel hat geschrieben:Nun würde ich gerne wissen, was der Nuitka Compiler eigentlich wirklich macht und wozu er dient. Übersetzt er Python-Programme wirklich in C++?
Nach einem kurzen Blick auf http://nuitka.net/pages/overview.html würde ich sagen, Nuitka übersetzt Python nicht vollständig in C++, sondern ersetzt den Kern des Python-Interpreters - eine Schleife, in der die Bytecodes-Befehle ausgeführt werden - durch äquivalente Befehle in C++, die aber immer noch auf die CPython-Funktionen des Laufzeitsystems zugreifen. Daher benötigt das Kompilat auch Zugriff auf eine CPython-Installation mit der Python-DLL und allen Python-Modulen.

Eine Python-Funktion

Code: Alles auswählen

def f(a): return a + 1
wird in einen Bytecode übersetzt, der ungefähr so aussieht:

Code: Alles auswählen

LOAD_FAST 0
LOAD_CONST 1
BINARY_ADD
RETURN_VALUE
Der CPython-Interpreter hat nun eine große Schleife, in der geprüft wird: Ist der Befehl "LOAD_CONST"? Dann nimm die Konstante und schiebe sie auf den Stapel für Zwischenergebnisse. Ist der Befehl "LOAD_FAST", dann nimm die erste lokale Variable, die gleichzeitig auch der erste Parameter der Funktion ist und schiebe sie auf den Stapel für Zwischenergebnisse. Ist der Befehl "BINARY_ADD", dann nimm zwei Werte vom Stapel, führe "+" aus und schiebe das Ergebnis wieder auf den Stapel. "RETURN_VALUE" nimmt schließlich einen Wert vom Stapel und übergibt ihn an die aufrufende Funktion. Dies nennt man auch "dispatch".

Ich rate, dass Nuitka nun in etwa dies erzeugt:

Code: Alles auswählen

PyObject userfunc_f(PyFrame *frame, PyObject *args, PyDict **kwargs) {
    frame->push(args[0]);
    frame->push(PyInteger_make(1));
    PyObject b = frame->pop();
    PyObject a = frame->pop();
    frame->push(PyObject_add_operator(a, b));
    return frame->pop();
}
D.h., die Bycode-Befehle werden quasi ausgerollt und eingebettet, was den "dispatch" und die Schleife einspart.

Man erkennt, dass es einige unnötige push()-Aufrufe gibt, denen sofort ein pop() folgt. Möglicherweise wird dies optimiert, etwa indem das oberste oder die beiden obersten Stackelemente in lokalen Variablen (die dann hoffentlich von C++ in CPU-Registern gehalten werden) abgelegt werden oder indem man statt die Stack-Maschine von CPython nachzuahmen, lieber eine Registermaschine benutzt, die überhaupt keinen Stack benutzt. Das sind typische Optimierungen, die im Rahmen von Forth-Compiler schon vor Jahrzehnten erforscht wurden.

Da die eigentliche Laufzeit aber in Aufrufen von Operationen wie "+" und dem Aufruf von Funktionen draufgeht, die alle noch genauso wie in CPython benutzt werden, ist auch kaum eine Geschwindigkeitssteigerung zu erkennen. Die Roadmap nennt ja den Punkt, dass man hofft, durch Typanalyse zu erkennen, dass z.B. "+" immer auf int-Objekten stattfindet, worauf man dann "+" einfacher gestalten könnte. Ich denke aber, dass einem hier sehr häufig die Flexibilität von Python in die Quere kommen wird.

Stefan
Zuletzt geändert von sma am Mittwoch 6. Juni 2012, 09:15, insgesamt 1-mal geändert.
BlackJack

Das würde ja bedeuten, dass Nuitka jetzt das gleiche macht wie Cython (ehemals Pyrex) und mal so etwas werden möchte wie eine statische Variante von Psyco‽
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

BlackJack hat geschrieben:Das würde ja bedeuten, dass Nuitka jetzt das gleiche macht wie Cython (ehemals Pyrex) und mal so etwas werden möchte wie eine statische Variante von Psyco‽
Möglich. Cython ist doch aber eher C mit einer Syntax wie Python, d.h. die Semantik wird nicht erhalten sondern ist nur ähnlich, oder?

Im Prinzip ist das Problem ja verstanden - behaupte ich mal, denn vor fast 20 Jahren ging es in meiner Diplomarbeit schon darum, CommonLisp in C zu übersetzen und der Teufel steckt eher im Detail der Semantik von Python. Doch zusammen mit Erkenntnissen aus den frühen 90ern, wie man VMs für dynamische Sprachen schnell bekommt (siehe V8 für eine Anwendung dieser Prinzipien) kann man denke ich recht gut abschätzen, wie schnell das ganze werden kann. Und es sollte auch klar sein, dass der Aufwand exponentiell ansteigt, wenn man immer schneller werden wil.

Stefan
deets

@sma

Das hast du falsch verstanden, was BlackJack meinte. CPython ist uA in der Lage, Python-Code einfach in die dazugehoerigen C-API-Calls umzusetzen. Statt Bytecode also (so wie von dir illustriert) C. Ohne weiter Optimierungen per se. Dann kommt dazu natuerlich noch die Anbindung an C/C++, und da mag er noch besseren Code generieren (man kann in Pyrex ja durchaus Typdeklarationen machen).

Alles in allem scheint mir das ein ziemlicher Unsinn was Nuitka macht. Dieser Weg ist schon so oft erfolglos begangen worden (Shedskin, Unladen Swallow) - die einzigen, die gekommen sind, um zu bleiben, sind die PyPy Jungs. Und deren Ergebnisse sind beeindruckend. Auch und gerade im Numpy Bereich. Ich ueberlege, ob ich auf den Sprint Ende Juli in Leipzig gehen soll.
BlackJack

@sma: Was meinst Du damit das die Semantik nicht erhalten bleibt? Wenn ich Cython gültiges Python gebe, sollte da etwas heraus kommen was sich genau wie der Python-Code verhält. Zumindest solange alle Konstrukte die man verwendet von Cython unterstützt werden. Ich weiss nicht ob und was es da gibt, Pyrex hat jedenfalls nicht alles abgedeckt, insbesondere „neue” Sachen wie Generatorausdrücke sind oder waren nicht möglich. Solange man nichts Cython-spezifisches in dem Quelltext hat, wird der Code in C übersetzt, der die Python-C-API direkt benutzt ohne über die Bytecode-Schleife gehen zu müssen. Dein Beispiel (nicht unbedingt schön zu lesen, weils nicht für Menschen gedacht ist):

Code: Alles auswählen

/* "_test.pyx":3
 * # -*- coding: utf-8 -*-
 * 
 * def f(a):             # <<<<<<<<<<<<<<
 *     return a + 1
 */

static PyObject *__pyx_pf_5_test_f(PyObject *__pyx_self, PyObject *__pyx_v_a); /*proto*/
static PyMethodDef __pyx_mdef_5_test_f = {__Pyx_NAMESTR("f"), (PyCFunction)__pyx_pf_5_test_f, METH_O, __Pyx_DOCSTR(0)};
static PyObject *__pyx_pf_5_test_f(PyObject *__pyx_self, PyObject *__pyx_v_a) {
  PyObject *__pyx_r = NULL;
  PyObject *__pyx_t_1 = NULL;
  __Pyx_RefNannySetupContext("f");
  __pyx_self = __pyx_self;

  /* "_test.pyx":4
 * 
 * def f(a):
 *     return a + 1             # <<<<<<<<<<<<<<
 */
  __Pyx_XDECREF(__pyx_r);
  __pyx_t_1 = PyNumber_Add(__pyx_v_a, __pyx_int_1); if (unlikely(!__pyx_t_1)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 4; __pyx_clineno = __LINE__; goto __pyx_L1_error;}
  __Pyx_GOTREF(__pyx_t_1);
  __pyx_r = __pyx_t_1;
  __pyx_t_1 = 0;
  goto __pyx_L0;

  __pyx_r = Py_None; __Pyx_INCREF(Py_None);
  goto __pyx_L0;
  __pyx_L1_error:;
  __Pyx_XDECREF(__pyx_t_1);
  __Pyx_AddTraceback("_test.f");
  __pyx_r = NULL;
  __pyx_L0:;
  __Pyx_XGIVEREF(__pyx_r);
  __Pyx_RefNannyFinishContext();
  return __pyx_r;
}
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Ich dachte, dass cython z.B. C-int statt Python-int benutzt und so einen int-Überlauf hat, aber offenbar passiert das nur, wenn man von Python-Syntax abweicht, und Variablen explizit mit einem Typ versieht. Also habt ihr recht und Cython ist ein semantisch äquivalentes Subset von CPython. Außerdem kann man in Cython dann noch direkt C-Funktionen oder C-Variablen mittels "cdef" benutzen, aber das ist ja noch einmal ein anderes Thema.

Mich wundert übrigens, dass das Ausrollen des Bytecode-Interpreters in Code wie den gezeigten eine messbare Performance-Steigerung bringt. Als ich mal in ceval.c von CPython gespricht hatte, meinte ich dort einen direct threaded code zu entdecken, der mittels berechnender gotos direkt zu dem nächsten Befehl springt und daher auch explizite Schleife mehr nutzt. Eigentlich müsste das Ausrollen und damit ewige Duplizieren der Implementierung der Bytecode-Befehle sich negativ auf den Prozessor-Cache auswirken. Wahrscheinlich spielt das aber alles keine große Rolle, weil das Referenzen zählen und die damit über den gesamten Heap gesprenkelten Änderungen von Speicher, gegenüber allem anderen dominiert. Oder ist der Zähler teil des Zeigers?

Meine Vermutung wäre, dass eine bessere GC-Strategie die vielversprechendeste Optimierung wäre - allerdings eine, die die Semantik von CPython ändert (weil __del__ effektiv ein noop wäre und externe Ressourcen wesentlich später automatisch freigegeben werden) und große Probleme für C-Erweiterungen bildet.

Eine weitere vielversprechende Optimierung wäre, das __dict__ von Exemplaren loszuwerden. Dies könnte man nach dem selben Prinzip wie bei V8 mit "hidden classes" zusammen mit einer Heuristik, die sich __init__ anschaut, machen, allerdings kommen bei Python erschwerend noch die Deskriptoren hinzu.

Ich würde allerdings nicht so weit gehen, und Nuitka als Unsinn zu bezeichnen. Allerdings versucht das Projekt ein schwieriges Problem zu lösen, welches am Anfang fieser Weise auch noch recht einfach aussieht und wo man durchaus vielversprechende Erfolge erzielen kann. Es gibt IMHO übrigens nichts, was Unladen Swallow falsch und PyPy nun richtig gemacht hat (beide wollten mit leicht unterschiedlichen das selbe erreichen), doch die PyPy-Jungs sind einfach zu stur zum Aufgeben :) Der Ansatz von US war schon genau der richtige und es waren auch Leute, die den aktuellen Stand der Forschung (wie gesagt, aus den frühen 90ern) kannten und umsetzen wollten. Python macht es einem nur nicht einfach.

Stefan
BlackJack

@sma: Cython rollt ja nicht nur den Bytecode aus, sondern speichert Zwischenergebnisse in lokalen temporären Variablen. Und vermeidet damit Push/Pops auf dem Python-Framestack; und wenn es nicht all zu viele temporäre Variablen sind, landen die nicht einmal auf dem C-Stack, sondern bleiben in Registern.
deets

sma hat geschrieben: Ich würde allerdings nicht so weit gehen, und Nuitka als Unsinn zu bezeichnen. Allerdings versucht das Projekt ein schwieriges Problem zu lösen, welches am Anfang fieser Weise auch noch recht einfach aussieht und wo man durchaus vielversprechende Erfolge erzielen kann. Es gibt IMHO übrigens nichts, was Unladen Swallow falsch und PyPy nun richtig gemacht hat (beide wollten mit leicht unterschiedlichen das selbe erreichen), doch die PyPy-Jungs sind einfach zu stur zum Aufgeben :) Der Ansatz von US war schon genau der richtige und es waren auch Leute, die den aktuellen Stand der Forschung (wie gesagt, aus den frühen 90ern) kannten und umsetzen wollten. Python macht es einem nur nicht einfach.
Wenn der Ansatz von US so goldrichtig war stellt sich die Frage, woran sie gescheitert sind... der dazugehoerige Blogpost erwaehnt mangelndes Interesse seitens Googles selbst. Was sein mag, aber der Rest der Welt scheint ja drauf gewartet zu haben.

Fuer mich bleibt ein Fakt bestehen: ausser PyPy hat noch *keiner* was geliefert, was auch nur ansatzweise die versprochenen Performance-Gewinne mit einer alltaeglichen Nutzbarkeit auf fuer nicht-trivialen Code geschafft hat. Was du stur nennst, wuerde ich umgekehrt mal als massive Unterschaetzung des Problems seitens der anderen ausdruecken ;)

Und ich bin auch der Ansicht, dass aufgrund der dynamischen Natur von Python alles ausser einem JIT letztlich zum scheitern verurteilt ist (ausser man betrachtet die in Py3 moeglichen Typannotationen, die ja aber nicht wirklich weit verbreitet geschweige denn standardisiert sind).

Denn schon zur statischen compile-Zeit da irgendwelche Annahmen zu machen wird der dynamischen Natur von Python halt nicht gerecht.
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Es gibt sogar ein Paper (von Brett Cannon?) dass genau aufzeigt wieso Typinferenz in Python nicht funktioniert. Ich würde behaupten dass es für einen Compiler unmöglich ist wesentlich besser als CPython zu sein.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Falls DasIch diesen Artikel von Brett Cannon meint: Ich habe nur kurz überflogen, doch ich denke, er beweist nicht, dass Typinferenz prinzipiell nicht funktioniert, sondern zeigt nur, dass der vorgeschlagene Ansatz nicht die erhoffte positive Wirkung hatte. "Introducing over 3,000 lines of new C code to Python’s compiler to get, at best, a 1% improvement is in no way justified." Weiter heißt es denn, dass 5% Geschwindigkeitssteigerung das Ziel gewesen wäre und ehrlich gesagt, dass ist IMHO auch noch viel zu wenig.

In dem Artikel geht es um Python 2.3.5. Ich meine, dort benutzte die Hauptschleife noch ein einfaches switch-case. Dieses durch direct threaded code zu ersetzen bringt (bzw. brachte) wahrscheinlich bereits die gewünschten 5% Performance-Steigerung.

Aus dem Self-Projekt (so ca. 1987-1995) gibt es übrigens die Erkenntnis, dass man dynamisch typisiert fast immer genau so schnell sein kann wie statisch typisiert, d.h. statische Typisierung ist keine notwendige Voraussetzung für gute Performance. Eine VM, die Inlining und Polymorphic Inlines Caches (PICs) benutzt, erzeugt "automatisch" die Optimierungen, die sonst ein Compiler statisch auf Grund der Typisierung erzeugen würde. Cannon zitiert zwar zwei Artikel aus dem Projekt, aber AFAICT nur in dem Abschnitt "das müsste man mal ausprobieren".

Woran US gescheitert ist, kann ich letztlich nur raten. Man wollte 100% kompatibel bleiben und idealer Weise CPython weiterentwickeln, was jedoch das CPython-Projekt nicht wollte. Die schienen nicht glücklich mit der Vorstellung, CPython hängt auf einmal von der LLVM an. Ich vermute, es war intern für Google jedoch wichtig, nicht einen eigenen Fork zu machen. Man hat dann den Performance-Verlust durch LLVM unterschätzt. Was die bessere VM gebracht hat, brachte nichts gegen den erhöhten Aufwand, dynamisch LLVM-Bytecode zu erzeugen und diesen von der VM in Maschinencode zu übersetzen. Ich habe es nicht so genau verfolgt, aber ich meine, die LLVM ist nicht dafür ausgelegt, nur hotspots zu übersetzen oder type feedback zu sammeln und daran inlining und PICs zu orientieren. Somit war das eigentlich die falsche VM und der Aufwand, jetzt noch "schnell" eine eigene VM zu bauen, war nicht geplant. Dann waren drei Leute (vielleicht nur mit 20%) einfach nicht genug. Man schaue sich an, wie viel Arbeit in V8 geflossen ist. Das dürften bislang einige Mannjahre gewesen sein. An dem Ding, das wahrscheinlich das meiste gebracht hätte: Der Speicherverwaltung, durften bzw. wollten sie nicht drehen, da eine selbstgestellte Forderung war, dass das C-API unverändert erhalten bleiben muss. Schließlich haben sie sich durchaus realistische Benchmarks wie ein großes Django-Projekt ausgesucht, wo die Optimierungen nicht wirklich gegriffen haben, weil letztlich gar nicht so viel Python-Code läuft, wenn man I/O betreibt, reguläre Ausdrücke auswertet oder mit Sockets spricht. Die erreichten 10-20% Leistungssteigerung haben denke dann dazu geführt, das Google das Projekt beendet hat, weil es das Ergebnis nicht im Verhältnis zum Aufwand stand. Wenn es darum geht, irgendeinen in Python geschriebenen Server für blogger oder youtube schneller zu machen, ist es irgendwann einfacher, das Ding lieber in Java oder einer anderen Sprache neu zu schreiben (indem man einen automatischen Übersetzer benutzt) anstatt zu versuchen, mit viel Aufwand CPython ein bisschen schneller zu machen. Oder man nimmt Pypy, wo die Entwickler nicht erklären müssen, ob ihre Arbeit auch die Zeit und das Geld wert ist, sondern sie einfach aus Spaß an der Freude und um die Welt ein bisschen Besser zu machen, daran arbeiten.

Stefan
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Wie sieht es eigentlich aktuell mit PyPy aus?

Mein letzter Stand war, das ich PyPy mit Django praktisch einsetzten könnte, wenn es eine Stabile Datenbank Anbindung gibt.

Mittlerweile scheint es es einige DB Anbindungen zu geben:
https://bitbucket.org/pypy/compatibilit ... b-adaptors

Praktisch ist anscheinend MySQL nicht einfach nutzbar.
MySQLdb benötigt einen Patch: https://bitbucket.org/pypy/compatibilit ... sql-python
mysql-ctypes scheint auch nicht so ohne weiteres zu funktionieren:
https://github.com/quora/mysql-ctypes/issues/10
https://github.com/quora/mysql-ctypes/pull/11

Was ohne Probleme funktioniert ist SQLite und PostgreSQL soll auch funktionieren...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Na dann steht einer Migration auf PostgreSQL nichts mehr im Wege.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
deets

Ich habe eh noch nie verstanden warum man MySQL verwenden mag... Postgres war immer schon feature-reicher und einfacher zu administrieren. Mag sein das MySQL seine Spezialecken hat - aber das es den Default darstellt? Mit ohne referentieller Integritaet und Co? Seltsam.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

deets hat geschrieben:Seltsam.
Ich stimme dir zu, habe seit *Jahren* PostgreSQL und bin zufrieden damit (was ich sogar noch seltsamer finde ist, dass PostgreSQL langsam dabei ist, die ganzen NoSQL-Erweiterungen aufzunehmen, keinen KV-Store hats bereits und JSON-Support ebenfalls). Aber dass MySQL als default selten ist, finde ich nicht erstaunlich. Genausowenig wie PHP. Wenn du mal in nen Buchladen gehst, dann siehst du stapelweise Bücher auf denen steht "PHP/MySQL", in so einem Mantra dass man als Einsteiger gar nicht weiß, dass es da eine Alternative gibt.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
jbs
User
Beiträge: 953
Registriert: Mittwoch 24. Juni 2009, 13:13
Wohnort: Postdam

Und in die Nudeln gehört kein Öl.

Machen trotzdem viele.
[url=http://wiki.python-forum.de/PEP%208%20%28%C3%9Cbersetzung%29]PEP 8[/url] - Quak!
[url=http://tutorial.pocoo.org/index.html]Tutorial in Deutsch[/url]
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Ich nutzte halt MySQL noch, weil ich mich da einfach mehr auskenne mit der Administration und MySQL macht eigentlich was es soll...
In PostgreSQL müsste ich mich erst wieder einarbeiten. In Zusammenhang mit pypy macht das aber am Ende vielleicht doch Sinn, zeit zu investieren. Das dumme ist halt, Zeit ist ständig knapp...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
lunar

@deets: MySQL 4 zwingt den Nutzer nicht, Datenbankentwurf zu lernen, ebenso wie PHP den Nutzer nicht zwingt, Programmieren zu lernen. Mithin der ideale Partner für PHP und dementsprechend erfolgreich.

Aktuelle MySQL-Versionen sind allerdings längst nicht mehr so schlecht wie ihr Ruf, PostgreSQL zwar in vielem unterlegen, aber dennoch brauchbar, sofern richtig konfiguriert. Replikation konnte MySQL übrigens lange Zeit tatsächlich besser als PostgreSQL.
Benutzeravatar
/me
User
Beiträge: 3554
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

lunar hat geschrieben:Replikation konnte MySQL übrigens lange Zeit tatsächlich besser als PostgreSQL.
Siehe dazu auch ein Posting eines Hosting-Anbieters von Ende 2009. (http://www.python-forum.de/viewtopic.ph ... 90#p154290)
Antworten