Python: почему * и ** быстрее, чем / и sqrt()?
при оптимизации моего кода, я понял следующее:
>>> 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]
а также:
>>> 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]
Я предполагаю, что это связано с тем, как python реализован в C, но мне интересно, не хочет ли кто-нибудь объяснить, почему это так?
1 ответ:
(несколько неожиданная) причина ваших результатов заключается в том, что Python, похоже, складывает постоянные выражения, включающие умножение с плавающей запятой и возведение в степень, но не деление.
math.sqrt()это совсем другой зверь, так как для него нет байт-кода, и он включает вызов функции.на Python 2.6.5, следующий код:
x1 = 1234567890.0 / 4.0 x2 = 1234567890.0 * 0.25 x3 = 1234567890.0 ** 0.5 x4 = math.sqrt(1234567890.0)компилируется в байткод следующее:
# 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)как видим, умножение и возведение в степень не торопитесь вообще, так как они сделаны, когда код компилируется. Разделение занимает больше времени, так как это происходит во время выполнения. Квадратный корень-это не только самая дорогостоящая вычислительная операция из четырех, но и различные накладные расходы, которые другие не делают (поиск атрибутов, вызов функций и т. д.).
если вы устраните эффект постоянного складывания, мало что можно разделить умножение и деление:
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)на самом деле немного быстрее, чемx ** 0.5, предположительно потому, что это частный случай последнего и поэтому может быть сделано более эффективно, несмотря на накладные расходы: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изменить 2011-11-16: складывание постоянного выражения выполняется оптимизатором глазка Python. Исходный код (
peephole.c) содержит следующий комментарий, который объясняет, почему постоянное деление не складывается:case BINARY_DIVIDE: /* Cannot fold this operation statically since the result can depend on the run-time presence of the -Qnew flag */ return 0;The
-Qnewфлаг включает "истинное деление", определенное в PEP 238.
Comments