Ich habe mir die Quelle der sortierten Container angesehen und war überrascht, diese Zeile zu sehen :
self._load, self._twice, self._half = load, load * 2, load >> 1
Hier load
ist eine ganze Zahl. Warum Bitverschiebung an einer Stelle und Multiplikation an einer anderen verwenden? Es erscheint vernünftig, dass die Bitverschiebung schneller ist als die integrale Division durch 2, aber warum nicht auch die Multiplikation durch eine Verschiebung ersetzen? Ich habe die folgenden Fälle verglichen:
- (Zeiten teilen)
- (Verschiebung, Verschiebung)
- (Zeiten, Verschiebung)
- (verschieben, teilen)
und festgestellt, dass # 3 durchweg schneller ist als andere Alternativen:
# self._load, self._twice, self._half = load, load * 2, load >> 1
import random
import timeit
import pandas as pd
x = random.randint(10 ** 3, 10 ** 6)
def test_naive():
a, b, c = x, 2 * x, x // 2
def test_shift():
a, b, c = x, x << 1, x >> 1
def test_mixed():
a, b, c = x, x * 2, x >> 1
def test_mixed_swapped():
a, b, c = x, x << 1, x // 2
def observe(k):
print(k)
return {
'naive': timeit.timeit(test_naive),
'shift': timeit.timeit(test_shift),
'mixed': timeit.timeit(test_mixed),
'mixed_swapped': timeit.timeit(test_mixed_swapped),
}
def get_observations():
return pd.DataFrame([observe(k) for k in range(100)])
Die Frage:
Ist mein Test gültig? Wenn ja, warum ist (multiplizieren, verschieben) schneller als (verschieben, verschieben)?
Ich führe Python 3.5 unter Ubuntu 14.04 aus.
Bearbeiten
Oben ist die ursprüngliche Aussage der Frage. Dan Getz liefert in seiner Antwort eine hervorragende Erklärung.
Der Vollständigkeit halber finden Sie hier Beispielabbildungen für größere, x
wenn Multiplikationsoptimierungen nicht zutreffen.
quelle
x
?x
sei denn, es ist sehr groß, denn das ist nur eine Frage, wie es im Speicher gespeichert ist, oder?Antworten:
Dies scheint darauf zurückzuführen zu sein, dass die Multiplikation kleiner Zahlen in CPython 3.5 so optimiert ist, wie dies bei Linksverschiebungen durch kleine Zahlen nicht der Fall ist. Positive Linksverschiebungen erzeugen immer ein größeres ganzzahliges Objekt, um das Ergebnis als Teil der Berechnung zu speichern. Bei Multiplikationen der in Ihrem Test verwendeten Sortierung wird dies durch eine spezielle Optimierung vermieden und ein ganzzahliges Objekt mit der richtigen Größe erstellt. Dies ist im Quellcode der Ganzzahlimplementierung von Python zu sehen .
Da Ganzzahlen in Python eine beliebige Genauigkeit haben, werden sie als Arrays von ganzzahligen "Ziffern" gespeichert, wobei die Anzahl der Bits pro ganzzahliger Ziffer begrenzt ist. Im allgemeinen Fall sind Operationen mit ganzen Zahlen keine einzelnen Operationen, sondern müssen den Fall mehrerer "Ziffern" behandeln. In pyport.h ist diese Bitbegrenzung als 30 Bit auf einer 64-Bit-Plattform oder ansonsten als 15 Bit definiert. (Ich werde diese 30 von nun an einfach aufrufen, um die Erklärung einfach zu halten. Beachten Sie jedoch, dass das Ergebnis Ihres Benchmarks davon abhängt, ob Sie Python verwenden, das für 32-Bit kompiliert wurde
x
es weniger als 32.768 beträgt oder nicht .)Wenn die Ein- und Ausgänge einer Operation innerhalb dieser 30-Bit-Grenze bleiben, kann die Operation anstelle der allgemeinen Methode auf optimierte Weise behandelt werden. Der Beginn der Implementierung der Ganzzahlmultiplikation ist wie folgt:
Wenn Sie also zwei Ganzzahlen multiplizieren, wobei jede in eine 30-Bit-Ziffer passt, erfolgt dies als direkte Multiplikation mit dem CPython-Interpreter, anstatt mit den Ganzzahlen als Arrays zu arbeiten. (
MEDIUM_VALUE()
Wird ein positives ganzzahliges Objekt aufgerufen, erhält es einfach seine erste 30-Bit-Ziffer.) Wenn das Ergebnis in eine einzelne 30-Bit-Ziffer passt,PyLong_FromLongLong()
wird dies bei einer relativ kleinen Anzahl von Operationen bemerkt und ein einstelliges ganzzahliges Objekt zum Speichern erstellt es.Im Gegensatz dazu werden Linksverschiebungen nicht auf diese Weise optimiert, und jede Linksverschiebung behandelt die Ganzzahl, die als Array verschoben wird. Insbesondere wenn Sie im Quellcode nachsehen, ob
long_lshift()
bei einer kleinen, aber positiven Linksverschiebung immer ein zweistelliges ganzzahliges Objekt erstellt wird, wenn die Länge später auf 1 gekürzt werden soll: (meine Kommentare in/*** ***/
)Ganzzahlige Division
Sie haben nicht nach der schlechteren Leistung der Ganzzahl-Bodenteilung im Vergleich zu Rechtsschichten gefragt, da dies Ihren (und meinen) Erwartungen entspricht. Das Teilen einer kleinen positiven Zahl durch eine andere kleine positive Zahl ist jedoch auch nicht so optimiert wie kleine Multiplikationen. Jeder
//
berechnet mit der Funktion sowohl den Quotienten als auch den Restlong_divrem()
. Dieser Rest wird für einen kleinen Divisor mit einer Multiplikation berechnet und in einem neu zugewiesenen ganzzahligen Objekt gespeichert , das in dieser Situation sofort verworfen wird.quelle
x
außerhalb des optimierten Bereichs anzuzeigen.