Hallo an alle. Heute möchten wir eine weitere Übersetzung teilen, die im Vorfeld des Starts des Kurses "Webentwickler in Python" erstellt wurde. Lass uns gehen!

Ich war kürzlich sehr überrascht, als ich das entdeckte
>>> pow(3,89)
arbeitet langsamer als
>>> 3**89
Ich habe versucht, eine akzeptable Erklärung zu finden, konnte es aber nicht. Ich habe die Ausführungszeit dieser beiden Ausdrücke mit dem timeit- Modul aus Python 3 verfolgt:
$ python3 -m timeit 'pow(3,89)' 500000 loops, best of 5: 688 nsec per loop $ python3 -m timeit '3**89' 500000 loops, best of 5: 519 nsec per loop
Der Unterschied ist gering. Nur 0,1 μs, aber es hat mich verfolgt. Wenn ich beim Programmieren nichts erklären kann, leide ich an Schlaflosigkeit
Ich habe die Antwort mithilfe des Python-IRC-Feeds auf Freenode gefunden. Der Grund, warum pow etwas langsamer arbeitet, ist, dass CPython bereits einen zusätzlichen Schritt zum Laden von pow aus dem Namespace hat. Beim Aufruf von 3 ** 9 wird eine solche Last grundsätzlich nicht benötigt. Dies bedeutet auch, dass diese Zeitdifferenz mehr oder weniger konstant bleibt, wenn die Eingabewerte zunehmen.
Die Hypothese wurde bestätigt:
$ python3 -m timeit 'pow(3,9999)' 5000 loops, best of 5: 58.5 usec per loop $ python3 -m timeit '3**9999' 5000 loops, best of 5: 57.3 usec per loop
Bei der Suche nach einer Lösung für dieses Problem habe ich auch das dis-Modul kennengelernt. Es ermöglicht Ihnen, Python-Bytecode zu dekompilieren und zu lernen. Es war eine äußerst aufregende Entdeckung, da ich kürzlich das Reverse Engineering von Binärdateien studiert habe und das entdeckte Modul in dieser Angelegenheit nützlich war.
Ich habe den Bytecode der obigen Ausdrücke dekompiliert und Folgendes erhalten:
>>> import dis >>> dis.dis('pow(3,89)')
Informationen zum korrekten Verständnis der Ausgabe von dis.dis finden Sie in dieser Antwort auf Stackoverflow.
Ok, zurück zum Code. Pow zu dekompilieren macht Sinn. Der Bytecode lädt pow aus dem Namespace, lädt in die Register 3 und 89 und ruft schließlich die pow-Funktion auf. Aber warum unterscheiden sich die Ergebnisse der nächsten beiden Dekompilierungen? Schließlich haben wir nur den Wert des Exponenten von 64 auf 65 geändert!
Diese Frage führte mich zu einem anderen neuen Konzept namens "Faltung von Konstanten". Wenn wir einen konstanten Ausdruck haben, berechnet Python seinen Wert in der Kompilierungsphase. Wenn Sie das Programm ausführen, dauert dies nicht lange, da Python den bereits berechneten Wert verwendet. Schauen Sie sich das an:
def one_plue_one(): return 1+1
Python kompiliert die erste Funktion in die zweite und verwendet sie beim Ausführen des Codes. Nicht schlecht, oder?
Warum funktioniert die Faltung von Konstanten für 3 ** 64, aber nicht für 3 ** 65? Nun, ich weiß es nicht. Dies hängt wahrscheinlich irgendwie mit der Begrenzung der Anzahl der Grad zusammen, die zuvor vom System im Speicher berechnet wurden. Ich könnte mich irren Der nächste Schritt, den ich vorhabe, besteht darin, in meiner Freizeit in den Python-Quellcode einzutauchen und zu verstehen, was los ist. Ich suche immer noch nach einer Antwort auf meine Frage. Wenn Sie also Ideen haben, teilen Sie diese in den Kommentaren mit.
Ich möchte, dass Sie sich von diesem Beitrag inspirieren lassen, um selbst eine Lösung für Ihre Probleme zu finden. Sie wissen nie, wohin Sie die Antworten führen werden. Letztendlich können Sie etwas völlig Neues lernen, wie es mir passiert ist. Ich hoffe, dass die Flamme der Neugier noch in dir brennt!
Haben Sie ähnliche Dinge bemerkt? Warten auf Ihre Kommentare!