Wie vergleicht sich libuv mit Boost / ASIO?


239

Ich würde mich für Aspekte interessieren wie:

  • Umfang / Funktionen
  • Performance
  • Reife

20
Lassen Sie uns diese Frage unterstützen und gute Antworten erhalten!
Viet

\ o / .. hoffe wir bekommen ein paar aufschlussreiche Antworten!
Oberstet

Antworten:


493

Umfang

Boost.Asio ist eine C ++ - Bibliothek, die mit dem Schwerpunkt Netzwerk begann. Die asynchronen E / A-Funktionen wurden jedoch auf andere Ressourcen erweitert. Da Boost.Asio Teil der Boost-Bibliotheken ist, wird der Umfang geringfügig eingeschränkt, um Doppelungen mit anderen Boost-Bibliotheken zu vermeiden. Beispielsweise stellt Boost.Asio keine Thread-Abstraktion bereit , da Boost.Thread bereits eine bereitstellt.

Andererseits ist libuv eine C-Bibliothek, die als Plattformschicht für Node.js konzipiert wurde . Es bietet eine Abstraktion für IOCP unter Windows, kqueue unter macOS und epoll unter Linux. Darüber hinaus sieht es so aus, als ob der Umfang geringfügig zugenommen hat, um Abstraktionen und Funktionen wie Threads, Threadpools und Kommunikation zwischen Threads einzuschließen.

Im Kern bietet jede Bibliothek eine Ereignisschleife und asynchrone E / A-Funktionen. Sie überschneiden sich mit einigen grundlegenden Funktionen wie Timern, Sockets und asynchronen Vorgängen. libuv hat einen breiteren Anwendungsbereich und bietet zusätzliche Funktionen wie Thread- und Synchronisationsabstraktionen, synchrone und asynchrone Dateisystemoperationen, Prozessmanagement usw. Im Gegensatz dazu bietet Boost.Asios ursprünglicher Netzwerkfokus Oberflächen, da es eine größere Anzahl netzwerkbezogener Funktionen bietet Funktionen wie ICMP, SSL, synchrone blockierende und nicht blockierende Vorgänge sowie übergeordnete Vorgänge für allgemeine Aufgaben, einschließlich Lesen aus einem Stream bis zum Empfang einer neuen Zeile.


Funktionsliste

Hier ist der kurze Vergleich einiger wichtiger Funktionen nebeneinander. Da Entwickler, die Boost.Asio verwenden, häufig andere Boost-Bibliotheken zur Verfügung haben, habe ich mich dafür entschieden, zusätzliche Boost-Bibliotheken in Betracht zu ziehen, wenn diese entweder direkt bereitgestellt oder einfach zu implementieren sind.

                         libuv Boost
Ereignisschleife: ja Asio
Threadpool: ja Asio + Threads
Einfädeln:              
  Themen: ja Themen
  Synchronisation: ja Threads
Dateisystemoperationen:
  Synchron: ja Dateisystem
  Asynchron: ja Asio + Dateisystem
Timer: ja Asio
Scatter / Gather I / O [1] : kein Asio
Vernetzung:
  ICMP: kein Asio
  DNS-Auflösung: Nur asynchrones Asio
  SSL: kein Asio
  TCP: Nur asynchrones Asio
  UDP: Nur asynchrones Asio
Signal:
  Handhabung: ja Asio
  Senden: ja nein
IPC:
  UNIX Domain Sockets: ja Asio
  Windows Named Pipe: Ja, Asio
Prozessmanagement:
  Abnehmen: ja Prozess
  E / A-Rohr: ja Prozess
  Laichen: ja Prozess
Systemabfragen:
  CPU: ja nein
  Netzwerkschnittstelle: ja nein
Serielle Schnittstellen: nein ja
TTY: ja nein
Laden der gemeinsam genutzten Bibliothek: ja Erweiterung [2]

1. E / A streuen / sammeln .

2. Boost.Extension wurde Boost nie zur Überprüfung vorgelegt. Wie hier erwähnt , hält der Autor es für vollständig.

Ereignisschleife

Während sowohl libuv als auch Boost.Asio Ereignisschleifen bereitstellen, gibt es einige subtile Unterschiede zwischen den beiden:

  • Libuv unterstützt zwar mehrere Ereignisschleifen, unterstützt jedoch nicht das Ausführen derselben Schleife aus mehreren Threads. Aus diesem Grund muss bei der Verwendung der Standardschleife ( uv_default_loop()) vorsichtig vorgegangen werden , anstatt eine neue Schleife ( uv_loop_new()) zu erstellen , da möglicherweise eine andere Komponente die Standardschleife ausführt.
  • Boost.Asio hat nicht den Begriff einer Standardschleife; Alle io_servicesind ihre eigenen Schleifen, mit denen mehrere Threads ausgeführt werden können. Um dies zu unterstützen, führt Boost.Asio eine interne Sperrung auf Kosten einer gewissen Leistung durch . Boost.Asio die Revisionsgeschichte zeigt , dass es wurden Verbesserungen mehrere Leistung die Verriegelung zu minimieren.

Threadpool

  • libuv's bietet einen Threadpool durch uv_queue_work. Die Threadpoolgröße kann über die Umgebungsvariable konfiguriert werden UV_THREADPOOL_SIZE. Die Arbeit wird außerhalb der Ereignisschleife und innerhalb des Threadpools ausgeführt. Sobald die Arbeit abgeschlossen ist, wird der Abschlusshandler in die Warteschlange gestellt, damit er innerhalb der Ereignisschleife ausgeführt werden kann.
  • Während Boost.Asio keinen Threadpool bereitstellt, io_servicekann dieser problemlos als einer fungieren, da io_servicemehrere Threads aufgerufen werden können run. Dies überträgt dem Benutzer die Verantwortung für die Thread-Verwaltung und das Thread-Verhalten, wie in diesem Beispiel zu sehen ist .

Threading und Synchronisation

  • libuv bietet eine Abstraktion für Threads und Synchronisationstypen.
  • Boost.Thread bietet einen Thread und Synchronisationstypen. Viele dieser Typen folgen eng dem C ++ 11-Standard, bieten jedoch auch einige Erweiterungen. Als Ergebnis von Boost.Asio, das es mehreren Threads ermöglicht, eine einzelne Ereignisschleife auszuführen, bietet es Stränge als Mittel zum Erstellen eines sequentiellen Aufrufs von Ereignishandlern ohne Verwendung expliziter Sperrmechanismen.

Dateisystemoperationen

  • libuv bietet eine Abstraktion für viele Dateisystemoperationen. Es gibt eine Funktion pro Operation, und jede Operation kann entweder synchron blockiert oder asynchron sein. Wenn ein Rückruf bereitgestellt wird, wird die Operation asynchron in einem internen Threadpool ausgeführt. Wenn kein Rückruf bereitgestellt wird, wird der Anruf synchron blockiert.
  • Boost.Filesystem bietet synchrone Blockierungsaufrufe für viele Dateisystemvorgänge. Diese können mit Boost.Asio und einem Threadpool kombiniert werden, um asynchrone Dateisystemoperationen zu erstellen.

Vernetzung

  • libuv unterstützt asynchrone Vorgänge auf UDP- und TCP-Sockets sowie die DNS-Auflösung. Anwendungsentwickler sollten sich bewusst sein, dass die zugrunde liegenden Dateideskriptoren auf nicht blockierend eingestellt sind. Daher sollten native synchrone Operationen Rückgabewerte und errno für EAGAINoder überprüfen EWOULDBLOCK.
  • Boost.Asio ist etwas umfangreicher in seiner Netzwerkunterstützung. Darüber hinaus bietet Boost.Asio viele der Funktionen, die das Netzwerk von libuv bietet, und unterstützt SSL- und ICMP-Sockets. Darüber hinaus bietet Boost.Asio zusätzlich zu seinen asynchronen Operationen synchrone Blockierungs- und synchrone nicht blockierende Operationen. Es gibt zahlreiche freistehende Funktionen, die allgemeine Operationen auf höherer Ebene bereitstellen, z. B. das Lesen einer festgelegten Anzahl von Bytes oder bis ein bestimmtes Trennzeichen gelesen wird.

Signal

  • libuv bietet eine Abstraktion killund Signalverarbeitung mit uv_signal_tTyp und uv_signal_*Operationen.
  • Boost.Asio stellt keine Abstraktion dar kill, signal_setbietet aber Signalverarbeitung.

IPC


API-Unterschiede

Während sich die APIs nur aufgrund der Sprache unterscheiden, gibt es hier einige wesentliche Unterschiede:

Operation and Handler Association

In Boost.Asio gibt es eine Eins-zu-Eins-Zuordnung zwischen einer Operation und einem Handler. Beispielsweise ruft jede async_writeOperation den WriteHandler einmal auf. Dies gilt für viele libuv-Operationen und -Handler. Libuvs uv_async_sendunterstützt jedoch eine Viele-zu-Eins-Zuordnung. Mehrere uv_async_sendAufrufe können dazu führen, dass uv_async_cb einmal aufgerufen wird.

Call Chains vs. Watcher Loops

Bei Aufgaben wie dem Lesen aus einem Stream / UDP, dem Verarbeiten von Signalen oder dem Warten auf Timer sind die asynchronen Aufrufketten von Boost.Asio etwas expliziter. Mit libuv wird ein Beobachter erstellt, um Interessen an einem bestimmten Ereignis zu bestimmen. Anschließend wird eine Schleife für den Beobachter gestartet, in der ein Rückruf bereitgestellt wird. Nach Erhalt des Interessenereignisses wird der Rückruf aufgerufen. Auf der anderen Seite erfordert Boost.Asio, dass jedes Mal eine Operation ausgegeben wird, wenn die Anwendung an der Behandlung des Ereignisses interessiert ist.

Um diesen Unterschied zu veranschaulichen, gibt es hier eine asynchrone Leseschleife mit Boost.Asio, in der der async_receiveAufruf mehrmals ausgegeben wird:

void start()
{
  socket.async_receive( buffer, handle_read ); ----.
}                                                  |
    .----------------------------------------------'
    |      .---------------------------------------.
    V      V                                       |
void handle_read( ... )                            |
{                                                  |
  std::cout << "got data" << std::endl;            |
  socket.async_receive( buffer, handle_read );   --'
}    

Und hier ist das gleiche Beispiel mit libuv, bei dem handle_readjedes Mal aufgerufen wird, wenn der Beobachter feststellt, dass der Socket Daten enthält:

uv_read_start( socket, alloc_buffer, handle_read ); --.
                                                      |
    .-------------------------------------------------'
    |
    V
void handle_read( ... )
{
  fprintf( stdout, "got data\n" );
}

Speicherzuweisung

Aufgrund der asynchronen Aufrufketten in Boost.Asio und der Beobachter in libuv erfolgt die Speicherzuweisung häufig zu unterschiedlichen Zeiten. Bei Beobachtern verschiebt libuv die Zuordnung, bis ein Ereignis empfangen wird, für dessen Verarbeitung Speicher erforderlich ist. Die Zuweisung erfolgt über einen Benutzerrückruf, der intern in libuv aufgerufen wird, und verschiebt die Freigabeverantwortung der Anwendung. Andererseits erfordern viele der Boost.Asio-Operationen, dass der Speicher zugewiesen wird, bevor die asynchrone Operation ausgegeben wird, wie im Fall des bufferfor async_read. Boost.Asio bietet die Möglichkeit null_buffers, auf ein Ereignis zu warten , sodass Anwendungen die Speicherzuweisung verschieben können, bis Speicher benötigt wird, obwohl dies veraltet ist.

Dieser Speicherzuordnungsunterschied zeigt sich auch innerhalb der bind->listen->acceptSchleife. Erstellt mit libuv uv_listeneine Ereignisschleife, die den Benutzerrückruf aufruft, wenn eine Verbindung zur Annahme bereit ist. Dadurch kann die Anwendung die Zuordnung des Clients verschieben, bis eine Verbindung versucht wird. Auf der anderen Seite listenändert Boost.Asio nur den Status des acceptor. Das async_acceptwartet auf das Verbindungsereignis und erfordert, dass der Peer zugewiesen wird, bevor er aufgerufen wird.


Performance

Leider habe ich keine konkreten Benchmark-Zahlen, um libuv und Boost.Asio zu vergleichen. Ich habe jedoch eine ähnliche Leistung bei Verwendung der Bibliotheken in Echtzeit- und Echtzeitanwendungen beobachtet. Wenn harte Zahlen gewünscht werden, kann der Benchmark-Test von libuv als Ausgangspunkt dienen.

Während die Profilerstellung durchgeführt werden sollte, um tatsächliche Engpässe zu identifizieren, sollten Sie außerdem die Speicherzuweisungen berücksichtigen. Für libuv ist die Speicherzuweisungsstrategie hauptsächlich auf den Allokator-Rückruf beschränkt. Auf der anderen Seite erlaubt die Boost.Asio-API keinen Allokator-Rückruf und überträgt stattdessen die Allokationsstrategie an die Anwendung. Die Handler / Rückrufe in Boost.Asio können jedoch kopiert, zugewiesen und freigegeben werden. Mit Boost.Asio können Anwendungen benutzerdefinierte Speicherzuweisungsfunktionen bereitstellen , um eine Speicherzuweisungsstrategie für Handler zu implementieren.


Reife

Boost.Asio

Die Entwicklung von Asio geht mindestens auf das OKT-2004 zurück und wurde am 22. März 2006 nach einer 20-tägigen Peer-Review in Boost 1.35 aufgenommen. Es diente auch als Referenzimplementierung und API für den Vorschlag für eine Netzwerkbibliothek für TR2 . Boost.Asio verfügt über eine beträchtliche Menge an Dokumentation , obwohl seine Nützlichkeit von Benutzer zu Benutzer unterschiedlich ist.

Die API hat auch ein ziemlich konsistentes Gefühl. Darüber hinaus sind die asynchronen Operationen im Namen der Operation explizit. Zum Beispiel acceptist synchrones Blockieren und async_acceptist asynchron. Die API bietet kostenlose Funktionen für allgemeine E / A-Aufgaben, z. B. Lesen aus einem Stream bis zum Lesen von a \r\n. Es wurde auch darauf geachtet, einige netzwerkspezifische Details zu verbergen, z. B. die ip::address_v4::any()Darstellung der Adresse "Alle Schnittstellen" von 0.0.0.0.

Schließlich bietet Boost 1.47+ Handler-Tracking , das sich beim Debuggen als nützlich erweisen kann, sowie C ++ 11-Unterstützung.

libuv

Basierend auf ihren Github-Diagrammen geht die Entwicklung von Node.js auf mindestens FEB-2009 und die Entwicklung von libuv auf MAR-2011 zurück . Das uvbook ist ein großartiger Ort für eine libuv-Einführung. Die API-Dokumentation finden Sie hier .

Insgesamt ist die API ziemlich konsistent und einfach zu bedienen. Eine Anomalie, die Verwirrung stiften kann, besteht darin, dass uv_tcp_listeneine Überwachungsschleife erstellt wird. Dies unterscheidet sich von anderen Beobachtern, die im Allgemeinen ein uv_*_startund uv_*_stopzwei Funktionen zur Steuerung der Lebensdauer der Überwachungsschleife haben. Außerdem haben einige der uv_fs_*Operationen eine anständige Anzahl von Argumenten (bis zu 7). Wenn das synchrone und asynchrone Verhalten bei Vorhandensein eines Rückrufs (dem letzten Argument) bestimmt wird, kann die Sichtbarkeit des synchronen Verhaltens verringert werden.

Ein kurzer Blick auf den libuv- Commit-Verlauf zeigt schließlich, dass die Entwickler sehr aktiv sind.


2
Danke, Mann! Gute Antwort! Ich kann mir nichts umfassenderes vorstellen :)
Viet

1
Sehr zufrieden mit der Antwort, ich vergebe Ihnen das Kopfgeld :) Lassen Sie den SO die beste Antwort für sich selbst entscheiden.
Viet

28
Unglaubliche Antwort. Dies deckt sowohl das allgemeine Bild als auch spezifische, wichtige Detailunterschiede ab (z. B. Threading / Eventloop). Vielen Dank!
Oberstet

1
@oberstet: Nein. Ich habe die Antwort aktualisiert, um zu erwähnen, dass die meisten Operationen von libuv eins zu eins sind. Libuv kann jedoch mehrere uv_async_sendAnrufe akkumulieren und alle mit einem einzigen Rückruf bearbeiten. Es ist hier dokumentiert . Vielen Dank auch an alle.
Tanner Sansbury

2
Die interne Sperrung der Ereignisschleife in Boost.Asio sieht vom Leistungsstandpunkt aus beängstigend aus. Wie kann es eine ähnliche Leistung wie ein sperrenfreies Libuv haben? Möglicherweise kann das Hinzufügen einer Warnmeldung im Leistungsbereich hilfreich sein.
Zeodtr

46

OK. Ich habe einige Erfahrung in der Verwendung beider Bibliotheken und kann einige Dinge klären.

Erstens sind diese Bibliotheken konzeptionell sehr unterschiedlich im Design. Sie haben unterschiedliche Architekturen, weil sie unterschiedlich groß sind. Boost.Asio ist eine große Netzwerkbibliothek zur Verwendung mit TCP / UDP / ICMP-Protokollen, POSIX, SSL usw. Libuv ist nur eine Ebene für die plattformübergreifende Abstraktion von IOCP für Node.js. Libuv ist also funktional eine Teilmenge von Boost.Asio (gemeinsame Funktionen nur TCP / UDP-Sockets-Threads, Timer). In diesem Fall können wir diese Bibliotheken anhand weniger Kriterien vergleichen:

  1. Integration mit Node.js - Libuv ist erheblich besser, weil es darauf abzielt (wir können es vollständig integrieren und in allen Aspekten verwenden, z. B. Cloud, z. B. Windows Azure). Asio implementiert jedoch auch fast die gleiche Funktionalität wie in der von der Ereigniswarteschlange gesteuerten Umgebung von Node.j.
  2. IOCP-Leistung - Ich konnte keine großen Unterschiede feststellen, da beide Bibliotheken die zugrunde liegende Betriebssystem-API abstrahieren. Aber sie machen es anders: Asio verwendet stark C ++ - Funktionen wie Vorlagen und manchmal TMP. Libuv ist eine native C-Bibliothek. Trotzdem ist die Asio-Realisierung von IOCP sehr effizient. UDP-Sockets in Asio sind nicht gut genug. Es ist besser, libuv für sie zu verwenden.

    Integration mit neuen C ++ - Funktionen: Asio ist besser (Asio 1.51 verwendet ausgiebig asynchrones C ++ 11-Modell, Verschiebungssemantik, variable Vorlagen). In Bezug auf die Reife ist Asio ein stabileres und ausgereifteres Projekt mit guter Dokumentation (im Vergleich zu libuv Header-Beschreibung), viele Informationen im Internet (Videogespräche, Blogs: http://www.gamedev.net/blog/950/entry-2249317-a-guide-to-getting-started-with-boostasio?pg = 1 usw.) Und sogar Bücher (nicht für Profis, aber dennoch: http://en.highscore.de/cpp/boost/index.html ). Libuv hat nur ein Online-Buch (aber auch gut) http://nikhilm.github.com/uvbook/index.htmlund mehrere Videogespräche, so dass es schwierig sein wird, alle Geheimnisse zu kennen (diese Bibliothek hat viele davon). Für eine genauere Diskussion der Funktionen siehe meine Kommentare unten.

Abschließend sollte ich sagen, dass alles von Ihren Zwecken, Ihrem Projekt und dem abhängt, was Sie konkret vorhaben.


11
Was zählt, ist Ihre technischen Fähigkeiten und Erfahrungen. Freundliche Grüße von einem Kubaner.
Unterzeichnen Sie den

2
Ich stimme allen Ihren Punkten mit Ausnahme der Dokumentation von Asio zu. Die offizielle Dokumentation wird dieser wunderbaren Bibliothek nicht gerecht. Es gibt eine Reihe anderer Dokumente und einen Boost Con Talk des Autors, den ich sehr nützlich fand. Und ich habe kein Buch für Asio gefunden. Können Sie das in Ihrer Antwort verknüpfen? Es wird sehr hilfreich sein.
Vikas

@vikas Ja, ich bin damit einverstanden, dass die Dokumentation schlecht und manchmal widersprüchlich ist, aber im Vergleich zu libuv ist es ein guter Anfang. Was Bücher betrifft, bearbeite ich meine Antwort, aber ich denke, Sie haben sie schon einmal gesehen (leider gibt es kein Buch, das ausschließlich Boost gewidmet ist - nur verstreut Informationen)
Oleksandr Karaberov

Was meinst du mit "libuv ist also funktional eine Teilmenge von Boost.Asio (TCP / UDP / Sockets und Threads)"? Laut Inhaltsverzeichnis hat nikhilm.github.com/uvbook/index.html libuv eine breitere Anwendung als boost :: asio.
Sergei Nikulov

7
@AlexanderKaraberov Könnten Sie die Probleme von ASIO mit UDP näher erläutern?
Bruno Martinez


2

Hinzufügen des Portabilitätsstatus: Zum Zeitpunkt der Veröffentlichung dieser Antwort und nach meinen eigenen Versuchen:

  • Boost.ASIO bietet keine offizielle Unterstützung für iOS und Android, z. B. funktioniert das Build-System nicht sofort für iOS.
  • libuv lässt sich problemlos für iOS und Android erstellen, wobei die offizielle Unterstützung für Android direkt in den Dokumenten enthalten ist . Mein eigenes generisches iOS-Build-Skript für Autotools-basierte Projekte funktioniert ohne Probleme.
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.