Vorbehalte von Select / Poll vs. Epoll-Reaktoren in Twisted


95

Alles, was ich gelesen und erlebt habe (Tornado-basierte Apps), lässt mich glauben, dass ePoll ein natürlicher Ersatz für Select- und Poll-basierte Netzwerke ist, insbesondere mit Twisted. Was mich paranoid macht, ist ziemlich selten, dass eine bessere Technik oder Methodik nicht mit einem Preis verbunden ist.

Ein paar Dutzend Vergleiche zwischen Epoll und Alternativen zeigen, dass Epoll eindeutig der Champion für Geschwindigkeit und Skalierbarkeit ist, insbesondere, dass es linear skaliert, was fantastisch ist. Was ist mit der Prozessor- und Speicherauslastung? Ist epoll immer noch der Champion?

Antworten:


190

Bei einer sehr kleinen Anzahl von Sockets (hängt natürlich von Ihrer Hardware ab, aber es handelt sich um etwas in der Größenordnung von 10 oder weniger) kann select die Speicherauslastung und die Laufzeitgeschwindigkeit übertreffen. Natürlich sind bei so wenigen Steckdosen beide Mechanismen so schnell, dass Sie sich in den allermeisten Fällen nicht wirklich um diesen Unterschied kümmern.

Eine Klarstellung. Sowohl Select als auch Epoll skalieren linear. Ein großer Unterschied besteht jedoch darin, dass die APIs mit Blick auf den Benutzerbereich Komplexitäten aufweisen, die auf unterschiedlichen Faktoren beruhen. Die Kosten eines selectAnrufs hängen ungefähr vom Wert des Dateideskriptors mit der höchsten Nummer ab, den Sie übergeben. Wenn Sie auf einem einzelnen fd 100 auswählen, ist das ungefähr doppelt so teuer wie auf einem einzelnen fd 50. Das Hinzufügen von mehr fds unter dem höchsten ist nicht ganz kostenlos, daher ist es in der Praxis etwas komplizierter, aber dies ist eine gute erste Annäherung für die meisten Implementierungen.

Die Kosten für epoll liegen näher an der Anzahl der Dateideskriptoren, auf denen tatsächlich Ereignisse vorhanden sind. Wenn Sie 200 Dateideskriptoren überwachen, aber nur 100 von ihnen Ereignisse enthalten, zahlen Sie (sehr grob) nur für diese 100 aktiven Dateideskriptoren. Hier bietet epoll tendenziell einen seiner Hauptvorteile gegenüber ausgewählten. Wenn Sie tausend Kunden haben, die größtenteils untätig sind, zahlen Sie bei Verwendung von select immer noch für alle tausend. Mit epoll haben Sie jedoch nur wenige - Sie zahlen nur für diejenigen, die zu einem bestimmten Zeitpunkt aktiv sind.

All dies bedeutet, dass epoll bei den meisten Workloads zu einer geringeren CPU-Auslastung führt. Was die Speichernutzung angeht, ist es ein bisschen schwierig. selectschafft es, alle notwendigen Informationen auf sehr kompakte Weise darzustellen (ein Bit pro Dateideskriptor). Und die FD_SETSIZE-Beschränkung (normalerweise 1024) für die Anzahl der Dateideskriptoren, mit denen Sie arbeiten können, selectbedeutet, dass Sie nie mehr als 128 Byte für jeden der drei fd-Sätze ausgeben, mit denen Sie arbeiten könnenselect(Lesen, Schreiben, Ausnahme). Im Vergleich zu diesen maximal 384 Bytes ist Epoll eine Art Schwein. Jeder Dateideskriptor wird durch eine Multi-Byte-Struktur dargestellt. In absoluten Zahlen wird jedoch immer noch nicht viel Speicher benötigt. Sie können eine große Anzahl von Dateideskriptoren in ein paar Dutzend Kilobyte darstellen (ungefähr 20.000 pro 1000 Dateideskriptoren, glaube ich). Sie können auch die Tatsache selectberücksichtigen , dass Sie alle 384 dieser Bytes ausgeben müssen, wenn Sie nur einen Dateideskriptor überwachen möchten, dessen Wert jedoch 1024 beträgt, während Sie mit epoll nur 20 Bytes ausgeben würden. Trotzdem sind alle diese Zahlen ziemlich klein, so dass es keinen großen Unterschied macht.

Und es gibt noch den anderen Vorteil von epoll, den Sie vielleicht bereits kennen, dass er nicht auf FD_SETSIZE-Dateideskriptoren beschränkt ist. Sie können damit so viele Dateideskriptoren überwachen, wie Sie haben. Und wenn Sie nur einen Dateideskriptor haben, dessen Wert jedoch größer als FD_SETSIZE ist, funktioniert epoll auch damit, aber selectnicht.

Zufällig habe ich kürzlich auch einen kleinen Nachteil epollgegenüber selectoder entdeckt poll. Während keine dieser drei APIs normale Dateien (dh Dateien in einem Dateisystem) unterstützt selectund polldiese mangelnde Unterstützung als Bericht über solche Deskriptoren darstellt, die immer lesbar und immer beschreibbar sind. Dies macht sie für jede sinnvolle Art von nicht blockierenden Dateisystem-E / A ungeeignet. Ein Programm, das einen Dateideskriptor aus dem Dateisystem verwendet selectoder pollzufällig findet, funktioniert zumindest weiterhin (oder wenn dies fehlschlägt, liegt dies nicht daran von selectoder poll), wenn auch vielleicht nicht mit der besten Leistung.

Auf der anderen Seite epollwird schnell mit einem Fehler ( EPERManscheinend) fehlschlagen , wenn Sie aufgefordert werden, einen solchen Dateideskriptor zu überwachen. Genau genommen ist das kaum falsch. Es signalisiert lediglich explizit den Mangel an Unterstützung. Normalerweise würde ich explizite Fehlerbedingungen begrüßen, aber diese sind nicht dokumentiert (soweit ich das beurteilen kann) und führen zu einer vollständig fehlerhaften Anwendung und nicht zu einer Anwendung, die lediglich mit potenziell verschlechterter Leistung arbeitet.

In der Praxis ist der einzige Ort, an dem ich dies gesehen habe, die Interaktion mit stdio. Ein Benutzer kann stdin oder stdout von / in eine normale Datei umleiten. Während früher stdin und stdout eine Pipe gewesen wären - von epoll ganz gut unterstützt -, wird es dann zu einer normalen Datei und epoll schlägt laut fehl und bricht die Anwendung.


Sehr schöne Antwort. Erwägen Sie, das Verhalten der pollVollständigkeit halber explizit zu beschreiben ?
Quark

6
Meine zwei Cent für das Verhalten beim Lesen aus normalen Dateien: Ich bevorzuge im Allgemeinen einen völligen Ausfall gegenüber einer Leistungsminderung. Der Grund dafür ist, dass es viel wahrscheinlicher ist, dass es während der Entwicklung erkannt und somit ordnungsgemäß umgangen wird (z. B. durch eine alternative Methode zum Ausführen der E / A für tatsächliche Dateien). YMMV natürlich: Es kann sein, dass es keine merkliche Verlangsamung gibt. In diesem Fall ist ein Fehler nicht besser. Eine dramatische Verlangsamung, die nur in besonderen Fällen auftritt, kann jedoch während der Entwicklung sehr schwer zu erfassen sein, sodass sie bei der tatsächlichen Bereitstellung als Zeitbombe verbleibt.
Quark

1
Ich muss nur deine Bearbeitung vollständig lesen. In gewissem Sinne stimme ich zu, dass es wahrscheinlich nicht richtig ist, dass epoll seine Vorgänger nicht nachahmt, aber andererseits kann ich mir vorstellen, dass der Entwickler, der den EPERM-Fehler implementiert hat, dachte: "Nur weil er immer kaputt war, macht es nicht richtig, meinen als zu brechen." Gut." Und noch ein Gegenargument: Ich bin ein defensiver Programmierer. Alles, was über 1 + 1 hinausgeht, ist verdächtig und ich codiere so, dass anmutige Fehler möglich sind. Es ist nicht nett oder rücksichtsvoll, wenn der Kernel einen unerwarteten Fehler auslöst.
David

1
@ Jean-Paul könntest du auch eine Erklärung zu Kqueue hinzufügen?
Gute Person

Abgesehen von der Leistung gibt es ein Problem, das sich daraus ergibt (von man select). Der Linux-Kernel legt kein festes Limit fest, aber die glibc-Implementierung macht fd_set zu einem Typ mit fester Größe, wobei FD_SETSIZE als 1024 definiert ist und die FD _ * () -Makros entsprechend arbeiten diese Grenze. Verwenden Sie stattdessen poll (2), um Dateideskriptoren zu überwachen, die größer als 1023 sind. Unter CentOS 7 sind bereits Probleme aufgetreten, bei denen mein eigener Code bei select () fehlgeschlagen ist, weil der Kernel ein Dateihandle> 1023 zurückgegeben hat, und ich sehe derzeit ein Problem, das nach Twisted riecht und möglicherweise dasselbe Problem betrifft.
Paul D Smith

4

Bei Tests in meinem Unternehmen trat ein Problem mit epoll () auf, also ein einziger Preis im Vergleich zu select.

Wenn Sie versuchen, mit einer Zeitüberschreitung aus dem Netzwerk zu lesen, ist das Erstellen eines epoll_fd (anstelle eines FD_SET) und das Hinzufügen des fd zum epoll_fd viel teurer als das Erstellen eines FD_SET (ein einfaches Malloc).

Gemäß der vorherigen Antwort werden die Kosten für select () höher, wenn die Anzahl der FDs im Prozess groß wird, aber in unseren Tests war select selbst bei fd-Werten in den 10.000 immer noch ein Gewinner. Dies sind Fälle, in denen es nur einen fd gibt, auf den ein Thread wartet, und der Versuch, die Tatsache zu überwinden, dass das Lesen und Schreiben im Netzwerk bei Verwendung eines blockierenden Thread-Modells keine Zeitüberschreitung verursacht. Natürlich sind blockierende Thread-Modelle im Vergleich zu nicht blockierenden Reaktorsystemen von geringer Leistung, aber es gibt Fälle, in denen eine Integration in eine bestimmte Legacy-Codebasis erforderlich ist.

Diese Art von Anwendungsfall ist in Hochleistungsanwendungen selten, da ein Reaktormodell nicht jedes Mal ein neues epoll_fd erstellen muss. Für das Modell, bei dem ein epoll_fd langlebig ist - was für jedes Hochleistungsserverdesign eindeutig bevorzugt wird -, ist epoll in jeder Hinsicht der klare Gewinner.


5
Aber Sie können nicht einmal verwenden, select()wenn Sie Dateideskriptorwerte im Bereich von 10k + haben - es sei denn, Sie kompilieren die Hälfte Ihres Systems neu, um FD_SETSIZE zu ändern -, also frage ich mich, wie diese Strategie überhaupt funktioniert hat. Für das Szenario, das Sie beschrieben haben, würde ich mir wahrscheinlich ansehen, poll()welches viel ähnlicher ist select()als es ist epoll()- aber die FD_SETSIZE-Einschränkung wird entfernt.
Jean-Paul Calderone

Sie können select () verwenden, wenn Sie Dateideskriptorwerte im 10K-Bereich haben, da Sie ein FD_SET malloc () können. Da FD_SETSIZE die Kompilierungszeit ist und das tatsächliche fd-Limit zur Laufzeit liegt, überprüft die EINZIGE sichere Verwendung von FD_SET die Nummer des Dateideskriptors anhand der Größe des FD_SET und führt ein Malloc (oder eine moralische Äquivalenz) durch, wenn das FD_SET ist zu klein. Ich war schockiert, als ich dies in der Produktion mit einem Kunden sah. Nachdem ich 20 Jahre lang Sockets programmiert habe, ist der gesamte Code, den ich jemals geschrieben habe - und die meisten Tutorials im Web - unsicher.
Brian Bulkowski

5
Soweit ich weiß, trifft dies auf keine gängigen Plattformen zu. FD_SETSIZEist eine Kompilierungszeitkonstante, die beim Kompilieren Ihrer C- Bibliothek festgelegt wird. Wenn Sie beim Erstellen Ihrer Anwendung einen anderen Wert definieren, stimmen Ihre Anwendung und die C-Bibliothek nicht überein und es läuft schlecht. Wenn Sie Referenzen haben, die behaupten, es sei sicher, sie neu zu definieren, FD_SETSIZEwäre ich interessiert, sie zu sehen.
Jean-Paul Calderone
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.