libuv与Boost/ASIO相比如何?

我感兴趣的方面有:

  • 范围/功能
  • 性能
  • 成熟
64599 次浏览

好的。我有一些使用这两个库的经验,可以澄清一些事情。

首先,从概念的角度来看,这些库在设计上有很大的不同。它们有不同的架构,因为它们的规模不同。提振。Asio是一个大型网络库,旨在与TCP/UDP/ICMP协议、POSIX、SSL等一起使用。Libuv只是一个用于Node.js的IOCP的跨平台抽象层。所以libuv在功能上是Boost的一个子集。Asio(常见功能只有TCP/UDP套接字线程,定时器)。在这种情况下,我们可以使用以下几个标准来比较这些库:

  1. 与Node.js的集成——Libuv要好得多,因为它是针对这一点的(我们可以完全集成它,并在所有方面使用,例如,云,例如windows azure)。但是Asio也实现了与Node.js事件队列驱动环境中几乎相同的功能。
  2. IOCP性能——我看不出有很大的区别,因为这两个库都抽象了底层的OS API。但他们采用了不同的方式:Asio大量使用c++特性,比如模板,有时还使用TMP。Libuv是一个原生的c语言库。但是,Asio实现IOCP是非常有效的。Asio中的UDP套接字不够好,最好使用libuv。与新的c++特性集成:Asio更好(Asio 1.51广泛使用c++ 11异步模型,移动语义,可变参数模板)。在成熟度方面,Asio是一个更加稳定和成熟的项目,具有良好的文档(如果将其与libuv头部描述进行比较),互联网上的大量信息(视频演讲,博客:http://www.gamedev.net/blog/950/entry-2249317-a-guide-to-getting-started-with-boostasio?pg=1等),甚至书籍(不适合专业人士,但仍然:http://en.highscore.de/cpp/boost/index.html)。Libuv只有一本在线书籍(但也很好)http://nikhilm.github.com/uvbook/index.html和几个视频演讲,所以很难知道所有的秘密(这个图书馆有很多)。有关函数的更详细讨论,请参阅下面的评论。

作为结论,我应该说这一切都取决于你的目的,你的项目和你具体想做什么。

范围

提振。Asio是一个c++库,一开始专注于网络,但它的异步I/O功能已经扩展到其他资源。此外,使用Boost。Asio是Boost库的一部分,它的范围被略微缩小,以防止与其他Boost库重复。例如,Boost。Asio不会提供线程抽象,而提振。线程已经提供了。

另一方面,libuv是一个C库,被设计为node . js的平台层。它提供了Windows上的IOCP、macOS上的kqueue和Linux上的epoll的抽象。此外,它的范围似乎略有增加,包括抽象和功能,如线程、线程池和线程间通信。

每个库的核心都提供了一个事件循环和异步I/O功能。它们在一些基本特性上有重叠,比如计时器、套接字和异步操作。Libuv具有更广泛的范围,并提供了额外的功能,例如线程和同步抽象、同步和异步文件系统操作、进程管理等。相比之下,Boost。Asio最初的网络重点浮出水面,因为它提供了一组更丰富的网络相关功能,如ICMP、SSL、同步阻塞和非阻塞操作,以及用于常见任务的高级操作,包括从流读取直到接收换行符。


特性列表

以下是对一些主要功能的简单并列比较。因为开发者使用Boost。Asio通常有其他可用的Boost库,我选择考虑额外的Boost库,如果它们要么直接提供,要么实现起来很简单。

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. 散点/集合I/O. "

2. Boost。扩展从未提交给Boost进行审查。如前所述here,作者认为它是完整的。

事件循环

而libuv和Boost。Asio提供事件循环,两者之间有一些细微的区别:

  • 虽然libuv支持多个事件循环,但它不支持从多个线程运行同一个循环。因此,在使用默认循环(uv_default_loop())时需要小心,而不是创建一个新循环(uv_loop_new()),因为另一个组件可能正在运行默认循环。
  • 提振。Asio没有默认循环的概念;所有io_service都是它们自己的循环,允许多个线程运行。为了支持这个Boost。Asio以一些性能为代价执行内部锁。提振。Asio的修订历史表明有几个性能改进来最小化锁定。

Threadpool

  • libuv's通过uv_queue_work提供线程池。线程池大小可以通过环境变量UV_THREADPOOL_SIZE来配置。该工作将在事件循环之外的线程池中执行。一旦工作完成,完成处理程序将排队在事件循环中运行。
  • 而提高。由于不提供线程池,io_service可以很容易地作为一个线程运行,因为io_service允许多个线程调用run。这将线程管理和行为的责任交给了用户,如示例中所示。

线程和同步

  • Libuv提供了线程和同步类型的抽象。
  • 提振。线程提供了线程和同步类型。这些类型中的许多都密切遵循c++ 11标准,但也提供了一些扩展。作为Boost的结果。Asio允许多个线程运行单个事件循环,它提供作为一种方法来创建事件处理程序的顺序调用,而不使用显式的锁定机制。

文件系统操作

  • Libuv为许多文件系统操作提供了抽象。每个操作有一个函数,每个操作可以是同步块,也可以是异步块。如果提供了回调,则操作将在内部线程池中异步执行。如果没有提供回调,那么调用将是同步阻塞的。
  • 提振。文件系统为许多文件系统操作提供同步阻塞调用。这些可以与Boost结合使用。Asio和线程池创建异步文件系统操作。

网络

  • libuv支持UDP和TCP套接字上的异步操作,以及DNS解析。应用程序开发人员应该意识到底层文件描述符被设置为非阻塞。因此,本机同步操作应该检查返回值和errno中的EAGAINEWOULDBLOCK
  • 提振。Asio在网络支持方面更丰富一些。此外,libuv的网络提供了许多功能,Boost。Asio支持SSL和ICMP socket。此外,提振。除异步操作外,Asio还提供同步块操作和同步非块操作。有许多独立的函数提供常见的高级操作,例如读取一组字节,或者直到读取指定的分隔符。

信号

  • libuv提供了一个抽象kill和它的uv_signal_t类型和uv_signal_*操作的信号处理。
  • 提振。Asio不提供kill的抽象,但它的signal_set提供了信号处理。

IPC


API的差异

虽然api因语言而异,但这里有几个关键的区别:

操作和处理程序关联

在提高。Asio,在操作和处理程序之间存在一对一的映射。例如,每个async_write操作将调用WriteHandler一次。许多libuv操作和处理程序都是如此。然而,libuv的uv_async_send支持多对一映射。多个uv_async_send调用可能导致uv_async_cb被调用一次。

调用链vs. Watcher循环

当处理任务时,如从流/UDP读取,处理信号,或等待计时器,Boost。Asio的异步调用链更加明确。使用libuv,创建一个观察者来指定特定事件中的利益。然后为观察者启动一个循环,其中提供了一个回调。在接收到兴趣事件时,将调用回调。另一方面,Boost。Asio要求每次应用程序有兴趣处理事件时都发出一个操作。

为了说明这种区别,这里有一个Boost的异步读循环。Asio,其中async_receive调用将被多次发出:

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

下面是libuv的相同示例,其中每当观察者观察到套接字有数据时,就会调用handle_read:

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

内存分配

由于Boost中的异步调用链。Asio和libuv中的观察者,内存分配通常发生在不同的时间。对于观察者,libuv将分配推迟到接收到需要内存处理的事件之后。分配是通过用户回调完成的,在libuv内部调用,并推迟应用程序的释放责任。另一方面,很多的Boost。Asio操作要求在发出异步操作之前分配内存,例如async_readbuffer。提振。Asio确实提供了null_buffers,它可以用来监听事件,允许应用程序推迟内存分配,直到需要内存,尽管这是不赞成的。

这种内存分配差异也出现在bind->listen->accept循环中。在libuv中,uv_listen创建了一个事件循环,在连接准备好接受时调用用户回调。这允许应用程序延迟客户端的分配,直到尝试连接。另一方面,Boost。Asio的listen只改变acceptor的状态。async_accept监听连接事件,并要求在被调用之前分配对等体。


性能

不幸的是,我没有任何具体的基准数据来比较libuv和Boost.Asio。但是,我在实时和近实时应用程序中使用这些库时,观察到类似的性能。如果需要硬数字,libuv的基准测试可以作为起点。

此外,在进行概要分析以确定实际瓶颈的同时,还要注意内存分配。对于libuv,内存分配策略主要限于allocator回调。另一方面,Boost。Asio的API不允许分配器回调,而是将分配策略推给应用程序。然而,Boost中的处理程序/回调。可以复制、分配和释放Asio。提振。Asio允许应用程序提供自定义内存分配函数,以便为处理器实现内存分配策略。


成熟

提振。Asio

Asio的开发至少可以追溯到2004年10月,经过20天的同行评审后,于2006年3月22日被纳入Boost 1.35。它也作为TR2的网络库建议的参考实现和API。提振。Asio有相当数量的文档,尽管它的有用性因用户而异。

API也有相当一致的感觉。此外,异步操作在操作名称中是显式的。例如,accept是同步阻塞,而async_accept是异步阻塞。API为常见的I/O任务提供了免费函数,例如,从流中读取直到读取\r\n。还注意隐藏一些网络特定的细节,例如ip::address_v4::any()表示0.0.0.0的“所有接口”地址。

最后,Boost 1.47+提供了处理程序跟踪,这在调试时被证明是有用的,以及c++ 11支持。

libuv

根据他们的github图表,Node.js的开发至少可以追溯到2009年2月,,而libuv的开发可以追溯到3月- 2011uvbook是介绍libuv的好地方。API文档是在这里

总的来说,这个API相当一致且易于使用。一个可能引起混淆的异常是uv_tcp_listen创建了一个watcher循环。这与其他监视器不同,后者通常有uv_*_startuv_*_stop对函数来控制监视器循环的生命周期。另外,一些uv_fs_*操作有相当数量的参数(最多7个)。由于同步和异步行为是在回调(最后一个参数)出现时确定的,同步行为的可见性可能会降低。

最后,快速浏览一下libuv 提交历史就会发现开发人员非常活跃。

一个巨大的不同是Asio的作者(Christopher Kohlhoff)正在整理他的库,以包括在c++标准库中,参见http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2175.pdfhttp://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4370.html

添加可移植性状态:在发布这个答案时,根据我自己的尝试:

  • 提振。ASIO没有iOS和Android的官方支持,例如,它的构建系统不能在iOS上开箱即用。
  • libuv易于为iOS和Android构建,在他们的文档中正式支持Android。我自己的通用iOS构建脚本用于基于autotools的项目,没有任何问题。