2014-10-02 16 views
5

Trong quá trình tìm kiếm lỗi hiệu năng, cuối cùng tôi đã xác định rằng nguồn gốc của vấn đề là trình bao bọc contextlib. Các chi phí khá đáng kinh ngạc và tôi không mong đợi đó là nguồn gốc của sự suy thoái. Sự chậm lại là trong khoảng 50X, tôi không thể đủ khả năng để có được trong một vòng lặp. Tôi chắc chắn sẽ đánh giá cao một cảnh báo trong tài liệu nếu nó có tiềm năng làm chậm mọi thứ xuống đáng kể.Tại sao chi phí đáng kinh ngạc [50X] của contextlib và câu lệnh With trong Python và phải làm gì về nó

Có vẻ như điều này đã được biết đến từ năm 2010 https://gist.github.com/bdarnell/736778

Nó có một tập hợp các tiêu chuẩn bạn có thể thử. Vui lòng thay đổi fn thành fn() trong simple_catch() trước khi chạy. Cảm ơn, DSM đã chỉ ra điều này.

Tôi ngạc nhiên rằng tình hình vẫn chưa được cải thiện kể từ thời điểm đó. Tôi có thể làm gì với nó? Tôi có thể thả xuống để thử/trừ, nhưng tôi hy vọng có nhiều cách khác để giải quyết nó.

+0

Tôi không biết câu lệnh của bạn có đúng hay không, nhưng cộng đồng Python thường không quan tâm đến hiệu suất. –

+0

@SiyuanRen "Tôi không biết nếu tuyên bố của bạn là đúng" đó là lý do tại sao tôi liên kết mã điểm chuẩn. Nó rất dễ chạy. – san

+1

Tôi đã chạy và dường như xác thực điểm của bạn. Nhưng nhiều lần vấn đề là với điểm chuẩn hơn là hệ thống thực tế, mà tôi thấy trong khi cho rằng 'C++ quá chậm và điều này' quá nhiều lần. Tôi không phải là thạo trong Python để nói trong này. –

Trả lời

2

Dưới đây là một số timings mới:

import contextlib 
import timeit 

def work_pass(): 
    pass 

def work_fail(): 
    1/0 

def simple_catch(fn): 
    try: 
     fn() 
    except Exception: 
     pass 

@contextlib.contextmanager 
def catch_context(): 
    try: 
     yield 
    except Exception: 
     pass 

def with_catch(fn): 
    with catch_context(): 
     fn() 

class ManualCatchContext(object): 
    def __enter__(self): 
     pass 

    def __exit__(self, exc_type, exc_val, exc_tb): 
     return True 

def manual_with_catch(fn): 
    with ManualCatchContext(): 
     fn() 

preinstantiated_manual_catch_context = ManualCatchContext() 
def manual_with_catch_cache(fn): 
    with preinstantiated_manual_catch_context: 
     fn() 

setup = 'from __main__ import simple_catch, work_pass, work_fail, with_catch, manual_with_catch, manual_with_catch_cache' 
commands = [ 
    'simple_catch(work_pass)', 
    'simple_catch(work_fail)', 
    'with_catch(work_pass)', 
    'with_catch(work_fail)', 
    'manual_with_catch(work_pass)', 
    'manual_with_catch(work_fail)', 
    'manual_with_catch_cache(work_pass)', 
    'manual_with_catch_cache(work_fail)', 
    ] 
for c in commands: 
    print c, ': ', timeit.timeit(c, setup) 

Tôi đã thực hiện simple_catchthực sự gọi hàm và tôi đã bổ sung thêm hai tiêu chuẩn mới.

Đây là những gì tôi nhận:

>>> python2 bench.py 
simple_catch(work_pass) : 0.413918972015 
simple_catch(work_fail) : 3.16218209267 
with_catch(work_pass) : 6.88726496696 
with_catch(work_fail) : 11.8109841347 
manual_with_catch(work_pass) : 1.60508012772 
manual_with_catch(work_fail) : 4.03651213646 
manual_with_catch_cache(work_pass) : 1.32663416862 
manual_with_catch_cache(work_fail) : 3.82525682449 
python2 p.py.py 33.06s user 0.00s system 99% cpu 33.099 total 

Và đối với PyPy:

>>> pypy bench.py 
simple_catch(work_pass) : 0.0104489326477 
simple_catch(work_fail) : 0.0212869644165 
with_catch(work_pass) : 0.362847089767 
with_catch(work_fail) : 0.400238037109 
manual_with_catch(work_pass) : 0.0223228931427 
manual_with_catch(work_fail) : 0.0208241939545 
manual_with_catch_cache(work_pass) : 0.0138869285583 
manual_with_catch_cache(work_fail) : 0.0213649272919 

Các chi phí nhỏ hơn nhiều so với bạn tuyên bố. Hơn nữa, chỉ PyPy trên không dường như có thể loại bỏ tương đối so với try ... catch cho biến thể thủ công là tạo đối tượng, được loại bỏ một cách trivially trong trường hợp này.


Thật không may with is way too involved for good optimization by CPython, đặc biệt là liên quan đến contextlib mà thậm chí PyPy thấy khó có thể tối ưu hóa. Điều này là bình thường OK bởi vì mặc dù tạo đối tượng + một cuộc gọi chức năng + tạo ra một máy phát điện là tốn kém, đó là giá rẻ so với những gì thường được thực hiện.

Nếu bạn là chắc chắn rằng with đang gây ra phần lớn chi phí của bạn, hãy chuyển đổi người quản lý ngữ cảnh thành các thể hiện được lưu trong bộ nhớ cache như tôi có. Nếu vẫn còn quá nhiều chi phí, bạn có thể gặp vấn đề lớn hơn với cách thiết kế hệ thống của bạn. Xem xét việc làm cho phạm vi của các with s lớn hơn (thường không phải là một ý tưởng tốt, nhưng chấp nhận được nếu cần).


Ngoài ra, PyPy. Dat JIT là nhanh.

+0

Vui mừng khi thấy PyPy tối ưu hóa chi phí. Caching thực sự là một ý tưởng tốt, cảm ơn! Tôi đang trên một máy chậm hơn nhiều và 32 bit quá, có thể giải thích sự khác biệt trong số tiền trên không nhận thức được. – san

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