web-dev-qa-db-fra.com

Comment libuv se compare-t-il à Boost / ASIO?

Je serais intéressé par des aspects comme:

  • portée/caractéristiques
  • performance
  • maturité
217
oberstet

Portée

Boost.Asio est une bibliothèque C++ qui a commencé par se concentrer sur la mise en réseau, mais ses fonctionnalités d'E/S asynchrones ont été étendues à d'autres ressources. De plus, Boost.Asio faisant partie des bibliothèques Boost, sa portée est légèrement réduite pour éviter les doubles emplois avec d'autres bibliothèques Boost. Par exemple, Boost.Asio ne fournira pas une abstraction de thread, car Boost.Thread en fournit déjà une.

D'autre part, libuv est une bibliothèque C conçue pour être la couche de plate-forme de Node.js . Il fournit une abstraction pour IOCP sous Windows, kqueue sous macOS et epoll sous Linux. En outre, il semble que sa portée ait légèrement augmenté pour inclure des abstractions et des fonctionnalités, telles que les threads, les pools de threads et la communication inter-thread.

À la base, chaque bibliothèque fournit une boucle d'événement et des fonctionnalités d'E/S asynchrones. Elles se chevauchent pour certaines des fonctionnalités de base, telles que les temporisations, les sockets et les opérations asynchrones. libuv a une portée plus large et fournit des fonctionnalités supplémentaires, telles que des abstractions de threads et de synchronisation, des opérations de système de fichiers synchrones et asynchrones, la gestion des processus, etc. En revanche, le focus original de Boost.Asio sur les réseaux, car il fournit un ensemble plus riche de des fonctionnalités telles que ICMP, SSL, des opérations de blocage synchrone et des opérations sans blocage et des opérations de niveau supérieur pour des tâches courantes, notamment la lecture d'un flux jusqu'à la réception d'une nouvelle ligne.


Liste des fonctionnalités

Voici la brève comparaison côte à côte sur certaines des principales caractéristiques. Les développeurs utilisant Boost.Asio ayant souvent d'autres bibliothèques Boost disponibles, j'ai choisi d'envisager des bibliothèques Boost supplémentaires si elles sont fournies directement ou si leur mise en œuvre est triviale.

 libuv Boost 
 Boucle d'événement: oui Asio 
 Threadpool: oui Asio + Threads 
 Threading: 
 Threads: oui Threads 
 Synchronisation : oui Fils 
 Opérations sur le système de fichiers: 
 Synchrone: oui Système de fichiers 
 Asynchrone: oui Asio + Système de fichiers 
 Temporisateurs: oui Asio 
 Dispersion/Collecte I/O[1]: no Asio 
 Réseau: 
 ICMP: no Asio 
 Résolution DNS: uniquement asynchrone Asio 
 SSL: non Asio 
 TCP: uniquement asynchrone Asio 
 UDP: Async seulement Asio 
 Signal: 
 Traitement: oui Asio 
 Envoi: oui non 
 IPC: 
 UNIX Domain Sockets: yes Asio 
 Windows Nipe Pipe: yes Asio 
 Gestion des processus: 
 Détachement: yes Process 
 I/O Pipe: yes Process 
 Génération: oui Processus 
 Requêtes système: 
 CPU: oui non 
 Interface réseau: oui non 
 Ports série: non oui 
 TTY: oui non 
 Chargement de la bibliothèque partagée: oui Extension[2]

1. Scatter/Gather I/O .

2. Boost.Extension n'a jamais été soumis pour examen à Boost. Comme indiqué ici , l'auteur considère qu'il est complet.

Boucle d'événement

Bien que libuv et Boost.Asio fournissent des boucles d'événement, il existe quelques différences subtiles entre les deux:

  • Bien que libuv prenne en charge plusieurs boucles d’événements, il ne prend pas en charge l’exécution de la même boucle à partir de plusieurs threads. Pour cette raison, vous devez faire attention lorsque vous utilisez la boucle par défaut (uv_default_loop()) plutôt que de créer une nouvelle boucle (uv_loop_new()), car un autre composant exécute peut-être la boucle par défaut.
  • Boost.Asio n'a pas la notion d'une boucle par défaut; tous io_service sont leurs propres boucles permettant l'exécution de plusieurs threads. Pour supporter cela, Boost.Asio effectue verrouillage interne au prix de quelques performance . La révision de Boost.Asio historique indique que plusieurs améliorations des performances ont été apportées pour minimiser le verrouillage.

Threadpool

  • libuv fournit un pool de threads via uv_queue_work. La taille du pool de threads est configurable via la variable d'environnement UV_THREADPOOL_SIZE. Le travail sera exécuté en dehors de la boucle d'événements et dans le pool de threads. Une fois le travail terminé, le gestionnaire d'achèvement sera mis en file d'attente pour s'exécuter dans la boucle d'événements.
  • Bien que Boost.Asio ne fournisse pas de pool de threads, le io_service Peut facilement fonctionner comme tel à la suite de io_service Permettant à plusieurs threads d'appeler run. Cela place la responsabilité de la gestion des threads et du comportement sur l'utilisateur, comme on peut le voir dans l'exemple this .

Filetage et Synchronisation

  • libuv fournit une abstraction aux threads et aux types de synchronisation.
  • Boost.Thread fournit un fil et des types de synchronisation. Beaucoup de ces types suivent de près le standard C++ 11, mais fournissent également des extensions. Du fait que Boost.Asio permet à plusieurs threads d’exécuter une seule boucle d’événements, il fournit brins comme moyen de créer un appel séquentiel de gestionnaires d’événements sans utiliser de mécanismes de verrouillage explicites.

Opérations du système de fichiers

  • libuv fournit une abstraction pour de nombreuses opérations du système de fichiers. Il existe une fonction par opération et chaque opération peut être un blocage synchrone ou asynchrone. Si un rappel est fourni, l'opération sera exécutée de manière asynchrone dans un pool de threads interne. Si aucun rappel n'est fourni, l'appel sera un blocage synchrone.
  • Boost.Filesystem fournit des appels de blocage synchrones pour de nombreuses opérations de système de fichiers. Ceux-ci peuvent être combinés avec Boost.Asio et un pool de threads pour créer des opérations de système de fichiers asynchrones.

La mise en réseau

  • libuv prend en charge les opérations asynchrones sur les sockets UDP et TCP, ainsi que la résolution DNS. Les développeurs d'applications doivent savoir que les descripteurs de fichiers sous-jacents sont définis sur non bloquants. Par conséquent, les opérations synchrones natives doivent vérifier les retours valeurs et errno pour EAGAIN ou EWOULDBLOCK.
  • Boost.Asio est un peu plus riche en support réseau. En outre, de nombreuses fonctionnalités de mise en réseau de libuv, Boost.Asio, prend en charge les sockets SSL et ICMP. De plus, Boost.Asio fournit des opérations de blocage synchrone et non bloquant synchrone, en plus de ses opérations asynchrones. Il existe de nombreuses fonctions indépendantes qui fournissent des opérations communes de niveau supérieur, telles que la lecture d'un nombre d'octets défini, ou jusqu'à la lecture d'un caractère délimiteur spécifié.

Signal

  • libuv fournit une abstraction kill et un traitement du signal avec ses opérations uv_signal_t et uv_signal_*.
  • Boost.Asio ne fournit pas une abstraction à kill, mais son signal_set assure la gestion du signal.

IPC


Différences d'API

Bien que les API diffèrent en fonction du langage, voici quelques différences essentielles:

Association Opération et Handler

Dans Boost.Asio, il existe un mappage univoque entre une opération et un gestionnaire. Par exemple, chaque opération async_write invoquera une fois WriteHandler . Ceci est vrai pour beaucoup d'opérations et de gestionnaires de libuv. Cependant, uv_async_send De libuv prend en charge un mappage plusieurs à un. Plusieurs appels uv_async_send Peuvent entraîner la uv_async_cb être appelé une fois.

Chaînes d'appel vs Watcher Loops

Lorsque vous traitez une tâche, telle que la lecture d'un flux/UDP, la gestion de signaux ou l'attente de minuteries, les chaînes d'appels asynchrones de Boost.Asio sont un peu plus explicites. Avec libuv, un observateur est créé pour désigner les intérêts d'un événement particulier. Une boucle est alors lancée pour l'observateur, où un rappel est fourni. À la réception de l'événement d'intérêt, le rappel sera appelé. D'autre part, Boost.Asio nécessite qu'une opération soit émise chaque fois que l'application est intéressée par le traitement de l'événement.

Pour illustrer cette différence, voici une boucle de lecture asynchrone avec Boost.Asio, dans laquelle l'appel async_receive Sera émis à plusieurs reprises:

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 );   --'
}    

Et voici le même exemple avec libuv, où handle_read Est invoqué chaque fois que l'observateur constate que le socket contient des données:

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

Allocation de mémoire

En raison des chaînes d'appels asynchrones dans Boost.Asio et des observateurs dans libuv, l'allocation de mémoire se produit souvent à des moments différents. Avec les observateurs, libuv diffère l’allocation jusqu’à ce qu’il reçoive un événement dont la gestion nécessite de la mémoire. L'attribution est effectuée via un rappel de l'utilisateur, invoqué de manière interne à libuv, et reporte la responsabilité de désallocation de l'application. D'autre part, de nombreuses opérations Boost.Asio nécessitent l'allocation de mémoire avant d'émettre l'opération asynchrone, comme dans le cas de buffer pour async_read. Boost.Asio fournit null_buffers , qui peut être utilisé pour écouter un événement, ce qui permet aux applications de différer l’allocation de mémoire jusqu’à ce que la mémoire soit nécessaire, bien que ce soit obsolète.

Cette différence d'allocation de mémoire se présente également dans la boucle bind->listen->accept. Avec libuv, uv_listen Crée une boucle d'événements qui appelle le rappel de l'utilisateur lorsqu'une connexion est prête à être acceptée. Cela permet à l'application de différer l'allocation du client jusqu'à la tentative de connexion. D'autre part, listen de Boost.Asio ne modifie que l'état de acceptor . Le async_accept écoute l'événement de connexion et exige que l'homologue soit attribué avant d'être appelé.


Performance

Malheureusement, je n'ai pas de chiffres de référence concrets pour comparer libuv et Boost.Asio. Cependant, j'ai observé des performances similaires en utilisant les bibliothèques dans des applications temps réel et quasi temps réel. Si vous souhaitez obtenir des chiffres précis, le test test d'évaluation de libuv peut servir de point de départ.

En outre, même si le profilage doit être effectué pour identifier les goulots d'étranglement réels, tenez compte des allocations de mémoire. Pour libuv, la stratégie d'allocation de mémoire est principalement limitée au rappel de l'allocateur. D'autre part, l'API de Boost.Asio ne permet pas un rappel d'allocateur, mais pousse la stratégie d'allocation à l'application. Toutefois, les gestionnaires/rappels dans Boost.Asio peuvent être copiés, alloués et désalloués. Boost.Asio permet aux applications de fournir allocation de mémoire personnalisée des fonctions permettant de mettre en œuvre une stratégie d’allocation de mémoire pour les gestionnaires.


Maturité

Boost.Asio

Le développement d'Asio remonte au moins à octobre 2004. Il a été accepté dans Boost 1.35 le 22 mars 2006 après avoir fait l'objet d'un examen par les pairs de 20 jours. Il a également servi d'implémentation de référence et d'API pour Proposition de bibliothèque de mise en réseau pour TR2 . Boost.Asio contient pas mal de documentation , bien que son utilité varie d’un utilisateur à l’autre.

Les API ont également une sensation assez cohérente. En outre, les opérations asynchrones sont explicites dans le nom de l'opération. Par exemple, accept est un blocage synchrone et async_accept Est asynchrone. L'API fournit des fonctions gratuites pour les tâches d'E/S courantes, par exemple, la lecture d'un flux jusqu'à la lecture d'un \r\n. Nous avons également veillé à masquer certains détails spécifiques au réseau, tels que la ip::address_v4::any() représentant l'adresse "toutes interfaces" de 0.0.0.0.

Enfin, Boost 1.47+ fournit suivi du gestionnaire , ce qui peut s'avérer utile lors du débogage, ainsi que la prise en charge de C++ 11.

libuv

Basé sur leurs graphes github, le développement de Node.js remonte au moins à FEB-2009 , et celui de libuv à MAR-2011 . Le vbook est un endroit idéal pour une introduction à libuv. La documentation de l'API est ici .

Globalement, l’API est relativement cohérente et facile à utiliser. Une anomalie qui pourrait être source de confusion est que uv_tcp_listen Crée une boucle d'observation. Cela diffère de celui des autres observateurs disposant généralement d'une paire de fonctions uv_*_start Et uv_*_stop Pour contrôler la durée de vie de la boucle d'observation. De plus, certaines opérations uv_fs_* Ont une quantité d'arguments décente (jusqu'à 7). Le comportement synchrone et asynchrone étant déterminé par la présence d'un rappel (le dernier argument), la visibilité du comportement synchrone peut être diminuée.

Enfin, un rapide coup d'œil sur libuv historique des mises à jour montre que les développeurs sont très actifs.

463
Tanner Sansbury

D'accord. J'ai une certaine expérience dans l'utilisation des deux bibliothèques et je peux clarifier certaines choses.

Premièrement, d’un point de vue conceptuel, ces bibliothèques ont une conception très différente. Ils ont des architectures différentes, car ils sont d'échelle différente. Boost.Asio est une grande bibliothèque réseau destinée à être utilisée avec les protocoles TCP/UDP/ICMP, POSIX, SSL, etc. Libuv est juste une couche d'abstraction multi-plateforme de IOCP pour Node.js, principalement. Donc, libuv est fonctionnellement un sous-ensemble de Boost.Asio (fonctionnalités communes uniquement les threads Sockets TCP/UDP, les timers). Dans ce cas, nous pouvons comparer ces bibliothèques en utilisant seulement quelques critères:

  1. L’intégration avec Node.js - Libuv est bien meilleure car elle est conçue pour cela (nous pouvons l’intégrer pleinement et l’utiliser dans tous les domaines, par exemple, le cloud, par exemple Windows Azure). Mais Asio implémente également les mêmes fonctionnalités que dans l’environnement piloté par la file d’événements Node.js.
  2. Performances IOCP - Je ne voyais pas de grandes différences, car ces deux bibliothèques font abstraction des API sous-jacentes du système d'exploitation. Mais ils le font de manière différente: Asio utilise beaucoup les fonctionnalités C++ telles que les modèles et parfois TMP. Libuv est une bibliothèque C native. Néanmoins, la réalisation de l'IOCP par Asio est très efficace. Les sockets UDP dans Asio ne sont pas assez bons, il vaut donc mieux utiliser libuv pour eux.

    Intégration avec les nouvelles fonctionnalités C++: Asio est meilleur (Asio 1.51 utilise abondamment le modèle asynchrone C++ 11, déplace la sémantique, modèles variadiques). En ce qui concerne la maturité, Asio est un projet plus stable et mature avec une bonne documentation (si on le compare à libuv description des en-têtes), beaucoup d’informations sur Internet (vidéoconférences, blogs: http://www.gamedev.net/blog/950/entry-2249317-a-guide-to-getting-started-with -boostasio? pg = 1 , etc.) et même des livres (pas pour les professionnels mais quand même: http://en.highscore.de/cpp/boost/index.html ). Libuv n’a qu’un seul livre en ligne (mais aussi bon) http://nikhilm.github.com/uvbook/index.html et plusieurs vidéoconférences, il sera donc difficile de connaître tous les secrets la bibliothèque en a beaucoup). Pour plus de détails sur les fonctions, voir mes commentaires ci-dessous.

En conclusion, je dois dire que tout dépend de vos objectifs, de votre projet et de ce que vous avez l'intention de faire concrètement.

45

Une énorme différence est l'auteur d'Asio (Christopher Kohlhoff) prépare sa bibliothèque pour l'inclure dans la bibliothèque standard C++, voir http://www.open-std.org/jtc1/sc22/wg21/docs/papers /2007/n2175.pdf et http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4370.html

16
Vinnie Falco