Schnelle for Schleifen mit Python

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Hartmannsgruber
User
Beiträge: 89
Registriert: Mittwoch 15. Januar 2014, 22:30
Wohnort: Bad Kötzting
Kontaktdaten:

Servus Forum,

allgemeine Frage, for Schleifen sind in Python recht langsam.
Welche Möglichkeiten gibt es diese zu "beschleunigen", sodass über größere Datenmengen (30.000 Einträge) schnell iteriert werden kann?
Ich habe schon von Cython und ähnlichen gelesen.

LG
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Die schnellsten for-Schleifen sind die, die vermieden werden. Welches Problem willst du tatsächlich lösen?
In specifications, Murphy's Law supersedes Ohm's.
narpfel
User
Beiträge: 645
Registriert: Freitag 20. Oktober 2017, 16:10

Die zweitschnellsten `for`-Schleifen gibt es mit PyPy. In Kombination mit C-Extensions (pandas, numpy, etc.) ist es allerdings nicht wirklich schnell.

30k Werte sind jetzt auch nicht so viel, dass die Iteration an sich langsam wäre. Was machst du denn in der Schleife?
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

Hartmannsgruber hat geschrieben: Dienstag 1. Juni 2021, 15:13 allgemeine Frage, for Schleifen sind in Python recht langsam.
Welche Möglichkeiten gibt es diese zu "beschleunigen", sodass über größere Datenmengen (30.000 Einträge) schnell iteriert werden kann?
Hm, "allgemeine Frage"... also hast Du keinen konkreten Anwendungsfall, verstehe ich das richtig?

Ich sag's mal so: Performanceoptimierung steht idealerweise erst am Ende eines Entwicklungsprozesses, der -- im besten Fall -- dem einfachen Paradigma von Kent Beck folgt: "make it work, make it right, make it fast". Die anderen beiden Leitsätze zur Performanceoptimierung lauten: "Premature optimization is the root of all evil" (Donald E. Knuth) und "Measure, don't guess" (Kirk Pepperdine).

Der wesentliche Kernpunkt des Ganzen ist, aus meiner Sicht: immer wenn Du "per default" etwas in Deinen Code einbaust, weil Du glaubst, daß es schneller sei, schießt Dir Dein Code hinterher in die Füße. Denn jede vorzeitige Optimierung, die Du selbst machst, macht Deinen Code schlechter les-, wart- und erweiterbar, führt häufig dazu, daß Dein Compiler oder Interpreter Deinen Code weniger gut optimieren kann, und am Ende stellst Du dann fest, daß Du häßlichen Code hast, dessen tatsächliche Bottlenecks ganz woanders sind, als Du vermutet hattest. Oh! 8-O

Ein ganz gutes Beispiel dafür kann man häufig bei Java-Entwicklern sehen. Da ist Multithreading eine ganz simple Angelegenheit, und im Gegensatz zu Python gibt es auch kein Global Interpreter Lock. Das führt leider dazu, daß ich schon oft und vor allem auch bei durchaus sehr professionellen Java-Entwicklern gesehen habe, daß die jeden Pippifax in einen eigenen Thread packen und sich am Ende wundern, warum das Ganze immer langsamer wird und das Betriebssystem unter seiner Systemlast erstickt. Klar: so ein Thread ist ein bisschen leichtgewichtiger als ein "richtiger" Prozeß, aber nicht so sehr, daß eine Vielzahl von Threads nicht irgendwann anfängt, eine erhebliche Belastung für die Prozeßverwaltungskomponenten (genauer: die Scheduler) des Systems darzustellen. Die müssen ja die aktuellen Abbilder der aktuell weniger hoch priorisierten Prozesse plus Instruction Pointer etc. erstmal speichern, dann die höchstpriorisierten Prozesse laden und wieder starten... that is not for the faint of heart und natürlich belastet das auch (mindestens) den Speicher-I/O. Und da der Kernel eines Systems nun einmal in Ring0 läuft und vor allem einige Scheduler, vor allem die Prozeß-Scheduler, mit maximaler Priorität laufen... genau. Besonders unschön ist dann an solchen Konstellationen, daß es häufig einen signifikanten Aufwand und große Umbauten bis in die Tiefen der Architektur bedeutet, eine auf diese Weise einmal "verhunzte" Software wieder glattzuziehen. BTDT, got the t-shirt.

Wenn Du Deine Python-Software ernsthaft optimieren willst, dann halte Dich an die oben genannten Leitsätze. Schau erst, daß Deine Software läuft ("make it run"), dann sorg' dafür, daß sie das Richtige oder Gewünschte tut ("make it work"), und erst dann, wenn diese beiden Punkte erfüllt sind, kannst Du Dich sinnvoll darauf konzentrieren, sie zu optimieren. Und zwar, indem Du die Ausführungszeiten und -Häufigkeiten einzelner Komponenten mißt -- dafür gibt es eigens bestimmte Software, die "Profiler" genannt wird, Python hat davon gleich mehrere und auf den darunter liegenden Ebenen gibt es noch eine Menge mehr.

Vielleicht magst Du angelegentlich über folgendes Beispiel nachdenken:

Code: Alles auswählen

def foo(*args): pass # ich brauche eine Zehntelekunde
def bar(*args): pass # ich brauche 100 Sekunden
Jemand, der in etwa so wie Du arbeiten wollen würde, käme jetzt auf die Idee, viel Zeit und Aufwand in die Optimierung der Funktion "bar()" zu investieren. Wenn unser jemand aber Pech hat, dann wird die Funktion "bar()" bei jedem Programmlauf nur einmal aufgerufen, die Funktion "foo()" aber zehn Millionen mal. Huch.

Nebenbei gibt es noch so ein paar Kleinigkeiten, auf die man achten sollte, zum Beispiel das Betriebssystem. Wenn das große Datenmengen von einem Persistenzmedium wie einer Festplatte, einer SSD, oder einer NVME liest, oder vielleicht aus einer Netzwerkressource... solche Dinge kann man meistens nur dann wirklich schneller machen, indem man sie ausbaut, also Persistenzmedien zum Beispiel auf ein schnelles RAID legt oder beim Netzwerk auf modernere Technologien wie Bonding und / oder 100 GbE setzt. Bei CPU-lastigen Workloads ist es oft sinnvoller, die Workload zu verteilen, sei es lokal über Multiprocessing oder womöglich über ein Netzwerk wie beispielsweise Apache Spark, Apache Storm, Gearpump, Heroku oder das gute alte Hadoop. Häufig können dabei auch echte Clustermanagmentsysteme wie Kubernetes, OpenStack und Ähnliche helfen, aber dabei geht es dann schon in Richtung Distributed Computing und das ist... wie soll ich sagen... eine eigene Disziplin für sich. ;-) #

Unter Python bieten sich bei cpu-intensiven Workloads oft noch ganz andere Möglichkeiten an. Die Wahl einer anderen Ausführungsmethode zum Beispiel, also: anstatt den standardmäßigen und (by design) nicht performanceoptimierten Standardinterpreter CPython zu benutzen, könntest Du PyPy oder einen anderen Interpreter benutzen (wobei ich mit Jython wenig und mit IronPython gar keine Erfahrung habe, ich mag Java nicht und benutze kein Windows). Oder Du könntest die Teile Deines Python-Code, die Du als zu langsam erkennst, in eine C- oder C++-Bibliothek auslagern (think Boost::Python). Oder Du kannst Teile Deines Code mit beispielsweise numba oder Deinen ganzen Code mit niutka übersetzen... oder -- und das bringt meiner Erfahrung nach häufig die einfachsten, elegantesten und besten Ergebnisse: an Deinen Algorithmen arbeiten.

Vor einigen Wochen gab es eine Diskussion in einem anderen Forum, in der jemand behauptet hat, Python sei prinzipiell langsam: [1] und nicht halb so schnell wie . Das haben einige Teilnehmer zum Anlaß genommen, mit anderen Interpretern und Algorithmen zu experimentieren, und siehe da: PyPy hat PHP, Ruby, und andere vergleichbare Interpreter locker abgehängt, und mit dem richtigen Algorithmus war die Implementierung sogar deutlich schneller als die ursprüngliche Implementierung in C -- und sogar so schnell, daß sie sich aus systemtechnischen Gründen nicht mehr sauber messen ließ.

Deswegen möchte ich Dir und allen anderen, die sich Gedanken über die Performance von Python-Basics wie Loops machen (wobei ich auch in C noch keine Schleife gesehen habe, die weniger als 4 CPU-Ticks braucht), den Rat mitgeben, daß Ihr damit höchstwahrscheinlich Eure kostbare Lebenszeit verschwendet. Und zuletzt möchte ich mir noch einen Hinweis auf einen eklatanten ökonomischen Zusammenhang erlauben, der bei solcherlei Betrachtungen häufig vergessen zu werden scheint: dickere Hardware ist immer billiger als die Arbeits- und Lebenszeit von Entwicklern... ;-)


[1] https://www.mikrocontroller.net/topic/517981#6683214
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

@Hartmannsgruber: for-Schleifen sind nicht per se schnell oder langsam, sondern dies ist der zur Problemlösung genutzte Algorithmus. Hier gilt es einen mit guter Laufzeit zu finden. Ist die nachfolgende Implementierung dann zu langsam, bedarf es eines Profilings um entscheiden zu können, an welchen Stellen im Programm anzusetzen ist. Da können auch erfahrene Programmierer gelegentlich Überraschungen erleben.
Antworten