2012-09-17 10 views
9

En mi código, estoy usando eval para evaluar una expresión de cadena dada por el usuario. ¿Hay alguna forma de compilar o acelerar esta afirmación?Python: ¿forma de acelerar una declaración de evaluación ejecutada repetidamente?

import math 
import random 

result_count = 100000 
expression = "math.sin(v['x']) * v['y']" 

variable = dict() 
variable['x'] = [random.random() for _ in xrange(result_count)] 
variable['y'] = [random.random() for _ in xrange(result_count)] 

# optimize anything below this line 

result = [0] * result_count 

print 'Evaluating %d instances of the given expression:' % result_count 
print expression 

v = dict() 
for index in xrange(result_count): 
    for name in variable.keys(): 
     v[name] = variable[name][index] 
    result[index] = eval(expression) # <-- option ONE 
    #result[index] = math.sin(v['x']) * v['y'] # <-- option TWO 

Para una comparación rápida UNA opción lleva 2.019 segundos en mi máquina, mientras que la segunda opción tiene sólo 0,218 segundos. Seguramente Python tiene una forma de hacer esto sin codificar la expresión.

+4

Consulte algunas alternativas para evaluar en esta publicación http://stackoverflow.com/questions/1832940, así como algunas buenas razones para mantenerse alejado de ella. –

+2

¿Qué sucede si el usuario escribe 'import os; os.system (" rm -rf/")'? Necesita escribir un analizador para interpretar la cadena de entrada, y solo reconocer lo que espera: 'sin',' cos', 'log', etc. Lanza un error si lo que ingresan no funciona. Podría ser malo si no haces eso. – jozzas

+0

Si el usuario quiere "rm -rf /" o ":() {: |: & };:" puede hacerlo en un shell en lugar de dentro de Python. – devtk

Respuesta

16

También puede engañar pitón:

expression = "math.sin(v['x']) * v['y']" 
exp_as_func = eval('lambda: ' + expression) 

y luego usarlo de esta manera:

exp_as_func() 

Prueba de velocidad:

In [17]: %timeit eval(expression) 
10000 loops, best of 3: 25.8 us per loop 

In [18]: %timeit exp_as_func() 
1000000 loops, best of 3: 541 ns per loop 

Como nota al margen, si v no es un global, puedes cr eate lambda como esto:

exp_as_func = eval('lambda v: ' + expression) 

y llamarlo:

exp_as_func(my_v) 
+2

Esta es una mejora de velocidad notable sobre la respuesta de F.J., que ya era una gran mejora de velocidad. – devtk

+0

Supongo que este truco es equivalente a usar 'compile' antes de eval porque cuando lo ejecutas obtienes' La ejecución más lenta tomó 17.90 veces más que el más rápido. Esto podría significar que un resultado intermedio está siendo almacenado en caché ". – Mermoz

11

Puede evitar la sobrecarga mediante la compilación de la expresión con antelación mediante compiler.compile():

In [1]: import math, compiler 

In [2]: v = {'x': 2, 'y': 4} 

In [3]: expression = "math.sin(v['x']) * v['y']" 

In [4]: %timeit eval(expression) 
10000 loops, best of 3: 19.5 us per loop 

In [5]: compiled = compiler.compile(expression, '<string>', 'eval') 

In [6]: %timeit eval(compiled) 
1000000 loops, best of 3: 823 ns per loop 

Sólo asegúrese de que no la compilación de una sola vez (fuera del bucle). Como se menciona en los comentarios, al usar eval en cadenas enviadas por el usuario, asegúrese de tener mucho cuidado con lo que acepta.

+0

que es una ganancia bastante significativa ... –

4

Creo que se trata de optimizar el extremo equivocado. Si desea realizar la misma operación para una gran cantidad de números que debe considerar el uso numpy:

import numpy 
import time 
import math 
import random 

result_count = 100000 
expression = "sin(x) * y" 

namespace = dict(
    x=numpy.array(
     [random.random() for _ in xrange(result_count)]), 
    y=numpy.array(
     [random.random() for _ in xrange(result_count)]), 
    sin=numpy.sin, 
) 
print ('Evaluating %d instances ' 
     'of the given expression:') % result_count 
print expression 

start = time.time() 
result = eval(expression, namespace) 
numpy_time = time.time() - start 
print "With numpy:", numpy_time 


assert len(result) == result_count 
assert all(math.sin(a) * b == c for a, b, c in 
      zip(namespace["x"], namespace["y"], result)) 

para darle una idea acerca de la posible ganancia He añadido una variante genérica usando Python y el truco lambda:

from math import sin 
from itertools import izip 

start = time.time() 
f = eval("lambda: " + expression) 
result = [f() for x, y in izip(namespace["x"], namespace["y"])] 
generic_time = time.time() - start 
print "Generic python:", generic_time 
print "Ratio:", (generic_time/numpy_time) 

Éstos son los resultados en mi máquina de envejecimiento:

$ python speedup_eval.py 
Evaluating 100000 instances of the given expression: 
sin(x) * y 
With numpy: 0.006098985672 
Generic python: 0.270224094391 
Ratio: 44.3063992807 

La aceleración no es tan alta como yo esperaba, pero aún significativa.

+0

No tengo acceso a 'numpy' aquí. Pero estoy de acuerdo, podría acelerar las cosas. En general, estoy en contra de depender de una biblioteca de terceros si puedo vivir sin eso. – devtk

Cuestiones relacionadas