python c performance python-2.7 python-internals

Python: ¿por qué*y** son más rápidos que/y sqrt()?



c performance (1)

La razón (algo inesperada) para sus resultados es que Python parece doblar expresiones constantes que involucran multiplicación de coma flotante y exponenciación, pero no división. math.sqrt() es una bestia completamente diferente ya que no tiene bytecode y se trata de una llamada de función.

En Python 2.6.5, el siguiente código:

x1 = 1234567890.0 / 4.0 x2 = 1234567890.0 * 0.25 x3 = 1234567890.0 ** 0.5 x4 = math.sqrt(1234567890.0)

compila a los siguientes 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)

Como puede ver, la multiplicación y la exponenciación no requieren mucho tiempo, ya que están listas cuando se compila el código. La división lleva más tiempo dado que ocurre en el tiempo de ejecución. La raíz cuadrada no es solo la operación más costosa desde el punto de vista computacional de los cuatro, sino que también incurre en varios gastos generales que los demás no (búsqueda de atributos, llamada de función, etc.).

Si eliminas el efecto del doblado constante, hay poco para separar la multiplicación y la división:

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) es en realidad un poco más rápido que x ** 0.5 , presumiblemente porque es un caso especial de este último y, por lo tanto, se puede hacer más eficientemente, a pesar de los gastos generales:

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

editar 2011-11-16: Plegado de expresión constante se realiza por el optimizador de mirilla de Python. El código fuente ( peephole.c ) contiene el siguiente comentario que explica por qué la división constante no está plegada:

case BINARY_DIVIDE: /* Cannot fold this operation statically since the result can depend on the run-time presence of the -Qnew flag */ return 0;

El indicador " -Qnew " habilita la "división verdadera" definida en PEP 238 .

Al optimizar mi código, me di cuenta de lo siguiente:

>>> 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]

y también:

>>> 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]

Supongo que tiene que ver con la forma en que Python se implementa en C, pero me pregunto si a alguien le importaría explicar por qué es así.