2012-09-17 24 views
9

Trong mã của tôi, tôi đang sử dụng eval để đánh giá một biểu thức chuỗi do người dùng đưa ra. Có cách nào để biên dịch hay tăng tốc tuyên bố này không?Python: Cách để tăng tốc độ câu lệnh eval được thực hiện liên tục?

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 

Để có tùy chọn so sánh nhanh ONE mất 2.019 giây trên máy, trong khi tùy chọn TWO chỉ mất 0,18 giây. Chắc chắn Python có một cách để làm điều này mà không cần mã hóa cứng biểu thức.

+4

Kiểm tra một số lựa chọn thay thế để đánh giá bài đăng này http://stackoverflow.com/questions/1832940 cũng như một số lý do chính đáng để tránh xa nó. –

+2

nếu người dùng nhập 'os os; os.system (" rm -rf/")' thì sao? Bạn cần phải viết một trình phân tích cú pháp để giải thích chuỗi đầu vào và chỉ nhận ra những gì bạn mong đợi: 'sin',' cos', 'log', v.v. Ném một lỗi nếu những gì chúng nhập vào không hoạt động. Nó có thể là xấu nếu bạn không làm điều đó. – jozzas

+0

Nếu người dùng muốn "rm -rf /" hoặc ":() {: |: & };:" anh ấy có thể làm điều đó trong trình bao thay vì trong Python. – devtk

Trả lời

16

Bạn cũng có thể lừa python:

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

Và sau đó sử dụng nó như vậy:

exp_as_func() 

thử nghiệm Tốc độ:

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 

Là một mặt lưu ý, nếu v không phải là toàn cầu, bạn có thể cr eate lambda như thế này:

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

và gọi nó là:

exp_as_func(my_v) 
+2

Đây là một cải thiện tốc độ đáng chú ý hơn phản ứng của F.J., mà đã là một cải tiến tốc độ lớn. – devtk

+0

Tôi đoán mẹo này tương đương với việc sử dụng 'biên dịch' trước khi eval vì khi bạn chạy nó bạn nhận được' Chạy chậm nhất mất 17,90 lần dài hơn nhanh nhất. Điều này có nghĩa là kết quả trung gian đang được lưu trong bộ nhớ cache'. – Mermoz

11

Bạn có thể tránh được những chi phí bằng cách biên dịch biểu thức trước sử dụng 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 

Chỉ cần chắc chắn rằng bạn các biên dịch chỉ một lần (bên ngoài vòng lặp). Như đã đề cập trong các nhận xét, khi sử dụng eval trên các chuỗi do người dùng gửi, hãy đảm bảo bạn rất cẩn thận về những gì bạn chấp nhận.

+0

thats một lợi ích khá đáng kể ... –

4

Tôi nghĩ rằng bạn đang tối ưu hóa kết thúc sai trái. Nếu bạn muốn thực hiện các hoạt động tương tự cho rất nhiều con số, bạn nên xem xét sử dụng 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)) 

Để cung cấp cho bạn một ý tưởng về những lợi ích có thể, tôi đã thêm một biến thể sử dụng python generic và lừa 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) 

Sau đây là các kết quả trên máy lão hóa của tôi:

$ 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 

tốc độ-up là không cao như tôi mong đợi, nhưng vẫn còn đáng kể.

+0

Tôi không có quyền truy cập vào 'numpy' tại đây. Nhưng tôi đồng ý, nó có thể tăng tốc mọi thứ. Tôi thường không dựa vào thư viện của bên thứ ba nếu tôi có thể nhận được mà không có nó. – devtk

Các vấn đề liên quan