La razón (algo inesperada) de sus resultados es que Python parece plegar expresiones constantes que involucran multiplicación y exponenciación de punto flotante, pero no división. math.sqrt()
es una bestia completamente diferente ya que no tiene un código de bytes e implica 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 con los siguientes códigos de bytes:
4 0 LOAD_CONST 1 (1234567890.0)
3 LOAD_CONST 2 (4.0)
6 BINARY_DIVIDE
7 STORE_FAST 0 (x1)
5 10 LOAD_CONST 5 (308641972.5)
13 STORE_FAST 1 (x2)
6 16 LOAD_CONST 6 (35136.418286444619)
19 STORE_FAST 2 (x3)
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 toman mucho tiempo, ya que están listas cuando se compila el código. La división tarda más ya que ocurre en tiempo de ejecución. La raíz cuadrada no solo es la operación más costosa desde el punto de vista computacional de las cuatro, sino que también incurre en varios gastos generales que las otras no (búsqueda de atributos, llamada de función, etc.)
Si elimina el efecto del plegado 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 de manera más eficiente, 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
edit 2011-11-16: El plegado de expresiones constantes lo realiza 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 se pliega:
case BINARY_DIVIDE:
return 0;
La -Qnew
bandera habilita la "verdadera división" definida en PEP 238 .