webentwicklung-frage-antwort-db.com.de

Wie vergleicht libuv mit Boost / ASIO?

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

  • umfang/Merkmale
  • performance
  • reife
217
oberstet

Umfang

Boost.Asio ist eine C++ - Bibliothek, deren Schwerpunkt auf der Vernetzung lag, deren asynchrone E/A-Funktionen jedoch auf andere Ressourcen ausgeweitet wurden. Da Boost.Asio Teil der Boost-Bibliotheken ist, wird der Umfang geringfügig eingeschränkt, um eine Duplizierung 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 Plattformebene für Node.js dient. Es bietet eine Abstraktion für IOCP unter Windows, kqueue unter macOS und epoll unter Linux. Darüber hinaus scheint der Umfang geringfügig zugenommen zu haben, 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 haben Überlappungen für einige der Grundfunktionen, z. B. Timer, Sockets und asynchrone Vorgänge. libuv hat einen breiteren Anwendungsbereich und bietet zusätzliche Funktionen, wie z. B. Thread- und Synchronisationsabstraktionen, synchrone und asynchrone Dateisystemoperationen, Prozessverwaltung usw. Im Gegensatz dazu weist Boost.Asios ursprünglicher Netzwerkfokus eine breitere Palette netzwerkbezogener Funktionen auf Funktionen wie ICMP, SSL, synchrone und nicht blockierende Vorgänge sowie Vorgänge auf höherer Ebene für allgemeine Aufgaben, einschließlich des Lesens aus einem Stream, bis eine neue Zeile empfangen wird.


Funktionsliste

Hier finden Sie eine kurze Gegenüberstellung einiger Hauptfunktionen. 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 trivial zu implementieren sind.

 libuv Boost 
 Event-Schleife: yes Asio 
 Threadpool: yes Asio + Threads 
 Threading: 
 Threads: yes Threads 
 Synchronisation : yes Threads 
 Dateisystemoperationen: 
 Synchron: yes Dateisystem 
 Asynchron: yes Asio + Dateisystem 
 Zeitgeber: yes Asio 
 Scatter/Gather I /O[1]: nein Asio 
 Netzwerk: 
 ICMP: nein Asio 
 DNS-Auflösung: nur asynchron Asio 
 SSL: nein Asio 
 TCP: nur asynchron Asio 
 UDP: Async-only Asio 
 Signal: 
 Handhabung: ja Asio 
 Senden: ja nein 
 IPC: 
 UNIX Domänen-Sockets: ja Asio 
 Windows Named Pipe: ja Asio 
 Prozessverwaltung: 
 Trennen: ja Prozess 
 E/A-Pipe: ja Prozess 
 Spawnen: ja Verarbeiten von 
 Systemabfragen: 
 CPU: ja nein 
 Netzwerkschnittstelle: ja nein 
 Serielle Ports: nein ja 
 TTY: ja nein 
 Shared Library Wird geladen: ja Erweiterung[2]

1. Scatter/Gather I/O .

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

Ereignisschleife

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

  • Während libuv mehrere Ereignisschleifen unterstützt, unterstützt es nicht die Ausführung derselben Schleife von mehreren Threads. Aus diesem Grund ist bei der Verwendung der Standardschleife (uv_default_loop()) Vorsicht geboten, anstatt eine neue Schleife (uv_loop_new()) zu erstellen, da möglicherweise eine andere Komponente die Standardschleife ausführt.
  • Boost.Asio kennt keine Standardschleife. Alle io_service sind ihre eigenen Schleifen, mit denen mehrere Threads ausgeführt werden können. Um dies zu unterstützen, führt Boost.Asio interne Sperre auf Kosten einiger Leistung aus. Die Überarbeitung von Boost.Asio Verlauf zeigt an, dass mehrere Leistungsverbesserungen vorgenommen wurden, um das Sperren zu minimieren.

Threadpool

  • libuvs bietet einen Threadpool durch uv_queue_work. Die Threadpool-Größe ist über die Umgebungsvariable UV_THREADPOOL_SIZE Konfigurierbar. Die Arbeit wird außerhalb der Ereignisschleife und innerhalb des Threadpools ausgeführt. Sobald die Arbeit abgeschlossen ist, wird der Beendigungshandler in die Warteschlange gestellt, um innerhalb der Ereignisschleife ausgeführt zu werden.
  • Während Boost.Asio keinen Threadpool bereitstellt, kann io_service Aufgrund von io_service, Das das Aufrufen von run durch mehrere Threads ermöglicht, problemlos als einer funktionieren. Dies überträgt die Verantwortung für Thread-Verwaltung und -Verhalten an den Benutzer, wie im Beispiel this zu sehen.

Threading und Synchronisation

  • libuv bietet eine Abstraktion für Threads und Synchronisationstypen.
  • Boost.Thread liefert einen Thread und Synchronisationstypen. Viele dieser Typen orientieren sich eng am 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 , um einen sequentiellen Aufruf von Ereignishandlern zu erstellen, ohne explizite Sperrmechanismen zu verwenden.

Dateisystemvorgänge

  • libuv bietet eine Abstraktion für viele Dateisystemoperationen. Es gibt eine Funktion pro Operation, und jede Operation kann entweder synchron blockieren 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 Dateisystemoperationen. Diese können mit Boost.Asio und einem Threadpool kombiniert werden, um asynchrone Dateisystemoperationen zu erstellen.

Vernetzung

  • libuv unterstützt asynchrone Operationen auf UDP- und TCP Sockets sowie DNS-Auflösung. Anwendungsentwickler sollten sich darüber im Klaren sein, dass die zugrunde liegenden Dateideskriptoren nicht blockierend sind. Daher sollten native synchrone Operationen die Rückgabe überprüfen Werte und errno für EAGAIN oder EWOULDBLOCK.
  • Boost.Asio ist ein bisschen umfangreicher in der Netzwerkunterstützung. Zusätzlich zu vielen Funktionen, die das Netzwerk von libuv bietet, unterstützt Boost.Asio SSL- und ICMP-Sockets. Darüber hinaus bietet Boost.Asio neben den asynchronen Vorgängen auch synchrone Blockierungs- und Nichtblockierungsvorgänge. 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 Begrenzungszeichen gelesen wird.

Signal

  • libuv bietet eine Abstraktion kill und eine Signalbehandlung mit den Operationen uv_signal_t und uv_signal_*.
  • Boost.Asio stellt kill keine Abstraktion zur Verfügung, aber sein signal_set stellt die Signalverarbeitung bereit.

IPC


API-Unterschiede

Obwohl 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_write - Operation den WriteHandler einmal auf. Dies gilt für viele libuv-Operationen und -Handler. Libuvs uv_async_send Unterstützt jedoch eine 1: 1-Zuordnung. Mehrere uv_async_send - Aufrufe können dazu führen, dass uv_async_cb einmal angerufen werden.

Ketten vs. Watcher Loops aufrufen

Bei Aufgaben wie dem Lesen aus einem Stream/UDP, dem Verarbeiten von Signalen oder dem Warten auf Zeitgeber sind die asynchronen Aufrufketten von Boost.Asio etwas expliziter. Mit libuv wird ein Beobachter erstellt, um Interessen an einem bestimmten Ereignis zu kennzeichnen. Anschließend wird eine Schleife für den Beobachter gestartet, in der ein Rückruf bereitgestellt wird. Bei Erhalt des Ereignisses von Interesse wird der Rückruf aufgerufen. Auf der anderen Seite erfordert Boost.Asio, dass eine Operation jedes Mal 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 Aufruf async_receive 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 dasselbe Beispiel mit libuv, in dem handle_read Jedes 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 Benutzer-Rückruf, der intern von libuv aufgerufen wird und die Verantwortung für die Freigabe der Anwendung aufschiebt. Andererseits erfordern viele Boost.Asio-Operationen, dass der Speicher zugewiesen wird, bevor die asynchrone Operation ausgeführt wird, beispielsweise der Fall von buffer für async_read. Boost.Asio bietet null_buffers , mit dem auf ein Ereignis gewartet werden kann, sodass Anwendungen die Speicherzuweisung verschieben können, bis Speicher benötigt wird, obwohl dies veraltet ist.

Diese Speicherzuordnungsdifferenz zeigt sich auch innerhalb der bind->listen->accept - Schleife. Mit libuv erstellt uv_listen Eine Ereignisschleife, die den Benutzer-Rückruf aufruft, wenn eine Verbindung zur Annahme bereit ist. Auf diese Weise kann die Anwendung die Zuweisung des Clients verschieben, bis versucht wird, eine Verbindung herzustellen. Auf der anderen Seite ändert Boost.Asios listen nur den Zustand des acceptor . Das async_accept wartet 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 der Verwendung der Bibliotheken in Echtzeit- und Fast-Echtzeit-Anwendungen beobachtet. Wenn harte Zahlen gewünscht werden, kann libuvs Benchmark-Test als Ausgangspunkt dienen.

Während die Profilerstellung durchgeführt werden sollte, um tatsächliche Engpässe zu identifizieren, sollten Sie 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 auf die Anwendung. Die Handler/Rückrufe in Boost.Asio können jedoch kopiert, zugewiesen und freigegeben werden. Boost.Asio ermöglicht es Anwendungen, benutzerdefinierte Speicherzuweisung Funktionen bereitzustellen, um eine Speicherzuweisungsstrategie für Handler zu implementieren.


Reife

Boost.Asio

Die Entwicklung von Asio reicht bis mindestens OKT-2004 zurück und wurde am 22. März 2006 nach einer 20-tägigen Begutachtung in Boost 1.35 aufgenommen. Es diente auch als Referenzimplementierung und API für Networking Library Proposal for TR2 . Boost.Asio hat eine Menge Dokumentation , obwohl seine Nützlichkeit von Benutzer zu Benutzer variiert.

Die API fühlt sich auch ziemlich konsistent an. Darüber hinaus sind die asynchronen Vorgänge im Namen des Vorgangs explizit enthalten. Zum Beispiel ist accept synchrones Blockieren und async_accept Ist asynchron. Die API bietet kostenlose Funktionen für allgemeine E/A-Aufgaben, beispielsweise das Lesen aus einem Stream, bis ein \r\n Gelesen wird. Es wurde auch darauf geachtet, einige netzwerkspezifische Details zu verbergen, wie beispielsweise die ip::address_v4::any(), die die "alle Schnittstellen" -Adresse von 0.0.0.0 Darstellt.

Schließlich bietet Boost 1.47+ Handler-Tracking , was 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 vbook ist ein großartiger Ort für eine Einführung in libuv. Die API-Dokumentation lautet hier .

Insgesamt ist die API ziemlich konsistent und einfach zu bedienen. Eine Anomalie, die zu Verwirrung führen kann, besteht darin, dass uv_tcp_listen Eine Überwachungsschleife erstellt. Dies unterscheidet sich von anderen Beobachtern, die im Allgemeinen über ein Funktionspaar uv_*_start Und uv_*_stop Verfügen, um die Lebensdauer der Beobachterschleife zu steuern. Außerdem haben einige der uv_fs_* - Operationen eine anständige Menge an Argumenten (bis zu 7). Wenn das synchrone und asynchrone Verhalten bei Vorhandensein eines Rückrufs (letztes Argument) bestimmt wird, kann die Sichtbarkeit des synchronen Verhaltens verringert werden.

Ein kurzer Blick auf die libuv commit history zeigt, dass die Entwickler sehr aktiv sind.

463
Tanner Sansbury

Okay. Ich habe einige Erfahrung in der Verwendung beider Bibliotheken und kann einige Dinge klarstellen.

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

  1. Die Integration mit Node.js - Libuv ist erheblich besser, weil es darauf abzielt (wir können es vollständig integrieren und in allen Aspekten verwenden, zum Beispiel Cloud, zum Beispiel Windows Azure). Asio implementiert jedoch 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 auf eine andere Art und Weise: Asio verwendet stark C++ - Funktionen wie Vorlagen und manchmal TMP. Libuv ist eine native C-Bibliothek. Trotzdem ist die Realisierung von IOCP durch Asio sehr effizient. UDP-Sockets in Asio sind nicht gut genug, es ist besser, libuv für sie zu verwenden.

    Integration in neue C++ - Funktionen: Asio ist besser (Asio 1.51 verwendet in großem Umfang asynchrones C++ 11-Modell, Verschiebungssemantik, variable Vorlagen). In Bezug auf die Reife ist Asio ein stabileres und ausgereifteres Projekt mit guter Dokumentation (wenn man es mit libuv vergleicht) Beschreibung der Überschriften), viele Informationen im Internet (Videogespräche, Blogs: http://www.gamedev.net/blog/950/entry-2249317-a-guide-to-getting-started-with -boostasio? pg = 1 , etc.) und sogar Bücher (nicht für Profis, aber trotzdem: http://en.highscore.de/cpp/boost/index.html ). Libuv hat nur ein (aber auch gutes) Online - Buch http://nikhilm.github.com/uvbook/index.html und mehrere Videogespräche, so dass es schwierig sein wird, alle Geheimnisse zu kennen (dieses) Bibliothek hat viele von ihnen). Für eine genauere Diskussion der Funktionen siehe meine Kommentare unten.

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

45

Ein großer Unterschied ist, dass der Autor von Asio (Christopher Kohlhoff) seine Bibliothek für die Aufnahme in die C++ Standard Library pflegt (siehe http://www.open-std.org/jtc1/sc22/wg21/docs/papers) /2007/n2175.pdf und http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4370.html

16
Vinnie Falco