2014-10-02 8 views
5

Nel processo di rilevamento dei bug di prestazioni ho finalmente identificato che l'origine del problema è il wrapper contextlib. Il sovraccarico è piuttosto sbalorditivo e non mi aspettavo che fosse la fonte del rallentamento. Il rallentamento è nell'intervallo 50X, non posso permettermi di farlo in un ciclo. Avrei sicuramente apprezzato un avvertimento nei documenti se avesse il potenziale di rallentare le cose in modo così significativo.Perché il sovraccarico sconcertante [50X] di contextlib e l'istruzione With in Python e cosa fare al riguardo

Sembra questo è stato conosciuto dal 2010 https://gist.github.com/bdarnell/736778

Ha una serie di parametri di riferimento si possono provare. Si prega di cambiare fn a in simple_catch() prima di correre. Grazie, DSM per averlo indicato.

Sono sorpreso che la situazione non sia migliorata da quei tempi. Cosa posso fare a riguardo? Posso cadere per provare/tranne, ma spero ci siano altri modi per affrontarlo.

+0

Non so se la tua affermazione è vera, ma la comunità Python di solito non è interessata alle prestazioni. –

+0

@SiyuanRen "Non so se la tua affermazione è vera" è il motivo per cui ho collegato il codice di benchmarking. È molto facile da gestire. – san

+1

L'ho eseguito e sembra confermare il tuo punto. Ma molte volte i problemi riguardano il benchmarking piuttosto che il sistema attuale, che vedo nel supposto 'C++ è troppo lento su questo e su quello' troppe volte. Non sono molto esperto in Python per raccontare questo. –

risposta

2

Ecco alcune nuove tempistiche:

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) 

che ho fatto simple_catchin realtà chiamare la funzione e ho aggiunto due nuovi parametri di riferimento.

Ecco quello che ho ottenuto:

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

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

il sovraccarico è molto più piccolo di quanto si rivendicato. Inoltre, l'unico sovraccarico PyPy non sembra essere in grado di rimuovere relativamente allo try ... catch per la variante manuale è la creazione di un oggetto, che in questo caso viene rimosso banalmente.


Purtroppo with is way too involved for good optimization by CPython, soprattutto per quanto riguarda contextlib cui anche PyPy trova difficile da ottimizzare. Questo è normalmente OK perché sebbene la creazione di oggetti + una chiamata di funzione + la creazione di un generatore sia costosa, è economico rispetto a ciò che viene normalmente fatto.

Se si è sicuri che with sta causando la maggior parte della vostra testa, di convertire i gestori di contesto in istanze memorizzate nella cache come se avessi. Se è ancora troppo sovraccarico, probabilmente avrai un problema più grande con il modo in cui è progettato il tuo sistema. Considera di rendere più ampio lo scope del with (di solito non è una buona idea, ma è accettabile se necessario).


Inoltre, PyPy. Dat JIT essere veloce.

+0

Felice di vedere che PyPy ottimizza l'overhead. Il caching è davvero una buona idea, grazie! Sono su una macchina molto più lenta e anche a 32 bit, che potrebbe spiegare la differenza nella quantità di sovraccarico percepito. – san