Bei der Optimierung meines Codes wurde Folgendes festgestellt:
>>> from timeit import Timer as T
>>> T(lambda : 1234567890 / 4.0).repeat()
[0.22256922721862793, 0.20560789108276367, 0.20530295372009277]
>>> from __future__ import division
>>> T(lambda : 1234567890 / 4).repeat()
[0.14969301223754883, 0.14155197143554688, 0.14141488075256348]
>>> T(lambda : 1234567890 * 0.25).repeat()
[0.13619112968444824, 0.1281130313873291, 0.12830305099487305]
und auch:
>>> from math import sqrt
>>> T(lambda : sqrt(1234567890)).repeat()
[0.2597470283508301, 0.2498021125793457, 0.24994492530822754]
>>> T(lambda : 1234567890 ** 0.5).repeat()
[0.15409398078918457, 0.14059877395629883, 0.14049601554870605]
Ich nehme an, es hat mit der Art und Weise zu tun, wie Python in C implementiert ist, aber ich frage mich, ob jemand erklären möchte, warum das so ist.
Antworten:
Der (etwas unerwartete) Grund für Ihre Ergebnisse ist, dass Python konstante Ausdrücke zu falten scheint, die Gleitkomma-Multiplikation und Exponentiation beinhalten, aber keine Division.
math.sqrt()
ist ein ganz anderes Tier, da es keinen Bytecode dafür gibt und es einen Funktionsaufruf beinhaltet.In Python 2.6.5 den folgenden Code:
x1 = 1234567890.0 / 4.0 x2 = 1234567890.0 * 0.25 x3 = 1234567890.0 ** 0.5 x4 = math.sqrt(1234567890.0)
Kompiliert zu folgenden Bytecodes:
# x1 = 1234567890.0 / 4.0 4 0 LOAD_CONST 1 (1234567890.0) 3 LOAD_CONST 2 (4.0) 6 BINARY_DIVIDE 7 STORE_FAST 0 (x1) # x2 = 1234567890.0 * 0.25 5 10 LOAD_CONST 5 (308641972.5) 13 STORE_FAST 1 (x2) # x3 = 1234567890.0 ** 0.5 6 16 LOAD_CONST 6 (35136.418286444619) 19 STORE_FAST 2 (x3) # x4 = math.sqrt(1234567890.0) 7 22 LOAD_GLOBAL 0 (math) 25 LOAD_ATTR 1 (sqrt) 28 LOAD_CONST 1 (1234567890.0) 31 CALL_FUNCTION 1 34 STORE_FAST 3 (x4)
Wie Sie sehen können, nehmen Multiplikation und Exponentiation überhaupt keine Zeit in Anspruch, da sie beim Kompilieren des Codes abgeschlossen sind. Die Teilung dauert länger, da sie zur Laufzeit erfolgt. Die Quadratwurzel ist nicht nur die rechenintensivste der vier Operationen, sondern verursacht auch verschiedene Gemeinkosten, die die anderen nicht verursachen (Attributsuche, Funktionsaufruf usw.).
Wenn Sie den Effekt der konstanten Faltung eliminieren, gibt es wenig, um Multiplikation und Division zu trennen:
In [16]: x = 1234567890.0 In [17]: %timeit x / 4.0 10000000 loops, best of 3: 87.8 ns per loop In [18]: %timeit x * 0.25 10000000 loops, best of 3: 91.6 ns per loop
math.sqrt(x)
ist tatsächlich ein bisschen schneller alsx ** 0.5
, vermutlich weil es sich um einen Sonderfall handelt und daher trotz des Overheads effizienter durchgeführt werden kann:In [19]: %timeit x ** 0.5 1000000 loops, best of 3: 211 ns per loop In [20]: %timeit math.sqrt(x) 10000000 loops, best of 3: 181 ns per loop
edit 2011-11-16: Das Falten konstanter Ausdrücke wird vom Python-Gucklochoptimierer durchgeführt. Der Quellcode (
peephole.c
) enthält den folgenden Kommentar, der erklärt, warum die konstante Division nicht gefaltet ist:case BINARY_DIVIDE: /* Cannot fold this operation statically since the result can depend on the run-time presence of the -Qnew flag */ return 0;
Das
-Qnew
Flag aktiviert die in PEP 238 definierte "wahre Teilung" .quelle
/
in Python 3 und//
in Python 2 und 3. Dies ist also höchstwahrscheinlich auf die Tatsache zurückzuführen, dass/
es in Python 2 unterschiedliche Bedeutungen haben kann. Wenn das konstante Falten durchgeführt wird, ist möglicherweise noch nicht bekannt, ob dies der Fallfrom __future__ import division
ist in der Tat?1./0.
in Python 2.7 ergibt sich nichtNaN
aber aZeroDivisionError
.