
On 2012-03-28, Stefan Behnel wrote:
Im reinen C-Teil verwende ich ja absichtlich _nicht_ die Python-Datenstrukturen
Hatte ich auch so verstanden. Der Code oben (deiner und meiner) dient nur der Konvertierung des Ergebnisses von C nach Python.
Der Code, den Cython generiert, hat nur minimal höheren Aufwand als dein handgeschriebener - er erzeugt die ursprüngliche Liste nämlich momentan nicht mit der finalen Länge. Und das auch nur deshalb, weil ich immer noch keine Zeit gefunden habe, die bekannte Länge der Ergebnisliste aus dem Iterator durchzuschleifen. Sprich: mit ein bisschen zusätzlichem Aufwand würde Cython sogar identischen Code generieren, ohne deinen ganzen C Eiertanz oben.
Allerdings würde es mich überraschen, wenn der Geschwindigkeitsunterschied jetzt schon sehr auffällig wäre. Der eher meistens vernachlässigbar, weil die Konvertierung der Listeneinträge normalerweise deutlich stärker zu Buche schlägt als die Vergößerung der Liste, die meistens ohnehin von der Speicherverwaltung aufgefangen wird.
Es geht bei der ganzen Sache um einen rein numerischen Algorithmus, und da ist Python nun mal prinziell weniger gut für geeignet, weil dort alles Objekte sind, für _jede_ Berechnung also erst die "Nutzlast" aus dem Objekt geholt werden muss. Das ist zwar sehr schön für die Allroundverwendung (also mehr als 99% der real anfallenden Fälle, s.u.), aber ein klarer Nachteil bei einer so speziellen Geschichte. Und weil es eben reine Berechnungen sind, sieht der C-Code auch kaum weniger übersichtlich aus als in Python. Ich gewinne also durch Verwendung von Python an der Stelle nichts, es wird nur langsamer. Anders siehst das z.B. aus, wenn man mit Datenbanken arbeitet, und möchte SQL auf die Applikationssprache abbilden. Ich verwende z.Z. auf der Arbeit Java und da ist das schon ein ganz schöner Eiertanz, wenn man Java double oder int einfach nur in ein Dictionary einfügen möchte oder etwa statt int SQL NULL bekommt. Alleine die Menge der try/catch/throws bläht den Code gegenüber Python ebenso gewaltig wie vollkommen unnötig auf. In Python (etwa zusammen mit psycopg2) erhält man dann ein sauberes und eindeutiges None und kein integer 0!. Alleine auf die Idee zu kommen, SQL NULL::int auf 0 ist mappen, ist krank und hochgradig fehlerträchtig. Und selbstverständlich kann ich in Python auch Ganzzahlen in ein Dictionary einfügen - ohne erst eine Wrapper-Klasse dafür anlegen zu müssen.
, weil ich eben schneller werden will (was auch weit über alle Erwartungen geklappt hat ;-). Gerade die dauernden appends, bzw. auch Listcomprehensions, wie Du sie erwähntest (ich weiß, die sind sehr elegant und ich verwende die auch sehr gerne), holen sich jedes Mal Speicher vom Heap, der praktisch dann gleich wieder freigegeben werden kann
Äh, bitte wie? Warum sollten sie das denn tun?
Wenn man Listen so erzeugt, wird jedesmal Speicher vom Heap geholt und der alte Speicherbereich kann freigegeben werden.
während der mir sehr wohl bekannte wirkliche Speicherverbrauch dieses Algorithmus _sehr_ überschaubar ist. Entsprechend dramatisch war dann auch der Speedup (mehr als Faktor 1000).
Aber nur gegenüber CPython, richtig?
Ja. Und ich muss auch sagen, dass ich bei der Umstellung diverse Schwächen des Python-Codes erkannt, und dann natürlich in C entsprechend geändert habe. So wurden z.B. in in der Pythonversion zwecks Abstandsberechnungen in der inneren Schleife jeweils Quadratwurzeln gezogen. Allerdings fiel mir jetzt auf, dass ich ja ebenso die Quadrate der Abstände vergleichen kann (es interessiert hier nur ein <=, kein ==), die eh einen Schritt vorher anfallen. Wenn man das jetzt in Python ebenfalls umstellen würde, wozu ich aber nun keine Lust habe, sähe das Ergebnis mit Sicherheit nicht ganz so schlecht für Python aus. [...]
Schau in die Doku (wie Dietmar es für dich getan hat). Unnötig zu sagen, dass Cython dir diese Details abnimmt.
Ich werde mir Cython wohl ebenfalls mal anschauen müssen :) Bernd -- "Die Antisemiten vergeben es den Juden nicht, dass die Juden Geist haben - und Geld." [Friedrich Nietzsche]

Bernd Nawothnig, 01.04.2012 11:29:
Es geht bei der ganzen Sache um einen rein numerischen Algorithmus, und da ist Python nun mal prinziell weniger gut für geeignet, weil dort alles Objekte sind, für _jede_ Berechnung also erst die "Nutzlast" aus dem Objekt geholt werden muss. Das ist zwar sehr schön für die Allroundverwendung (also mehr als 99% der real anfallenden Fälle, s.u.), aber ein klarer Nachteil bei einer so speziellen Geschichte.
Deswegen benutzen die Number-Cruncher ja auch NumPy und/oder Cython (und manchmal PyPy).
, weil ich eben schneller werden will (was auch weit über alle Erwartungen geklappt hat ;-). Gerade die dauernden appends, bzw. auch Listcomprehensions, wie Du sie erwähntest (ich weiß, die sind sehr elegant und ich verwende die auch sehr gerne), holen sich jedes Mal Speicher vom Heap, der praktisch dann gleich wieder freigegeben werden kann
Äh, bitte wie? Warum sollten sie das denn tun?
Wenn man Listen so erzeugt, wird jedesmal Speicher vom Heap geholt und der alte Speicherbereich kann freigegeben werden.
Ah, du meinst das realloc(). Nein, da wird im Normallfall kein neuer Speicherbereich alloziiert, sondern nur der alte erweitert. Entweder macht das CPython selbst in seiner Heap-Verwaltung, indem es sich schon vorab größere Bereiche greift, oder das Betriebsystem macht es, notfalls indem es die virtuelle Speicherverwaltung umkonfiguriert. Manchmal muss kopiert werden, aber wenn alles gut läuft, kann das eben auch komplett vermieden werden. Unter'm Strich geht der zusätzliche Aufwand gegen Null. Stefan
participants (2)
-
Bernd Nawothnig
-
Stefan Behnel