Mises en garde concernant les réacteurs Select / Poll vs Epoll dans Twisted


95

Tout ce que j'ai lu et expérimenté (applications basées sur Tornado) me porte à croire que l'ePoll est un remplacement naturel pour le réseautage basé sur Select et Poll, en particulier avec Twisted. Ce qui me rend paranoïaque, c'est assez rare pour une meilleure technique ou méthodologie de ne pas avoir de prix.

La lecture de quelques douzaines de comparaisons entre epoll et alternatives montre qu'epoll est clairement le champion de la vitesse et de l'évolutivité, en particulier qu'il évolue de manière linéaire, ce qui est fantastique. Cela dit, qu'en est-il de l'utilisation du processeur et de la mémoire, epoll est-il toujours le champion?

Réponses:


190

Pour un très petit nombre de sockets (cela varie en fonction de votre matériel, bien sûr, mais nous parlons de quelque chose de l'ordre de 10 ou moins), select peut battre epoll en termes d'utilisation de la mémoire et de vitesse d'exécution. Bien sûr, pour un si petit nombre de sockets, les deux mécanismes sont si rapides que vous ne vous souciez pas vraiment de cette différence dans la grande majorité des cas.

Une clarification, cependant. Les deux select et epoll sont mis à l'échelle de manière linéaire. Une grande différence, cependant, est que les API orientées vers l'espace utilisateur présentent des complexités basées sur des choses différentes. Le coût d'un selectappel correspond à peu près à la valeur du descripteur de fichier numéroté le plus élevé que vous lui transmettez. Si vous sélectionnez sur un seul fd, 100, alors c'est à peu près deux fois plus cher que de sélectionner sur un seul fd, 50. Ajouter plus de fds en dessous du plus élevé n'est pas tout à fait gratuit, donc c'est un peu plus compliqué que cela en pratique, mais cela est une bonne première approximation pour la plupart des implémentations.

Le coût d'epoll est plus proche du nombre de descripteurs de fichiers qui contiennent réellement des événements. Si vous surveillez 200 descripteurs de fichiers, mais que seuls 100 d'entre eux ont des événements, vous ne payez (très grossièrement) que pour ces 100 descripteurs de fichiers actifs. C'est là qu'epoll a tendance à offrir l'un de ses principaux avantages par rapport à select. Si vous avez un millier de clients pour la plupart inactifs, lorsque vous utilisez select, vous payez toujours pour les mille d'entre eux. Cependant, avec epoll, c'est comme si vous n'en aviez que quelques-uns - vous ne payez que pour ceux qui sont actifs à un moment donné.

Tout cela signifie qu'epoll réduira l'utilisation du processeur pour la plupart des charges de travail. En ce qui concerne l'utilisation de la mémoire, c'est un peu compliqué. selectparvient à représenter toutes les informations nécessaires de manière très compacte (un bit par descripteur de fichier). Et la limitation FD_SETSIZE (généralement 1024) sur le nombre de descripteurs de fichiers que vous pouvez utiliser selectsignifie que vous ne dépenserez jamais plus de 128 octets pour chacun des trois ensembles de fd que vous pouvez utiliser avecselect(lecture, écriture, exception). Comparé à ces 384 octets maximum, epoll est une sorte de porc. Chaque descripteur de fichier est représenté par une structure multi-octets. Cependant, en termes absolus, cela n'utilisera toujours pas beaucoup de mémoire. Vous pouvez représenter un grand nombre de descripteurs de fichiers dans quelques dizaines de kilo-octets (environ 20k pour 1000 descripteurs de fichiers, je pense). Et vous pouvez également ajouter le fait que vous devez dépenser les 384 octets avec selectsi vous ne voulez surveiller qu'un seul descripteur de fichier mais que sa valeur se trouve être de 1024, alors qu'avec epoll vous ne dépenseriez que 20 octets. Pourtant, tous ces chiffres sont assez petits, donc cela ne fait pas beaucoup de différence.

Et il y a aussi cet autre avantage d'epoll, dont vous êtes peut-être déjà conscient, qu'il n'est pas limité aux descripteurs de fichier FD_SETSIZE. Vous pouvez l'utiliser pour surveiller autant de descripteurs de fichiers que vous en avez. Et si vous n'avez qu'un seul descripteur de fichier, mais que sa valeur est supérieure à FD_SETSIZE, epoll fonctionne également avec cela, mais selectpas.

Au hasard, j'ai également découvert récemment un léger inconvénient epollpar rapport à selectou poll. Bien qu'aucune de ces trois API ne prenne en charge les fichiers normaux (c'est-à-dire les fichiers sur un système de fichiers), selectet pollprésente ce manque de support comme rapportant de tels descripteurs comme toujours lisibles et toujours inscriptibles. Cela les rend inadaptés à tout type significatif d'E / S de système de fichiers non bloquant, un programme qui utilise selectou pollet rencontre un descripteur de fichier du système de fichiers continuera au moins à fonctionner (ou s'il échoue, ce ne sera pas parce que de selectou poll), mais il peut - être pas la meilleure performance.

D'autre part, epolléchouera rapidement avec une erreur ( EPERM, apparemment) lorsqu'on lui demande de surveiller un tel descripteur de fichier. À proprement parler, ce n'est guère incorrect. Cela signale simplement son manque de soutien de manière explicite. Normalement, j'applaudirais les conditions d'échec explicites, mais celle-ci est non documentée (pour autant que je sache) et entraîne une application complètement cassée, plutôt qu'une application qui fonctionne simplement avec des performances potentiellement dégradées.

En pratique, le seul endroit où j'ai vu cela se produire est lors de l'interaction avec stdio. Un utilisateur peut rediriger stdin ou stdout depuis / vers un fichier normal. Alors que précédemment stdin et stdout auraient été un tube - supporté par epoll très bien - il devient alors un fichier normal et epoll échoue bruyamment, cassant l'application.


Très belle réponse. Envisagez-vous d'être explicite sur le comportement de pollpour être complet?
quark

6
Mes deux cents sur le comportement de la lecture à partir de fichiers ordinaires: je préfère généralement l'échec pur et simple à la dégradation des performances. La raison en est qu'il est beaucoup plus susceptible d'être détecté pendant le développement, et donc correctement travaillé (par exemple, en ayant une méthode alternative pour effectuer les E / S pour les fichiers réels). YMMV bien sûr: il se peut qu'il n'y ait pas de ralentissement notable, auquel cas l'échec n'est pas mieux. Mais un ralentissement spectaculaire qui ne se produit que dans des cas particuliers peut être très difficile à détecter pendant le développement, le laissant comme une bombe à retardement lorsqu'il est effectivement déployé.
quark

1
Je viens de lire complètement votre modification. Dans un sens, je suis d'accord que ce n'est probablement pas juste pour epoll de ne pas imiter ses prédécesseurs, mais là encore, je peux imaginer que le développeur qui a implémenté l'erreur EPERM a pensé: "Ce n'est pas parce qu'il a toujours été cassé, qu'il est juste de casser le mien comme bien." Et encore un autre contre-argument, je suis un programmeur défensif, tout ce qui dépasse 1 + 1 est suspect et je code de manière à permettre des échecs gracieux. Avoir le noyau déclencher une erreur hors des attentes n'est ni agréable ni prévenant.
David

1
@ Jean-Paul pourriez-vous aussi ajouter quelques explications sur kqueue?
Good Person

En mettant de côté la performance, est - il un problème résultant de cette (de man select) Le noyau Linux impose limite pas fixe, mais les marques de mise en œuvre de la glibc fd_set un type de taille fixe, avec FD_SETSIZE définie comme 1024, et les macros FD _ * () fonctionnant selon cette limite. Pour surveiller les descripteurs de fichiers supérieurs à 1023, utilisez plutôt poll (2). Sur CentOS 7, j'ai déjà rencontré des problèmes où mon propre code a échoué à select () car le noyau a renvoyé un descripteur de fichier> 1023 et je suis actuellement à la recherche d'un problème qui sent qu'il peut être Twisted et que je rencontre le même problème.
Paul D Smith

4

Lors de tests dans mon entreprise, un problème avec epoll () est survenu, donc un coût unique par rapport à select.

Lorsque vous essayez de lire depuis le réseau avec un délai d'expiration, créer un epoll_fd (au lieu d'un FD_SET) et ajouter le fd à epoll_fd, est beaucoup plus coûteux que de créer un FD_SET (qui est un simple malloc).

Selon la réponse précédente, à mesure que le nombre de FD dans le processus devient important, le coût de select () devient plus élevé, mais dans nos tests, même avec des valeurs fd dans les 10000, select était toujours un gagnant. Ce sont des cas où il n'y a qu'un seul fd qu'un thread attend, et essayant simplement de surmonter le fait que la lecture réseau et l'écriture réseau ne dépassent pas le délai lors de l'utilisation d'un modèle de thread bloquant. Bien sûr, les modèles de filetage bloquant sont peu performants par rapport aux systèmes de réacteurs non bloquants, mais il y a des occasions où, pour s'intégrer à une base de code héritée particulière, il est nécessaire.

Ce type de cas d'utilisation est rare dans les applications hautes performances, car un modèle de réacteur n'a pas besoin de créer un nouveau epoll_fd à chaque fois. Pour le modèle où un epoll_fd a une longue durée de vie - ce qui est clairement préféré pour toute conception de serveur haute performance - epoll est clairement le gagnant à tous égards.


5
Mais vous ne pouvez même pas utiliser select()si vous avez des valeurs de descripteur de fichier dans la gamme 10k + - à moins que vous ne recompiliez la moitié de votre système pour changer FD_SETSIZE - donc je me demande comment cette stratégie a fonctionné. Pour le scénario que vous avez décrit, je regarderais probablement ce qui ressemble poll()beaucoup plus à ce select()que c'est epoll()- mais supprime la limitation FD_SETSIZE.
Jean-Paul Calderone

Vous pouvez utiliser select () si vous avez des valeurs de descripteur de fichier dans la plage 10K, car vous pouvez malloc () un FD_SET. En fait, puisque FD_SETSIZE est au moment de la compilation et que la limite fd réelle est au moment de l'exécution, la SEULE utilisation sûre de FD_SET vérifie le numéro du descripteur de fichier par rapport à la taille du FD_SET, et fait un malloc (ou l'équivalent moral) si le FD_SET est trop petit. J'ai été choqué quand j'ai vu cela en production avec un client. Après avoir programmé des sockets pendant 20 ans, tout le code que j'avais écrit - et la plupart des didacticiels sur le Web - ne sont pas sûrs.
Brian Bulkowski

5
Ce n'est pas vrai, pour autant que je sache, sur les plateformes populaires. FD_SETSIZEest une constante de temps de compilation définie lorsque votre bibliothèque C est compilée. Si vous la définissez avec une valeur différente lorsque vous créez votre application, votre application et la bibliothèque C ne seront pas d'accord et les choses iront mal. Si vous avez des références prétendant qu'il est sûr de redéfinir, FD_SETSIZEje serais intéressé de les voir.
Jean-Paul Calderone
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.