Qu'est-ce qui fait une différence significative de performances entre eventlet et gevent ?
-
12-12-2019 - |
Question
Ces deux bibliothèques partagent une philosophie similaire et, par conséquent, des décisions de conception similaires.Mais ce benchmark WSGI populaire dit eventlet
est bien plus lent que gevent
.Qu’est-ce qui rend leur performance si différente ?
Comme je le sais, les principales différences entre eux sont :
gevent
dépend intentionnellement de et est couplé àlibev
(libevent
, précédemment) tandis queeventlet
définit une interface de réacteur indépendante et implémente des adaptateurs particuliers en utilisantselect
,epoll
, et le réacteur Twisted derrière. Une interface de réacteur supplémentaire entraîne-t-elle des performances critiques ?gevent
est principalement écrit en Cython tandis queeventlet
est écrit en Python pur. Cython compilé nativement est-il si plus rapide que Python pur, pour des programmes peu informatiques mais liés aux IO ?Primitives de
gevent
émuler les interfaces des bibliothèques standard tout eneventlet
Les primitives de diffèrent de la norme et fournissent une couche supplémentaire pour l'émuler. Une couche d'émulation supplémentaire rend-elleeventlet
Ralentissez?La mise en œuvre de
eventlet.wsgi
juste pire quegevent.pywsgi
?
Je me le demande vraiment, car dans l’ensemble, ils me ressemblent beaucoup.
La solution
Eh bien, gevent n'est pas "principalement" écrit en Cython, bien que certaines sections critiques le soient.
Cython fait une énorme différence.Les optimisations du processeur fonctionnent bien mieux avec le code compilé.La prédiction de branchement, par exemple, s'effondre dans les systèmes basés sur VM car l'indirection du branchement au niveau d'exécution d'une VM lui est opaque.L'empreinte du cache est plus étroite.Le code compilé fait ici une énorme différence, et les E/S peuvent être très sensibles à la latence.
Dans le même ordre d’idées, Libev est très rapide.Mêmes raisons.
Il ne semble pas que eventlet aurait dû utiliser le hub de sélection (Python 2.6 utilise généralement par défaut epoll).S'il était bloqué lors de la sélection, cela le rendrait vraiment lent (car Python doit convertir le select fd_set en une liste Python, donc ça devient moche quand il est au milieu d'une boucle).
Je n'ai fait aucun profilage, mais je serais prêt à parier que libev / libevent plus Cython font la grande différence.Notamment, certaines des primitives de thread sont en Cython en gevent.C'est un gros problème car beaucoup de code les touche indirectement via les E/S et même la bibliothèque standard à certains endroits.
Quant à la couche d'émulation supplémentaire de l'eventlet, elle semble y avoir beaucoup plus de rebond.En fait, le chemin du code semble construire des rappels et laisser le hub les appeler.eventlet semble faire davantage de comptabilité que le hub effectue dans gevent.Mais encore une fois, je n'en ai pas fait le profil.Quant au Monkeypatching lui-même, ils se ressemblent assez.
Le serveur WSGI est un autre serveur difficile.Notamment, l'analyse de l'en-tête dans gevent est reportée à la bibliothèque standard, alors qu'ils l'implémentent eux-mêmes dans eventlet.Je ne sais pas s’il s’agit d’un impact important ou non, mais il ne serait pas surprenant qu’il y ait quelque chose qui se cache là-dedans.Le plus révélateur est que le serveur d'eventlet est basé sur une version patchée par singe de la bibliothèque standard BaseHTTPServer.Je ne peux pas imaginer que ce soit tout à fait optimal.Gevent implémente un serveur conscient de l'émulation.
Autres conseils
Désolé pour la réponse tardive.
Il y a deux raisons principales à une grande différence de performances dans ce repère:
- comme indiqué précédemment, les chemins critiques de gevent sont fortement optimisés
- ce benchmark effectue des tests de résistance.Il n'est plus lié aux E/S, car il essaie de faire en sorte que la machine exécute autant de requêtes que possible.Et c'est là que le code Cythonisé brille.
"Dans le monde réel", cela ne se produit que lors des rafales de trafic "slashdot".Ce qui est important et il faut être prêt, mais lorsque cela se produit, vous réagissez en ajoutant plus de serveurs ou en désactivant les fonctionnalités gourmandes en ressources.Je n'ai pas vu de référence qui ajoute réellement plus de serveurs lorsque la charge augmente.
Si, en revanche, le benchmark simulait une charge « journée normale » (qui varierait d'un site Web à l'autre) mais pourrait généralement être approché par une demande, une pause aléatoire, une répétition.Moins nous faisons de pause, plus nous simulons de trafic.De plus, le côté client du benchmark devrait simuler la latence.Sous Linux, cela pourrait être fait en utilisant le génial netem[1], sinon, en mettant de petits délais avant les appels de réception/envoi (ce qui serait très difficile car les tests utilisent généralement des bibliothèques de niveau supérieur).
Maintenant, si ces conditions sont remplies, nous évaluerions réellement les problèmes liés aux IO.Mais les résultats ne seraient pas trop impressionnants :tous les candidats ont servi avec succès des chargements de 10, 50 et même 200 qps.Ennuyeux, non ?Nous pourrions ainsi mesurer la distribution de la latence, le temps nécessaire pour répondre à 99 % des requêtes, etc.Gevent montrerait encore de meilleurs résultats.Mais la différence ne serait guère impressionnante.