2016-01-19 1 views
3

Un collaborateur m'a fait remarquer que l'instruction with peut être lente. J'ai donc mesuré et en effet il faut 20 fois plus de temps pour obtenir une valeur à partir d'une fonction contextmanager que d'un générateur en Python 2.7 et même 200 fois plus en PyPy 2.6.Pourquoi contextmanager est lent

Pourquoi est-ce vrai? Est-il possible de réécrire contextlib.contextmanager() pour courir plus vite?

Pour la référence:

def value_from_generator(): 
    def inner(): yield 1 

    value, = inner() 
    return value 

def value_from_with(): 
    @contextmanager 
    def inner(): yield 1 

    with inner() as value: 
     return value 

Et timings:

$ python -m timeit 'value_from_generator()' 
10000000 loops, best of 3: 0.169 usec per loop 

$ python -m timeit 'value_from_with()' 
100000 loops, best of 3: 3.04 usec per loop 
+0

Un test qui recrée à chaque fois le gestionnaire de contexte n'est pas un cas de test particulièrement bon pour le coût d'utilisation d'un gestionnaire de contexte. En général, une fonction décorée '@ contextmanager' est définie une fois, mais utilisée plusieurs fois; c'est comme rejeter l'utilisation de 'class'es et de fonctions car utiliser' dict's et mettre tout en ligne est plus rapide. – ShadowRanger

+0

@ShadowRanger vous avez raison. Il semble que le 'contextmanager' contribue beaucoup au ralentissement. Et après avoir remplacé le décorateur par une classe de gestionnaire de contexte, @krrr suggère d'accélérer les choses encore plus. –

Répondre

3

Utilisation profileur et source de contextlib, je l'ai trouvé:

value_from_with: 
ncalls tottime cumtime filename:lineno(function) 
1000000 1.415 4.802 value_from_with # 1sec more than value_from_generator, likely caused by with statement 
1000000 1.115 1.258 contextlib.py:37(__init__) # better doc string of context manager instance 
1000000 0.656 0.976 contextlib.py:63(__exit__) # optional exception handling 
1000000 0.575 1.833 contextlib.py:124(helper) # "wrapped" in decorator 
2000000 0.402 0.604 {built-in method next} # why it's so expensive? 
1000000 0.293 0.578 contextlib.py:57(__enter__) # a next() call to the generator in try&except block (just for error msg) 
2000000 0.203 0.203 inner1 
1000000 0.143 0.143 {built-in method getattr} # better doc string, called by __init__ 

value_from_generator: 
ncalls tottime cumtime filename:lineno(function) 
1000000 0.416 0.546 value_from_generator 
2000000 0.130 0.130 inner2 

Il nous a dit: déballage du générateur est plus rapide que d'utiliser next(); l'appel de fonction est cher; la gestion des exceptions est coûteuse ... donc la comparaison est injuste, et ce profilage est juste pour le plaisir. Il nous a également indiqué que chaque fois qu'un bloc "with" est exécuté, une instance de gestionnaire de contexte est créée (presque inévitable). En outre, contextmanager a fait un travail pour nous convinient. Si vous voulez vraiment l'optimiser, vous pouvez écrire une classe de gestionnaire de contexte au lieu d'utiliser le décorateur

Code profilé:

def inner1(): yield 1 

def value_from_generator(): 
    value, = inner1() 
    return value 

# inner should not be created again and again 
@contextmanager 
def inner2(): yield 1 

def value_from_with(): 
    with inner2() as value: 
     return value 
1

Ces deux outils ont « légèrement » différentes fins, si l'on compare leur performance gagné » Je ne montre vraiment rien. Les gestionnaires de contexte vous permettent d'effectuer certaines opérations avant et après l'exécution du code dans le bloc with

L'utilisation courante consiste à occuper les ressources au démarrage, à effectuer le travail, à effectuer des nettoyages, par exemple les connexions DB, l'accès aux fichiers, etc.

Les générateurs vous permettent de coder des fonctions qui sauvegardent l'état entre les appels. Utilisation commune pour économiser des ressources sur des calculs inutiles (à un moment particulier), et économiser de la mémoire en ne stockant pas tous les résultats de l'opération à la fois. Donc, l'utilisation primaire est à des fins de calcul.

+0

En effet, le générateur et le gestionnaire de contexte ont des objectifs assez différents. J'ai utilisé un générateur pour la comparaison, car il est destiné à être défini comme faisant partie du gestionnaire de contexte. –