Как сравнить libuv с Boost/ASIO?

Мне были бы интересны такие аспекты, как:

  • сфера/функции
  • производительности
  • зрелость

Ответ 1

Область

Boost.Asio - это библиотека C++, которая изначально была ориентирована на работу в сети, но ее возможности асинхронного ввода-вывода были расширены для других ресурсов. Кроме того, поскольку Boost.Asio является частью библиотек Boost, его область действия немного сужена, чтобы предотвратить дублирование с другими библиотеками Boost. Например, Boost.Asio не предоставляет абстракцию потока, так как Boost.Thread уже предоставляет ее.

С другой стороны, libuv - это библиотека C, предназначенная для уровня платформы для Node.js. Он предоставляет абстракцию для IOCP в Windows, kqueue в macOS и epoll в Linux. Кроме того, похоже, что его область немного увеличилась, чтобы включить абстракции и функциональные возможности, такие как потоки, пулы потоков и связь между потоками.

По своей сути каждая библиотека предоставляет цикл обработки событий и возможности асинхронного ввода-вывода. Они перекрываются для некоторых основных функций, таких как таймеры, сокеты и асинхронные операции. 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. Scatter/Gather I/O.

2. Boost.Extension никогда не отправлялось на проверку в Boost. Как отмечается здесь, автор считает, что оно завершено.

Цикл событий

Хотя и libuv, и Boost.Asio предоставляют циклы событий, между ними есть некоторые тонкие различия:

  • Хотя libuv поддерживает несколько циклов событий, она не поддерживает запуск одного и того же цикла из нескольких потоков. По этой причине необходимо соблюдать осторожность при использовании цикла по умолчанию (uv_default_loop()), а не при создании нового цикла (uv_loop_new()), поскольку другой компонент может выполнять цикл по умолчанию.
  • Boost.Asio не имеет понятия цикла по умолчанию; все io_service являются собственными циклами, которые позволяют запускать несколько потоков. Для поддержки этого Boost.Asio выполняет внутреннюю блокировку за счет некоторой производительности. Редакция Boost.Asio history указывает, что было сделано несколько улучшений производительности для минимизации блокировки.

Threadpool

  • libuv предоставляет пул потоков через uv_queue_work. Размер пула потоков настраивается с помощью переменной среды UV_THREADPOOL_SIZE. Работа будет выполняться вне цикла событий и в пуле потоков. После завершения работы обработчик завершения будет поставлен в очередь для запуска в цикле событий.
  • Хотя Boost.Asio не предоставляет пул потоков, io_service может легко функционировать как единое целое в результате io_service, позволяя нескольким потокам вызывать run. Это возлагает ответственность за управление потоками и поведение на пользователя, как можно видеть в этом примере.

Потоки и синхронизация

  • libuv предоставляет абстракцию для потоков и типов синхронизации.
  • Boost.Thread предоставляет поток и типы синхронизации. Многие из этих типов соответствуют стандарту C++ 11, но также предоставляют некоторые расширения. Благодаря тому, что Boost.Asio позволяет нескольким потокам запускать один цикл событий, он предоставляет цепочки в качестве средства для последовательного вызова обработчиков событий без использования явных механизмов блокировки.

Операции с файловой системой

  • libuv предоставляет абстракцию для многих операций файловой системы. Для каждой операции предусмотрена одна функция, и каждая операция может быть либо синхронной, либо асинхронной. Если предусмотрен обратный вызов, то операция будет выполняться асинхронно во внутреннем пуле потоков. Если обратный вызов не предусмотрен, то вызов будет синхронной блокировкой.
  • Boost.Filesystem обеспечивает синхронные вызовы блокировки для многих операций файловой системы. Их можно комбинировать с Boost.Asio и пулом потоков для создания операций асинхронной файловой системы.

Сеть

  • libuv поддерживает асинхронные операции над сокетами UDP и TCP, а также разрешение DNS. Разработчики приложений должны знать, что базовые файловые дескрипторы настроены на неблокирование. Поэтому собственные синхронные операции должны проверять возвращаемые значения и ошибки для EAGAIN или EWOULDBLOCK.
  • Boost.Asio немного более богата своей сетевой поддержкой. В дополнение ко многим функциям, которые предоставляет libuv network, Boost.Asio поддерживает сокеты SSL и ICMP. Кроме того, Boost.Asio обеспечивает синхронную блокировку и синхронные неблокирующие операции в дополнение к своим асинхронным операциям. Существует множество автономных функций, которые обеспечивают общие операции более высокого уровня, такие как чтение заданного количества байтов или до считывания указанного символа разделителя.

  • сигналаlibuv обеспечивает абстракцию kill и обработку сигналов с помощью своего типа uv_signal_t и операций uv_signal_*.
  • Boost.Asio не предоставляет абстракцию для kill, но его signal_set обеспечивает обработку сигналов.

IPC


Различия API

Хотя API-интерфейсы различаются в зависимости от языка, вот несколько ключевых отличий:

Ассоциация Операторов и Обработчиков

В Boost.Asio существует взаимно-однозначное сопоставление между операцией и обработчиком. Например, каждая операция async_write будет вызывать WriteHandler один раз. Это верно для многих операций и обработчиков libuv. Однако libuv uv_async_send поддерживает отображение "многие к одному". Несколько вызовов uv_async_send могут привести к тому, что uv_async_cb будет вызван один раз.

Цепочки вызовов против петель-наблюдателей

При работе с такими задачами, как чтение из потока /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 требуется выделение памяти перед выполнением асинхронной операции, как, например, в случае buffer для async_read. Boost.Asio предоставляет null_buffers, который можно использовать для прослушивания события, что позволяет приложениям откладывать выделение памяти до тех пор, пока не потребуется память, хотя это не рекомендуется.

Эта разница в распределении памяти также проявляется в цикле bind->listen->accept. С помощью libuv uv_listen создает цикл обработки событий, который вызывает обратный вызов пользователя, когда соединение готово к приему. Это позволяет приложению откладывать выделение клиента до попытки подключения. С другой стороны, Boost.Asio listen только изменяет состояние acceptor. async_accept прослушивает событие подключения и требует, чтобы узел был выделен перед вызовом.


Производительность

К сожалению, у меня нет конкретных показателей для сравнения libuv и Boost.Asio. Тем не менее, я наблюдал похожую производительность при использовании библиотек в приложениях реального времени и почти реального времени. Если вам нужны жесткие числа, отправной точкой может служить libuv тест.

Кроме того, в то время как профилирование должно быть сделано, чтобы идентифицировать фактические узкие места, помните о распределении памяти. Для libuv стратегия выделения памяти в основном ограничена обратным вызовом распределителя. С другой стороны, API Boost.Asio не допускает обратный вызов распределителя и вместо этого передает стратегию выделения приложению. Однако обработчики/обратные вызовы в Boost.Asio могут быть скопированы, выделены и освобождены. Boost.Asio позволяет приложениям предоставлять пользовательские функции выделения памяти для реализации стратегии выделения памяти для обработчиков.


зрелость

Boost.Asio

Разработка Asio началась как минимум в октябре 2004 года, и она была принята в Boost 1.35 22 марта 2006 года после 20-дневной экспертной оценки. Он также служил эталонной реализацией и API для Предложения по сетевой библиотеке для TR2. Boost.Asio содержит достаточное количество документации, хотя его полезность варьируется от пользователя к пользователю.

У API также есть довольно последовательное чувство. Кроме того, асинхронные операции явно указаны в имени операции. Например, accept является синхронной блокировкой, а async_accept является асинхронной. API предоставляет бесплатные функции для обычной задачи ввода-вывода, например, чтение из потока до считывания \r\n. Также было уделено внимание скрытию некоторых специфических для сети деталей, таких как ip::address_v4::any(), представляющих адрес "всех интерфейсов" 0.0.0.0.

Наконец, Boost 1. 47+ предоставляет отслеживание обработчика, которое может оказаться полезным при отладке, а также поддержку C++ 11.

libuv

На основе их графов github разработка Node.js начинается как минимум с FEB-2009, а разработка libuv - с MAR-2011. uvbook - отличное место для знакомства с libuv. Документация по API здесь.

В целом, API довольно последовательный и простой в использовании. Одна аномалия, которая может привести к путанице, заключается в том, что uv_tcp_listen создает цикл наблюдателя. Это отличается от других наблюдателей, у которых обычно есть пара функций uv_*_start и uv_*_stop для управления жизненным циклом наблюдателя. Кроме того, некоторые операции uv_fs_* имеют приличное количество аргументов (до 7). С синхронным и асинхронным поведением, определяемым при наличии обратного вызова (последний аргумент), видимость синхронного поведения может быть уменьшена.

Наконец, быстрый взгляд на историю коммитов libav показывает, что разработчики очень активны.

Ответ 2

Ok. У меня есть некоторый опыт использования обеих библиотек и вы можете очистить некоторые вещи.

Во-первых, с концептуальной точки зрения эти библиотеки совершенно разные по дизайну. Они имеют разные архитектуры, потому что они имеют разный масштаб. Boost.Asio - это большая сетевая библиотека, предназначенная для использования с протоколами TCP/UDP/ICMP, POSIX, SSL и т.д. Libuv - это всего лишь слой для межплатформенной абстракции IOCP для Node.js, преимущественно. Таким образом, libuv функционально является подмножеством Boost.Asio(общие функции только потоки TCP/UDP Sockets, таймеры). В этом случае мы можем сравнить эти библиотеки, используя только несколько критериев:

  • Интеграция с Node.js - Libuv значительно лучше, потому что она нацелена на это (мы можем полностью интегрировать ее и использовать во всех аспектах, например облаке, например, в окнах azure). Но Asio также реализует почти те же функциональные возможности, что и в среде Node.js, ориентированной на очередь событий.
  • Производительность IOCP - я не видел больших различий, потому что обе эти библиотеки описывают базовый OS API. Но они делают это по-другому: Asio сильно использует функции С++, такие как шаблоны, а иногда и TMP. Libuv - это родная C-библиотека. Но, тем не менее, реализация IOCP в Asio очень эффективна. UDP-сокеты в Asio недостаточно хороши, поэтому лучше использовать libuv для них.

    Интеграция с новыми возможностями С++: Asio лучше (Asio 1.51 широко использует асинхронную модель С++ 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 и несколько видео-разговоров, поэтому будет сложно узнать все секреты (в этой библиотеке много их). Более подробное обсуждение функций см. В моих комментариях ниже.

Как вывод, я должен сказать, что все зависит от ваших целей, вашего проекта и того, что конкретно вы намереваетесь делать.

Ответ 4

Добавление статуса кроссплатформенности: На момент публикации этого ответа и в соответствии с моими собственными попытками:

  • Boost.ASIO не имеет официальной поддержки iOS и Android, например, система сборки не работает для iOS из коробки.
  • libuv легко собирается для iOS и Android, с официальной поддержкой Android прямо в их документах. Мой собственный универсальный скрипт сборки iOS для проектов на основе Autotools работает без проблем.