2010-01-09 5 views
88

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, besonders mit Twisted. Das macht mich paranoid, es ist ziemlich selten für eine bessere Technik oder Methodik, nicht mit einem Preis zu kommen.Vorbehalte von Select/Poll vs. Epoll Reaktoren in Twisted

Lesen Sie ein paar Dutzend Vergleiche zwischen Epoll und Alternativen zeigt, dass Epoll ist eindeutig der Champion für Geschwindigkeit und Skalierbarkeit, speziell, dass es in einer linearen Weise skaliert, die fantastisch ist. Wie sieht es mit der Prozessor- und Speichernutzung aus, ist epoll immer noch der Champion?

Antwort

180

Für eine sehr kleine Anzahl von Sockeln (hängt natürlich von Ihrer Hardware ab, aber wir reden über etwas in der Größenordnung von 10 oder weniger), wählen Sie epoll in Speicherauslastung und Laufzeitgeschwindigkeit. Natürlich sind bei so wenigen Sockeln beide Mechanismen so schnell, dass Sie sich in den allermeisten Fällen nicht wirklich um diesen Unterschied kümmern.

Eine Klarstellung, obwohl. Sowohl select als auch epoll skalieren linear. Ein großer Unterschied besteht jedoch darin, dass die Benutzeroberflächen-APIs Komplexitäten haben, die auf verschiedenen Dingen basieren. Die Kosten eines select Aufrufs gehen ungefähr mit dem Wert des Dateideskriptors mit der höchsten Nummer einher, den Sie übergeben. Wenn Sie auf einem einzigen fd, 100 wählen, dann ist das ungefähr doppelt so teuer wie die Auswahl auf einem einzigen fd, 50. Das Hinzufügen von mehr fds unterhalb des höchsten ist nicht ganz kostenlos, also ist es etwas komplizierter als das in der Praxis, aber das ist eine gute erste Annäherung für die meisten Implementierungen.

Die Kosten von epoll liegen näher an der Anzahl der Dateideskriptoren, die tatsächlich Ereignisse auf ihnen haben. Wenn Sie 200 Dateideskriptoren überwachen, aber nur 100 davon über Ereignisse verfügen, zahlen Sie (grob gesagt) nur für diese 100 aktiven Dateideskriptoren. Hier bietet epoll einen seiner wichtigsten Vorteile gegenüber der Auswahl. Wenn Sie tausende Kunden haben, die meist untätig sind, dann zahlen Sie, wenn Sie select verwenden, immer noch für alle tausend. Mit epoll haben Sie jedoch nur ein paar - Sie zahlen nur für diejenigen, die zu einem bestimmten Zeitpunkt aktiv sind.

All dies bedeutet, dass epoll bei den meisten Workloads zu weniger CPU-Auslastung führt. Was die Speicherauslastung betrifft, ist es ein bisschen ein Toss Up. select schafft es, alle notwendigen Informationen in einer sehr kompakten Art und Weise darzustellen (ein Bit pro Dateideskriptor). Und die FD_SETSIZE (typischerweise 1024) Beschränkung, wie viele Dateideskriptoren Sie mit select verwenden können, bedeutet, dass Sie nie mehr als 128 Bytes für jeden der drei fd-Sätze ausgeben, die Sie mit select (lesen, schreiben, Ausnahme) verwenden können. Verglichen mit diesen 384 Bytes ist epoll eine Art Schwein. Jeder Dateideskriptor wird durch eine Multi-Byte-Struktur dargestellt. In absoluten Zahlen wird es jedoch immer noch nicht viel Speicher verbrauchen. Sie können eine große Anzahl von Dateideskriptoren in ein paar Dutzend Kilobyte darstellen (ungefähr 20k pro 1000 Dateideskriptoren, denke ich). Und Sie können auch die Tatsache einwerfen, dass Sie alle 384 dieser Bytes mit select ausgeben müssen, wenn Sie nur einen Dateideskriptor überwachen möchten, aber sein Wert ist zufällig 1024, während Sie mit epoll nur 20 Bytes ausgeben würden. Trotzdem sind all diese Zahlen ziemlich klein, so dass es keinen großen Unterschied macht.

Und es gibt noch diesen anderen Vorteil von epoll, von dem Sie vielleicht schon wissen, dass es 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, aber select nicht.

Zufällig habe ich vor kurzem einen kleinen Nachteil zu epoll im Vergleich zu select oder poll entdeckt.Während keine dieser drei APIs normale Dateien (dh Dateien in einem Dateisystem) unterstützt, stellen select und poll diesen Mangel an Unterstützung dar, da solche Deskriptoren immer lesbar und immer schreibbar sind. Dies macht sie ungeeignet für jede sinnvolle Art von blockierungsfreiem Dateisystem-I/O, ein Programm, das select oder poll verwendet und zufälligerweise einen Dateideskriptor aus dem Dateisystem findet, wird zumindest weiter funktionieren (oder wenn es fehlschlägt, wird es nicht funktionieren sei wegen select oder poll), wenn auch vielleicht nicht mit der besten Leistung.

Auf der anderen Seite wird epoll schnell mit einem Fehler (EPERM, anscheinend) fehlschlagen, wenn aufgefordert, einen solchen Dateideskriptor zu überwachen. Streng genommen ist das kaum inkorrekt. Es signalisiert lediglich seinen Mangel an Unterstützung auf explizite Weise. Normalerweise würde ich expliziten Fehlerbedingungen applaudieren, aber diese ist undokumentiert (soweit ich das beurteilen kann) und führt zu einer völlig kaputten Anwendung, und nicht zu einer, die lediglich mit einer möglicherweise verschlechterten Leistung arbeitet.

In der Praxis ist der einzige Ort, wo ich diese kommen gesehen habe, ist, wenn sie mit stdio interagieren. Ein Benutzer kann stdin oder stdout von/zu einer normalen Datei umleiten. Während vorher stdin und stdout eine Pipe waren - unterstützt von epoll - ist es eine normale Datei und epoll versagt laut und bricht die Anwendung.

+0

Sehr nette Antwort. Betrachten Sie explizit das Verhalten von 'Poll' aus Gründen der Vollständigkeit? – quark

+6

Meine zwei Cent auf das Verhalten des Lesens von normalen Dateien: Ich bevorzuge im Allgemeinen direkten Ausfall zu Leistungseinbußen. Der Grund ist, dass es viel wahrscheinlicher ist, während der Entwicklung entdeckt zu werden, und somit richtig herumzuarbeiten (sagen wir, indem wir eine alternative Methode haben, die E/A für tatsächliche Dateien zu machen). YMMV natürlich: Es kann keine merkliche Verlangsamung geben, in welchem ​​Fall der Ausfall nicht besser ist. Aber eine dramatische Verlangsamung, die nur in speziellen Fällen auftritt, kann während der Entwicklung sehr schwer zu erfassen sein, so dass sie als echte Zeitbombe zurückbleibt. – quark

+1

Ich muss deine Bearbeitung vollständig lesen. In gewissem Sinne stimme ich zu, dass es wahrscheinlich nicht richtig für epoll ist, seine Vorgänger nicht nachzuahmen, aber dann kann ich mir den Entwickler vorstellen, der den EPERM-Fehler implementiert hat: "Nur weil es immer kaputt war, macht es nicht richtig, meinen zu brechen Gut." Und noch ein weiteres Gegenargument, ich bin ein defensiver Programmierer, alles nach 1 + 1 ist suspekt und ich code so, dass ich graziöse Fehler zulasse. Wenn der Kernel einen unerwarteten Fehler hat, ist das nicht nett oder rücksichtsvoll. – David

3

In Tests in meiner Firma, kam ein Problem mit epoll() nach oben, so dass eine einzelne Kosten aus, die verglichen.

Beim Versuch, mit einem Timeout aus dem Netzwerk zu lesen, ist das Erstellen eines epoll_fd (anstelle von FD_SET) und das Hinzufügen von fd zu epoll_fd viel teurer als das Erstellen eines FD_SET (ein einfaches malloc).

Gemäß der vorherigen Antwort, da die Anzahl der FDs im Prozess groß wird, werden die Kosten von select() höher, aber in unseren Tests war select sogar mit fd-Werten in den 10.000ern immer noch ein Gewinner . In diesen Fällen gibt es nur einen fd, auf den ein Thread wartet, und versucht lediglich, die Tatsache zu überwinden, dass das Lesen im Netzwerk und das Schreiben im Netzwerk bei Verwendung eines blockierenden Thread-Modells nicht abläuft. Blocking-Thread-Modelle sind im Vergleich zu nicht blockierenden Reaktorsystemen natürlich wenig leistungsfähig, aber es gibt Gelegenheiten, bei denen eine Integration in eine bestimmte Legacy-Code-Basis erforderlich ist.

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

+5

Aber du kannst 'select()' nicht benutzen, wenn du Dateideskriptorwerte im 10k + Bereich hast - es sei denn du kompilierst die Hälfte deines Systems, um FD_SETSIZE zu ändern - also frage ich mich, wie diese Strategie überhaupt funktioniert. Für das Szenario, das Sie beschrieben haben, würde ich wahrscheinlich 'poll()' betrachten, was viel mehr wie 'select()' ist, als es wie 'epoll() 'ist - aber die Beschränkung FD_SETSIZE entfernt. –

+0

Sie können select() verwenden, wenn Sie Dateideskriptorwerte im 10K-Bereich haben, weil Sie malloc() ein FD_SET verwenden können. Da FD_SETSIZE Kompilierzeit ist und das tatsächliche fd-Limit zur Laufzeit ist, überprüft die EINZIGE sichere Verwendung von FD_SET tatsächlich die Nummer des Dateideskriptors gegen die Größe von FD_SET und führt ein malloc (oder moralisches Äquivalent) durch, wenn FD_SET ist zu klein. Ich war schockiert, als ich das in der Produktion mit einem Kunden sah. Nach der Programmierung von Sockets für 20 Jahre, ist der gesamte Code, den ich jemals geschrieben habe - und die meisten Tutorials im Internet - unsicher. –

+5

Das ist, soweit ich weiß, nicht auf allen gängigen Plattformen der Fall. 'FD_SETSIZE' ist eine Kompilierzeitkonstante, die gesetzt wird, wenn Ihre * C * -Bibliothek kompiliert wird. Wenn Sie beim Erstellen Ihrer Anwendung einen anderen Wert angeben, wird Ihre Anwendung und die C-Bibliothek nicht einverstanden sein und die Dinge werden schlecht laufen. Wenn Sie Referenzen haben, die behaupten, dass es sicher ist, 'FD_SETSIZE' neu zu definieren, würde mich das interessieren. –