c++ - ¿Cómo se compara libuv con Boost/ASIO?
boost-asio (3)
Me interesaría aspectos como:
- alcance / características
- actuación
- madurez
Alcance
Boost.Asio es una biblioteca de C ++ que comenzó con un enfoque en redes, pero sus capacidades de E / S asíncronas se han ampliado a otros recursos. Además, como Boost.Asio es parte de las bibliotecas de Boost, su alcance se reduce ligeramente para evitar la duplicación con otras bibliotecas de Boost. Por ejemplo, Boost.Asio no proporcionará una abstracción de hilos, ya que Boost.Thread ya proporciona uno.
Por otro lado, libuv es una biblioteca de C diseñada para ser la capa de plataforma para node.js Proporciona una abstracción a IOCP para Windows y libev en sistemas Unix. Aunque hay esfuerzos para eliminar libev como se señala en this problema. Además, parece que su alcance ha aumentado ligeramente para incluir abstracciones y funcionalidades, como subprocesos, agrupaciones de hilos y comunicación entre subprocesos.
En su núcleo, cada biblioteca proporciona un bucle de eventos y capacidades de E / S asíncronas. Se han superpuesto para algunas de las funciones básicas, como temporizadores, sockets y operaciones asíncronas. libuv tiene un alcance más amplio y proporciona funcionalidad adicional, como abstracciones de subprocesos y sincronización, operaciones de sistema de archivos síncronas y asíncronas, administración de procesos, etc. capacidades, como ICMP, SSL, bloqueo sincrónico y operaciones sin bloqueo, y operaciones de alto nivel para tareas comunes, incluida la lectura desde un flujo hasta que se recibe una nueva línea.
Lista de características
Aquí está la breve comparación lado a lado en algunas de las características principales. Dado que los desarrolladores que usan Boost.Asio a menudo tienen otras bibliotecas de Boost disponibles, he optado por considerar bibliotecas adicionales de Boost si se proporcionan directamente o son triviales de implementar.
libuv Boost Event Loop: yes Asio Threadpool: yes Asio + Threads Threading: Threads: yes Threads Synchronization: yes Threads File System Operations: Synchronous: yes FileSystem Asynchronous: yes Asio + Filesystem Timers: yes Asio Scatter/Gather I/O[1]: no Asio Networking: ICMP: no Asio DNS Resolution: async-only Asio SSL: no Asio TCP: async-only Asio UDP: async-only Asio Signal: Handling: yes Asio Sending: yes no IPC: UNIX Domain Sockets: yes Asio Windows Named Pipe: yes Asio Process Management: Detaching: yes Process I/O Pipe: yes Process Spawning: yes Process System Queries: CPU: yes no Network Interface: yes no Serial Ports: no yes TTY: yes no Shared Library Loading: yes Extension[2]
1. Dispersión / Recopilación de E / S.
2. Boost.Extension . La Boost.Extension nunca se envió para su revisión a Boost. Como se señala here , el autor considera que está completo.
Evento de bucle
Si bien tanto libuv como Boost.Asio proporcionan bucles de eventos, existen algunas diferencias sutiles entre los dos:
- Si bien libuv admite varios bucles de eventos, no admite la ejecución del mismo bucle desde varios subprocesos. Por este motivo, se debe tener cuidado al usar el bucle predeterminado (
uv_default_loop()
), en lugar de crear un nuevo bucle (uv_loop_new()
), ya que otro componente puede estar ejecutando el bucle predeterminado. - Boost.Asio no tiene la noción de un bucle predeterminado; todos los
io_service
son sus propios bucles que permiten la ejecución de múltiples hilos. Para admitir esto, Boost.Asio realiza el bloqueo interno a costa de cierto performance . El history revisiones de Boost.Asio indica que ha habido varias mejoras de rendimiento para minimizar el bloqueo.
Piscina de hilo
- libuv''s proporciona un conjunto de subprocesos a través de
uv_queue_work
. El tamaño de la agrupación de hilos es un detalle de la implementación y no parece ser configurable a través de la API. El trabajo se ejecutará fuera del bucle de eventos y dentro de la agrupación de subprocesos. Una vez que se complete el trabajo, el controlador de finalización se pondrá en cola para ejecutarse dentro del bucle de eventos. - Si bien Boost.Asio no proporciona un conjunto de subprocesos, el
io_service
puede funcionar fácilmente como uno de los resultados deio_service
permitiendo que seio_service
varios subprocesos. Esto coloca la responsabilidad de la administración y el comportamiento de los hilos en el usuario, como se puede ver en this ejemplo.
Roscado y Sincronización
- libuv proporciona una abstracción a hilos y tipos de sincronización.
- Boost.Thread proporciona un hilo y tipos de sincronización. Muchos de estos tipos siguen de cerca el estándar C ++ 11, pero también proporcionan algunas extensiones. Como resultado de que Boost.Asio permite que varios subprocesos ejecuten un solo bucle de eventos, proporciona
strands
como un medio para crear una invocación secuencial de controladores de eventos sin utilizar mecanismos de bloqueo explícitos.
Operaciones del sistema de archivos
- libuv proporciona una abstracción a muchas operaciones del sistema de archivos. Hay una función por operación, y cada operación puede ser de bloqueo síncrono o asíncrono. Si se proporciona una devolución de llamada, la operación se ejecutará de forma asíncrona dentro de un conjunto de subprocesos interno. Si no se proporciona una devolución de llamada, entonces la llamada será un bloqueo síncrono.
- Boost.Filesystem proporciona llamadas de bloqueo síncrono para muchas operaciones del sistema de archivos. Estos se pueden combinar con Boost.Asio y un grupo de subprocesos para crear operaciones de sistema de archivos asíncronas.
Redes
- libuv admite operaciones asíncronas en sockets UDP y TCP, así como la resolución de DNS. Los desarrolladores de aplicaciones deben tener en cuenta que los descriptores de archivo subyacentes están configurados como no bloqueantes. Por lo tanto, las operaciones síncronas nativas deben verificar los valores de retorno y errno para
EAGAIN
oEWOULDBLOCK
. - Boost.Asio es un poco más rico en su soporte de red. Además, muchas de las características que proporciona la red de libuv, Boost.Asio es compatible con SSL y sockets ICMP. Además, Boost.Asio proporciona operaciones de bloqueo síncrono y no síncrono, además de sus operaciones asíncronas. Existen numerosas funciones independientes que proporcionan operaciones comunes de alto nivel, como leer una cantidad determinada de bytes o hasta que se lea un carácter delimitador específico.
Señal
- libuv proporciona una
kill
abstracción y manejo de señales con su tipouv_signal_*
yuv_signal_*
. Por el momento , solo el bucle de eventos predeterminado admite señales. - Boost.Asio no proporciona una abstracción para
kill
, pero su serviciosignal_set_service
proporciona el manejo de la señal.
IPC
- libuv abstrae Unix Domain Sockets y Windows Named Pipes a través de un solo tipo
uv_pipe_t
. - Boost.Asio separa los dos en local::stream_protocol::socket / local::datagram_protocol::socket y
windows::stream_handle
.
Diferencias API
Si bien las API son diferentes según el lenguaje solo, aquí hay algunas diferencias clave:
Asociación de Manejo y Manejo
Dentro de Boost.Asio, hay una asignación uno a uno entre una operación y un controlador. Por ejemplo, cada operación async_write
invocará el WriteHandler una vez. Esto es cierto para muchas de las operaciones y manejadores de libuv. Sin embargo, uv_async_send de uv_async_send
admite una uv_async_send
muchos a uno. Las llamadas múltiples uv_async_send
pueden dar como resultado que se llame a uv_async_cb una vez.
Cadenas de llamadas contra loops de vigilantes
Cuando se trata de una tarea, como leer un flujo / UDP, manejar señales o esperar en los temporizadores, las cadenas de llamadas asíncronas de Boost.Asio son un poco más explícitas. Con libuv, se crea un observador para designar intereses en un evento en particular. Luego se inicia un bucle para el observador, donde se proporciona una devolución de llamada. Al recibir el evento de intereses, se invocará la devolución de llamada. Por otro lado, Boost.Asio requiere que se realice una operación cada vez que la aplicación esté interesada en manejar el evento.
Para ayudar a ilustrar esta diferencia, aquí hay un bucle de lectura asíncrono con Boost.Asio, donde se async_receive
llamada async_receive
varias veces:
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 ); --''
}
Y aquí está el mismo ejemplo con libuv, donde se invoca handle_read
cada vez que el observador observa que el socket tiene datos:
uv_read_start( socket, alloc_buffer, handle_read ); --.
|
.-------------------------------------------------''
|
V
void handle_read( ... )
{
fprintf( stdout, "got data/n" );
}
Asignación de memoria
Como resultado de las cadenas de llamadas asíncronas en Boost.Asio y los observadores en libuv, la asignación de memoria a menudo ocurre en diferentes momentos. Con los observadores, libuv difiere la asignación hasta después de que reciba un evento que requiere memoria para manejar. La asignación se realiza a través de una devolución de llamada del usuario, invocada interna a libuv, y difiere la responsabilidad de desasignación de la aplicación. Por otro lado, muchas de las operaciones de Boost.Asio requieren que la memoria se asigne antes de emitir la operación asíncrona, como en el caso del buffer
para async_read
. Boost.Asio proporciona null_buffers
, que pueden usarse para escuchar un evento, permitiendo que las aplicaciones aplacen la asignación de memoria hasta que se necesite memoria.
Esta diferencia de asignación de memoria también se presenta dentro del bucle bind->listen->accept
. Con libuv, uv_listen
crea un bucle de eventos que invocará la devolución de llamada del usuario cuando una conexión esté lista para ser aceptada. Esto permite que la aplicación aplace la asignación del cliente hasta que se intente una conexión. Por otro lado, la listen
de Boost.Asio solo cambia el estado del acceptor
. El async_accept
escucha el evento de conexión, y requiere que el par esté asignado antes de ser invocado.
Actuación
Desafortunadamente, no tengo ningún número de referencia concreto para comparar libuv y Boost.Asio. Sin embargo, he observado un rendimiento similar al usar las bibliotecas en aplicaciones en tiempo real y casi en tiempo real. Si se desean números concretos, la prueba de referencia de libuv puede servir como punto de partida.
Además, aunque se debe hacer un perfil para identificar cuellos de botella reales, tenga en cuenta las asignaciones de memoria. Para libuv, la estrategia de asignación de memoria se limita principalmente a la devolución de llamada del asignador. Por otro lado, la API de Boost.Asio no permite una devolución de llamada del asignador, y en su lugar empuja la estrategia de asignación a la aplicación. Sin embargo, los manejadores / devoluciones de llamada en Boost.Asio pueden ser copiados, asignados y desasignados. Boost.Asio permite que las aplicaciones proporcionen funciones de asignación de memoria personalizadas para implementar una estrategia de asignación de memoria para los manejadores.
Madurez
Boost.Asio
El desarrollo de Asio se remonta al menos a OCT-2004, y se aceptó en Boost 1.35 el 22-MAR-2006 después de someterse a una revisión por pares de 20 días. También sirvió como la implementación de referencia y API para la propuesta de biblioteca de red para TR2 . Boost.Asio tiene una buena cantidad de documentation , aunque su utilidad varía de un usuario a otro.
La API también tiene una sensación bastante consistente. Además, las operaciones asíncronas son explícitas en el nombre de la operación. Por ejemplo, accept
es un bloqueo síncrono y async_accept
es asíncrono. La API proporciona funciones gratuitas para la tarea de E / S común, por ejemplo, leer desde un flujo hasta que se lee un /r/n
. También se ha prestado atención para ocultar algunos detalles específicos de la red, como ip::address_v4::any()
representa la dirección de "todas las interfaces" de 0.0.0.0
.
Finalmente, Boost 1.47+ proporciona un seguimiento del controlador , que puede resultar útil para la depuración, así como el soporte de C ++ 11.
libuv
Basado en sus gráficos de github, el desarrollo de Node.js se remonta al menos a FEB-2009 , y el desarrollo de libuv se remonta a MAR-2011 . El uvbook es un gran lugar para una introducción libuv. La API está documented en forma de un encabezado detallado, pero aún podría utilizar contribuciones en algunas áreas.
En general, la API es bastante consistente y fácil de usar. Una anomalía que puede ser una fuente de confusión es que uv_tcp_listen
crea un bucle de observador. Esto es diferente a otros observadores que generalmente tienen un par de funciones uv_*_start
y uv_*_stop
para controlar la vida útil del bucle de observador. Además, algunas de las operaciones uv_fs_*
tienen una cantidad decente de argumentos (hasta 7). Si el comportamiento síncrono y asíncrono se determina en presencia de una devolución de llamada (el último argumento), la visibilidad del comportamiento síncrono puede disminuir.
Finalmente, un vistazo rápido al historial de compromiso de libuv muestra que los desarrolladores son muy activos.
De acuerdo. Tengo algo de experiencia en el uso de ambas bibliotecas y puedo aclarar algunas cosas.
Primero, desde un punto de vista conceptual, estas bibliotecas son muy diferentes en diseño. Tienen diferentes arquitecturas, porque son de diferente escala. Boost.Asio es una gran biblioteca de red destinada a ser utilizada con los protocolos TCP / UDP / ICMP, POSIX, SSL, etc. Libuv es solo una capa para la abstracción multiplataforma de IOCP para Node.js, predominantemente. Por lo tanto, libuv es funcionalmente un subconjunto de Boost.Asio (las características comunes solo son hilos de TCP / UDP Sockets, temporizadores). Siendo ese el caso, podemos comparar estas bibliotecas usando solo algunos criterios:
- La integración con Node.js - Libuv es considerablemente mejor porque está destinada a esto (podemos integrarla completamente y usarla en todos los aspectos, por ejemplo, en la nube, por ejemplo, en el azul de las ventanas). Pero Asio también implementa casi la misma funcionalidad que en el entorno controlado por colas de eventos Node.js.
- Rendimiento de IOCP: no pude ver grandes diferencias, porque ambas bibliotecas resumen la API subyacente del sistema operativo. Pero lo hacen de una manera diferente: Asio utiliza en gran medida las características de C ++, como las plantillas y, a veces, TMP. Libuv es una biblioteca C nativa. Pero sin embargo la realización de IOCP por parte de Asio es muy eficiente. Los sockets UDP en Asio no son lo suficientemente buenos, es mejor usar libuv para ellos.
Integración con las nuevas características de C ++: Asio es mejor (Asio 1.51 usa ampliamente el modelo asíncrono de C ++ 11, mueve semánticas, plantillas variadic). En cuanto a la madurez, Asio es un proyecto más estable y maduro con buena documentación (si se compara con libuv descripción de los encabezados), una gran cantidad de información en Internet (conversaciones de video, blogs: http://www.gamedev.net/blog/950/entry-2249317-a-guide-to-getting-started-with-boostasio?pg=1 , etc.) E incluso libros (no para profesionales, pero sin embargo: http://en.highscore.de/cpp/boost/index.html ). Libuv tiene solo un libro en línea (pero también bueno) http://nikhilm.github.com/uvbook/index.html y varias charlas en video, por lo que será difícil saber todos los secretos (esta biblioteca tiene muchos de ellos) . Para una discusión más específica de las funciones, vea mis comentarios a continuación.
Como conclusión, debo decir que todo depende de sus propósitos, su proyecto y lo que concretamente pretende hacer.
Una gran diferencia es que el autor de Asio (Christopher Kohlhoff) está preparando su biblioteca para incluirla en la Biblioteca Estándar de C ++, consulte http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2175 .pdf y http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4370.html