2014-10-02 36 views
5

W trakcie polowania na błędy wydajności, w końcu stwierdziłem, że źródłem problemu jest opakowanie contextlib. Obciążenie jest dość oszałamiające i nie spodziewałem się, że będzie źródłem spowolnienia. Spowolnienie jest w zakresie 50X, nie mogę sobie pozwolić na to w pętli. Z pewnością doceniłbym ostrzeżenie w dokumentach, jeśli ma potencjał spowolnienia rzeczy tak znacząco.Dlaczego zawroty głowy [50X] kontekstlib i instrukcja With w Pythonie i co z tym zrobić

Wydaje się to było znane od 2010 https://gist.github.com/bdarnell/736778

Posiada zestaw benchmarków można spróbować. Przed uruchomieniem zmień fn na fn() w simple_catch(). Dzięki, DSM za wskazanie tego.

Jestem zaskoczony, że sytuacja nie poprawiła się od tamtych czasów. Co mogę z tym zrobić? Mogę zejść na dół, by spróbować/z wyjątkiem, ale mam nadzieję, że są inne sposoby radzenia sobie z tym.

+0

Nie wiem, czy twoje oświadczenie jest prawdziwe, ale społeczność Pythona generalnie nie jest zainteresowana wydajnością. –

+0

@SiyuanRen "Nie wiem, czy twoje stwierdzenie jest prawdziwe", dlatego połączyłem kod testu porównawczego. Jest bardzo łatwy do uruchomienia. – san

+1

Uruchomiłem to i wydaje się, że to potwierdza twój punkt widzenia. Ale wiele razy problemy są z benchmarkingiem, a nie z rzeczywistym systemem, który widzę w rzekomym "C++ jest zbyt wolny na to i to" zbyt wiele razy. Nie jestem tak dobrze zorientowany w Pythonie, żeby powiedzieć w tym. –

Odpowiedz

2

Oto niektóre nowe czasy:

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) 

Zrobiłem simple_catchrzeczywiście wywołać funkcję i Dodałem dwa nowe punkty odniesienia.

Oto co mam:

>>> 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 

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 

koszt jest znacznie mniejszy, niż twierdzi. Co więcej, jedyny napowietrzny PyPy nie wydaje się być w stanie usunąć w stosunku do try ... catch dla wariantu ręcznego to tworzenie obiektu, który w tym przypadku jest trywialnie usuwany.


Niestety with is way too involved for good optimization by CPython, zwłaszcza w odniesieniu do contextlib który nawet pypy znajdzie trudno zoptymalizować. Zwykle jest tak, ponieważ chociaż tworzenie obiektu + wywołanie funkcji + tworzenie generatora jest kosztowne, to jest ono w porównaniu do tego, co zwykle wykonuje się.

Jeśli jesteś pewnie, który powoduje, że with powoduje większość kosztów, przekonwertuj menedżerów kontekstów do pamięci podręcznej instancji, tak jak ja. Jeśli nadal jest to zbyt duże obciążenie, prawdopodobnie masz większy problem z zaprojektowaniem systemu. Zastanów się nad zwiększeniem zakresu (zwykle nie jest to dobry pomysł, ale akceptowalny, jeśli zajdzie taka potrzeba).


Również PyPy. Dat JIT be fast.

+0

Cieszę się, że PyPy optymalizuje koszty ogólne. Buforowanie jest naprawdę dobrym pomysłem, dzięki! Jestem na znacznie wolniejszym komputerze i 32-bitowym, co może wyjaśniać różnicę w postrzeganej wysokości. – san