How does libuv compare to Boost/ASIO? [closed]
Asked Answered
K

4

274

I'd be interested in aspects like:

  • scope/features
  • performance
  • maturity
Kilocalorie answered 10/7, 2012 at 23:22 Comment(0)
N
560

Scope

Boost.Asio is a C++ library that started with a focus on networking, but its asynchronous I/O capabilities have been extended to other resources. Additionally, with Boost.Asio being part of the Boost libraries, its scope is slightly narrowed to prevent duplication with other Boost libraries. For example, Boost.Asio will not provide a thread abstraction, as Boost.Thread already provides one.

On the other hand, libuv is a C library designed to be the platform layer for Node.js. It provides an abstraction for IOCP on Windows, kqueue on macOS, and epoll on Linux. Additionally, it looks as though its scope has increased slightly to include abstractions and functionality, such as threads, threadpools, and inter-thread communication.

At their core, each library provides an event loop and asynchronous I/O capabilities. They have overlap for some of the basic features, such as timers, sockets, and asynchronous operations. libuv has a broader scope, and provides additional functionality, such as thread and synchronization abstractions, synchronous and asynchronous file system operations, process management, etc. In contrast, Boost.Asio's original networking focus surfaces, as it provides a richer set of network related capabilities, such as ICMP, SSL, synchronous blocking and non-blocking operations, and higher-level operations for common tasks, including reading from a stream until a newline is received.


Feature List

Here is the brief side-by-side comparison on some of the major features. Since developers using Boost.Asio often have other Boost libraries available, I have opted to consider additional Boost libraries if they are either directly provided or trivial to implement.

                         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. Scatter/Gather I/O.

2. Boost.Extension was never submitted for review to Boost. As noted here, the author considers it to be complete.

Event Loop

While both libuv and Boost.Asio provide event loops, there are some subtle differences between the two:

  • While libuv supports multiple event loops, it does not support running the same loop from multiple threads. For this reason, care needs to be taken when using the default loop (uv_default_loop()), rather than creating a new loop (uv_loop_new()), as another component may be running the default loop.
  • Boost.Asio does not have the notion of a default loop; all io_service are their own loops that allow for multiple threads to run. To support this Boost.Asio performs internal locking at the cost of some performance. Boost.Asio's revision history indicates that there have been several performance improvements to minimize the locking.

Threadpool

  • libuv's provides a threadpool through uv_queue_work. The threadpool size is configurable via the environment variable UV_THREADPOOL_SIZE. The work will be executed outside of the event loop and within the threadpool. Once the work is completed, the completion handler will be queued to run within the event loop.
  • While Boost.Asio does not provide a threadpool, the io_service can easily function as one as a result of io_service allowing multiple threads to invoke run. This places the responsibility of thread management and behavior to the user, as can be seen in this example.

Threading and Synchronization

  • libuv provides an abstraction to threads and synchronization types.
  • Boost.Thread provides a thread and synchronization types. Many of these types follow closely to the C++11 standard, but also provide some extensions. As a result of Boost.Asio allowing multiple threads to run a single event loop, it provides strands as a means to create a sequential invocation of event handlers without using explicit locking mechanisms.

File System Operations

  • libuv provides an abstraction to many file system operations. There is one function per operation, and each operation can either be synchronous blocking or asynchronous. If a callback is provided, then the operation will be executed asynchronously within an internal threadpool. If a callback is not provided, then the call will be synchronous blocking.
  • Boost.Filesystem provides synchronous blocking calls for many file system operations. These can be combined with Boost.Asio and a threadpool to create asynchronous file system operations.

Networking

  • libuv supports asynchronous operations on UDP and TCP sockets, as well as DNS resolution. Application developers should be aware that the underlying file descriptors are set to non-blocking. Therefore, native synchronous operations should check return values and errno for EAGAIN or EWOULDBLOCK.
  • Boost.Asio is a bit more rich in its networking support. In addition many of the features libuv's networking provides, Boost.Asio supporting SSL and ICMP sockets. Furthermore, Boost.Asio provides synchronous blocking and synchronous non-blocking operations, into addition to its asynchronous operations. There are numerous free standing functions that provide common higher-level operations, such as reading a set amount of bytes, or until a specified delimiter character is read.

Signal

  • libuv provides an abstraction kill and signal handling with its uv_signal_t type and uv_signal_* operations.
  • Boost.Asio does not provde an abstraction to kill, but its signal_set provides signal handling.

IPC


API Differences

While the APIs are different based on the language alone, here are a few key differences:

Operation and Handler Association

Within Boost.Asio, there is a one-to-one mapping between an operation and a handler. For instance, each async_write operation will invoke the WriteHandler once. This is true for many of libuv operations and handlers. However, libuv's uv_async_send supports a many-to-one mapping. Multiple uv_async_send calls may result in the uv_async_cb being called once.

Call Chains vs. Watcher Loops

When dealing with task, such as reading from a stream/UDP, handling signals, or waiting on timers, Boost.Asio's asynchronous call chains are a bit more explicit. With libuv, a watcher is created to designate interests in a particular event. A loop is then started for the watcher, where a callback is provided. Upon receiving the event of interests, the callback will be invoked. On the other hand, Boost.Asio requires an operation to be issued each time the application is interested in handling the event.

To help illustrate this difference, here is an asynchronous read loop with Boost.Asio, where the async_receive call will be issued multiple times:

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

And here is the same example with libuv, where handle_read is invoked each time the watcher observes that the socket has data:

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

Memory Allocation

As a result of the asynchronous call chains in Boost.Asio and the watchers in libuv, memory allocation often occurs at different times. With watchers, libuv defers allocation until after it receives an event that requires memory to handle. The allocation is done through a user callback, invoked internal to libuv, and defers deallocation responsibility of the application. On the other hand, many of the Boost.Asio operations require that the memory be allocated before issuing the asynchronous operation, such as the case of the buffer for async_read. Boost.Asio does provide null_buffers, that can be used to listen for an event, allowing applications to defer memory allocation until memory is needed, although this is deprecated.

This memory allocation difference also presents itself within the bind->listen->accept loop. With libuv, uv_listen creates an event loop that will invoke the user callback when a connection is ready to be accepted. This allows the application to defer the allocation of the client until a connection is being attempted. On the other hand, Boost.Asio's listen only changes the state of the acceptor. The async_accept listens for the connection event, and requires the peer to be allocated before being invoked.


Performance

Unfortunately, I do not have any concrete benchmark numbers to compare libuv and Boost.Asio. However, I have observed similar performance using the libraries in real-time and near-real-time applications. If hard numbers are desired, libuv's benchmark test may serve as a starting point.

Additionally, while profiling should be done to identify actual bottlenecks, be aware of memory allocations. For libuv, the memory allocation strategy is primarily limited to the allocator callback. On the other hand, Boost.Asio's API does not allow for an allocator callback, and instead pushes the allocation strategy to the application. However, the handlers/callbacks in Boost.Asio may be copied, allocated, and deallocated. Boost.Asio allows for applications to provide custom memory allocation functions in order to implement a memory allocation strategy for handlers.


Maturity

Boost.Asio

Asio's development dates back to at least OCT-2004, and it was accepted into Boost 1.35 on 22-MAR-2006 after undergoing a 20-day peer review. It also served as the reference implementation and API for Networking Library Proposal for TR2. Boost.Asio has a fair amount of documentation, although its usefulness varies from user to user.

The API also have a fairly consistent feel. Additionally, the asynchronous operations are explicit in the operation's name. For example, accept is synchronous blocking and async_accept is asynchronous. The API provides free functions for common I/O task, for instance, reading from a stream until a \r\n is read. Attention has also been given to hide some network specific details, such as the ip::address_v4::any() representing the "all interfaces" address of 0.0.0.0.

Finally, Boost 1.47+ provides handler tracking, which can prove to be useful when debugging, as well as C++11 support.

libuv

Based on their github graphs, Node.js's development dates back to at least FEB-2009, and libuv's development dates to MAR-2011. The uvbook is a great place for a libuv introduction. The API documentation is here.

Overall, the API is fairly consistent and easy to use. One anomaly that may be a source of confusion is that uv_tcp_listen creates a watcher loop. This is different than other watchers that generally have a uv_*_start and uv_*_stop pair of functions to control the life of the watcher loop. Also, some of the uv_fs_* operations have a decent amount of arguments (up to 7). With the synchronous and asynchronous behavior being determined on the presence of a callback (the last argument), the visibility of the synchronous behavior can be diminished.

Finally, a quick glance at the libuv commit history shows that the developers are very active.

Nacelle answered 4/11, 2012 at 16:38 Comment(10)
Thanks man! Great answer! I can't think of anything more comprehensive :)Inkstand
Very happy with the answer, I award you with the bounty :) Let the SO to decide the best answer for himself.Inkstand
Incredible answer. This covers both the high-level picture, as well as specific, important differences in detail (like i.e. threading/eventloop). Thank you very much!Kilocalorie
A comment: "Multiple uv_async_send calls may result in the uv_async_cb being called once" => should that read "being called more than once"?Kilocalorie
@oberstet: Nope. I've updated the answer to mention that most of libuv's operations are one-to-one. However, libuv can accumulate multiple uv_async_send calls, and handle them all with a single callback. It is documented here. Also, thanks everyone.Nacelle
Hi, does this mean Python 3.3 has Scatter/Gather I/O capabilities? I think writev is based on sendmsg?Souvaine
libuv does not support access to serial ports, and it seems they look at serial support as out of scope. github.com/joyent/libuv/issues/318Kilocalorie
Looks like libuv no longer only allows signals on the default loop. github.com/joyent/libuv/blob/master/include/uv.h#L2130Subchaser
The internal locking on the event loop on Boost.Asio looks scary from performance standpoint. How can it have similar performance to lock-free libuv? Maybe adding a warning statement on the performance section can be helpful.Kerriekerrigan
Based in personal experience, I found Boost ASIO to be a poor performer. That was a few years ago. I wonder if things have changed?Oldest
S
52

Ok. I have some experience in using both libraries and can clearify some things.

First, from a conceptual view-point these libraries are quite different in design. They have different architectures, because they are of different scale. Boost.Asio is a large networking library aimed to be used with TCP/UDP/ICMP protocols, POSIX, SSL and so on. Libuv is just a layer for cross-platform abstraction of IOCP for Node.js, predominantly. So libuv is functionally a subset of Boost.Asio (common features only TCP/UDP Sockets threads,timers). Being that the case, we can compare these libraries using only few criteria:

  1. Integration with Node.js - Libuv is considerably better because it is aimed for this (we can fully integrate it and use in all aspects, for instance, cloud e.g. windows azure). But Asio also implements almost the same functionality as in Node.js event queue driven environment.
  2. IOCP Performance - I couldn't see great differencies, because both these libraries abstract underlying OS API. But they do it in a different way: Asio heavily uses C++ features such as templates and sometimes TMP. Libuv is a native C-library. But nevertheless Asio realisation of IOCP is very efficient. UDP sockets in Asio are not good enough it is better to use libuv for them.

    Integration with new C++ features: Asio is better (Asio 1.51 extensively use C++11 asynchronous model, move semantics, variadic templates).In regard to maturity, Asio is a more stable and mature project with good documentation (if compare it to libuv headers description), a lot of information across the Internet (video talks, blogs: http://www.gamedev.net/blog/950/entry-2249317-a-guide-to-getting-started-with-boostasio?pg=1 ,etc.) and even books (not for professionals but nevertheless: http://en.highscore.de/cpp/boost/index.html ). Libuv has only one online book (but also good) http://nikhilm.github.com/uvbook/index.html and several video talks, so it will be difficult to know all the secrets (this library has a lot of them). For more specific discussion of functions see my comments below.

As conclusion, I should said that it all depends on your purposes, your project and what concretely you intend to do.

Shagbark answered 30/10, 2012 at 12:27 Comment(11)
What matters is your technical skill and experience. Kind greetings from a Cuban.Downing
I agree with all your points except documentation of Asio. The official documentation doesn't do any justice to this wonderful library. There are bunch of other docs and a boost con talk from the author that I found very useful. And I haven't come across a book for Asio. Can you link that in your answer? It will be very helpful.Maceio
@vikas Yes I agree documentation is poor and sometimes contradictory but comparing to libuv it is nice for getting started.As for books I edit my answer but I think you have seen it before (unfortunately there is no book entirely dedicated to Boost - only scattered information)Shagbark
What do you mean by "So libuv is functionally a subset of Boost.Asio (TCP/UDP/Sockets and threads)"? According to TOC nikhilm.github.com/uvbook/index.html libuv has broader application then boost::asio.Linstock
@Sergey It only seems so.When you begin to use these libraries intensively all became clear: For instance: 1) ICMP - libuv doesn't support this protocol 2) SSL - also doesn't 3) SCTP there are Boost.Asio.SCTP but not the same in libuv. 4)Reactor and proactor patterns - in ASIO it's ready fot use solutions Asio from the beginning was developed as general purpose networking library, libuv - only for abstracting IOCP and tight integration with Node.js.Shagbark
New Asio releases also extensively use new C++11 asynchronous and multithreaded features which extends its functionality. Of course libuv is still evolving but nevertheless libuv < Boost.AsioShagbark
Rgd "mainly IOCP": does libuv use "modern" OS interfaces like kqueue/epoll on non-Windows platforms? Rgd "libuv is functionally a subset of Boost.Asio": doesn't libuv provide asynch file I/O also? How would you judge it's (libuv) cababilities in this regard? Does ASIO provide DNS asynch resolver?Kilocalorie
@Kilocalorie Thanks for this question. First - answer is yes. In my answer I speak more windows specifically.On Windows libuv is IOCP abstract but on linux systems it's abstracts libev (and libev is based on epoll,kqueue,event ports so on). So at first lets not mixing concepts (Windows analogue of epoll/kqueue/ is IOCP (In Win32, async operations use the OVERLAPPED structure) and they are alos quite good and fast (I think low level comparison here not needed because we talk about the libraries)Shagbark
Second. Yes -in regards to async file IO libuv is better because ASIO has only limited implementation of asynchronous file I/O operations (only Windows wrapper for HANDLE) as far as I know. In libuv file IO is asynchronous by default (it is doing by uv_pipe_t). So here is a score for libuv. Third Yes ASIO also provide methods for writing DNS asynch resolvers (ip::tcp::resolver::async_resolve() or ip::udp::resolver::async_resolve()) and there a number of really good extensions to Boost asio ( boost::net dns resolver for instance).So as you see Boost.Asio is more wide library.Shagbark
@Kilocalorie And if you want to choose that which will fits your project you must have definite requiriments.ASio is more broad (which aimed for general network programming). Libuv (is for asynchronous network communication in tight integration with Node.js and better for non Windows platforms) Hope this helpsShagbark
@AlexanderKaraberov could you expand on the problems ASIO has with UDP?Weiler
D
24

One huge difference is the author of Asio (Christopher Kohlhoff) is grooming his library for inclusion in the C++ Standard Library, see http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2175.pdf and http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4370.html

Diphthong answered 25/3, 2016 at 14:36 Comment(0)
S
2

Adding the portability status: As of posting this answer and according to my own attempts:

  • Boost.ASIO has no official support for iOS and Android, e.g., its build system does not work for iOS out of the box.
  • libuv builds easily for iOS and Android, with official support for Android right in their docs. My own generic iOS build script for Autotools-based projects works without issues.
Succursal answered 20/10, 2019 at 4:14 Comment(1)
It is quite easy to build a cross platform framework for iOS and andriod using bazel using bazel boost build rules.Potage

© 2022 - 2024 — McMap. All rights reserved.