Python: способ ускорить многократно выполненное выражение eval?
В моем коде я использую eval
для оценки строкового выражения, заданного пользователем. Есть ли способ скомпилировать или иным образом ускорить это утверждение?
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
Для быстрой опции сравнения ONE занимает 2,019 секунды на моей машине, а опция TWO занимает всего 0,218 секунды. Конечно, у Python есть способ сделать это без жесткого кодирования выражения.
Ответы
Ответ 1
Вы также можете обмануть python:
expression = "math.sin(v['x']) * v['y']"
exp_as_func = eval('lambda: ' + expression)
И затем используйте его так:
exp_as_func()
Тест скорости:
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
В качестве побочного примечания, если v
не является глобальным, вы можете создать лямбда следующим образом:
exp_as_func = eval('lambda v: ' + expression)
и назовите его:
exp_as_func(my_v)
Ответ 2
Вы можете избежать накладных расходов путем компиляции выражения заранее, используя 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
Просто убедитесь, что вы выполняете компиляцию только один раз (вне цикла). Как упоминалось в комментариях, при использовании eval
для строк, представленных пользователем, убедитесь, что вы очень осторожно относитесь к тому, что вы принимаете.
Ответ 3
Я думаю, что вы оптимизируете неправильный конец. Если вы хотите выполнить одну и ту же операцию для большого количества чисел, вам следует использовать 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))
Чтобы дать вам представление о возможном усилии, я добавил вариант, используя общий питон и лямбда-трюк:
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)
Вот результаты на моей стареющей машине:
$ 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
Ускорение не так высоко, как я ожидал, но все же значителен.