1. ホーム
  2. c++

[解決済み】libuvはBoost/ASIOと比べてどうなのか?

2022-05-05 02:05:27

質問

などに興味があります。

  • スコープ/フィーチャー
  • 性能
  • 成熟度

解決方法は?

範囲

ブースト.アシオ は、ネットワークに着目してスタートしたC++ライブラリですが、その非同期I/O機能は他のリソースにも拡張されています。 また、Boost.AsioはBoostライブラリの一部であるため、他のBoostライブラリとの重複を防ぐために、その範囲は若干狭くなっています。 例えば、Boost.Asioはスレッドの抽象化を提供しません。 ブースト.スレッド はすでに提供されています。

一方 libuv のプラットフォーム層として設計された C ライブラリです。 Node.js . を抽象化したものです。 IOCP をWindows上で実行します。 クエイクエ はmacOSで、そして エポール を追加しました。 さらに、スレッド、スレッドプール、スレッド間通信といった抽象化された機能を含むように、その範囲が若干拡大したように見えます。

各ライブラリの中核は、イベントループと非同期I/O機能を提供することです。 libuv はより広い範囲をカバーし、スレッドや同期の抽象化、同期・非同期ファイルシステム操作、プロセス管理などの追加機能を提供します。 一方、Boost.Asioは、ICMP、SSL、同期ブロッキングおよびノンブロッキング操作、ストリームから改行を受信するまで読み込むなどの一般的なタスクに対する高レベル操作など、ネットワーク関連の機能をより豊富に提供しており、ネットワークに焦点を当てた本来の姿が浮かび上がっています。


機能一覧

ここでは、主要な機能を簡単に並べて比較しています。 Boost.Asioを使っている開発者は、他のBoostライブラリを利用していることが多いので、直接提供されているか、実装が簡単であれば、追加のBoostライブラリも考慮することにしました。

                         libuv ブースト
イベントループ:あり Asio
スレッドプール: はい Asio + Threads
スレッド化              
  スレッド: はい スレッド
  同期: はい スレッド
ファイルシステム操作。
  同期: はい ファイルシステム
  非同期: はい Asio + Filesystem
タイマー:あり Asio
スキャッター/ギャザーI/O

[1]

アシオなし
ネットワーキング
  ICMP: なし Asio
  DNS解決:非同期のみAsio
  SSL:なし アシオ
  TCP:非同期型アシオ
  UDP:非同期のみAsio
シグナル
  ハンドリング: はい Asio
  送信: はい いいえ
IPC
  UNIX ドメインソケット: はい Asio
  Windows 名前付きパイプ:あり Asio
プロセス管理。
  デタッチ: はい プロセス
  I/Oパイプ:あり プロセス
  スポーン: はい プロセス
システムクエリ。
  CPU: はい いいえ
  ネットワークインターフェース: はい いいえ
シリアルポート: いいえ はい
TTY: はい いいえ
共有ライブラリのロード: はい 拡張

[2]

<サブ 1. スキャッター/ギャザーI/O .

<サブ 2. ブースト.エクステンション はBoostに審査に出されることはなかった。 前述のとおり こちら というもので、作者は完成していると考えています。

イベントループ

libuv と Boost.Asio の両方がイベントループを提供しますが、両者には微妙な違いがあります。

  • libuv は複数のイベントループをサポートしますが、複数のスレッドから同じループを実行することはサポートしません。 このため、デフォルトのループ ( uv_default_loop() )、新しいループを作るのではなく、( uv_loop_new() というのも、他のコンポーネントがデフォルトのループを実行している可能性があるからです。
  • Boost.Asioにはデフォルトループという概念はありません。 io_service は、複数のスレッドを実行することができる独自のループです。 これをサポートするためにBoost.Asioは以下を実行します。 内部ロック の代償として パフォーマンス . Boost.Asioの修正 歴史 は、ロックを最小化するためにいくつかのパフォーマンス改善が行われたことを示しています。

スレッドプール

  • libuv では、スレッドプールを uv_queue_work . スレッドプールのサイズは、環境変数 UV_THREADPOOL_SIZE . 作業はイベントループの外側で、スレッドプール内で実行されます。 作業が完了すると、完了ハンドラがイベントループ内で実行されるようにキューに入れられます。
  • Boost.Asioはスレッドプールを提供しませんが io_service の結果、簡単に1つの機能として機能することができます。 io_service を複数のスレッドで呼び出すことができます。 run . これは、スレッドの管理と動作の責任をユーザーに負わせるもので、次のようになります。 これ の例です。

スレッドと同期化

  • libuv はスレッドと同期型の抽象化を提供します。
  • Boost.Thread(ブースト・スレッド は、スレッドと同期の型を提供します。 これらの型の多くは C++11 標準に忠実に準拠しているが、いくつかの拡張も提供する。 Boost.Asio では、複数のスレッドが 1 つのイベントループを実行できるようにした結果、以下のようなものが提供されます。 ストランド は、明示的なロック機構を使用せずにイベントハンドラの連続呼び出しを作成するための手段として使用されます。

ファイルシステム操作

  • libuv は多くのファイルシステム操作の抽象化を提供します。 操作ごとに1つの関数があり、各操作は同期ブロックか非同期のいずれかになります。 コールバックが提供された場合、その操作は内部のスレッドプール内で非同期に実行されます。 コールバックが提供されない場合、呼び出しは同期ブロッキングになります。
  • ブースト.ファイルシステム は、多くのファイルシステム操作のための同期ブロッキング呼び出しを提供します。 これらを Boost.Asio やスレッドプールと組み合わせることで、非同期なファイルシステム操作を実現することができます。

ネットワーキング

  • libuv は UDP と TCP ソケットの非同期操作と DNS 解決をサポートしています。 アプリケーション開発者は、基礎となるファイルディスクリプタがノンブロッキングに設定されていることに注意する必要があります。 したがって、ネイティブの同期操作では、戻り値をチェックし エルノ に対して EAGAIN または EWOULDBLOCK .
  • Boost.Asioは、ネットワークサポートが少し豊富です。 libuv のネットワーキングが提供する多くの機能に加えて、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 の場合、メモリ割り当て戦略は主にアロケータコールバックに限定されます。 一方、Boost.Asio の API では、アロケータコールバックは使用できず、代わりに割り当て戦略がアプリケーションにプッシュされます。 ただし、Boost.Asioのハンドラ/コールバックは、コピー、アロケーション、デアロケーションが可能である。 Boost.Asioでは、アプリケーションから カスタムメモリアロケーション 関数を使用することで、ハンドラのメモリ割り当て戦略を実装することができます。


成熟度

ブースト.アシオ

Asioの開発は少なくとも2004年10月までさかのぼり、20日間のピアレビューを経て2006年3月22日にBoost 1.35にアクセプトされました。 のリファレンス実装とAPIとして使用されました。 TR2へのネットワークライブラリ提案 . Boost.Asioは、かなりの量の ドキュメント その有用性はユーザーによって異なりますが。

また、APIもかなり統一感があります。 さらに、非同期操作は操作名で明示される。 例えば accept は同期ブロッキングであり async_accept は非同期である。 APIは、一般的なI/Oタスクのためのフリー関数を提供する。 \r\n が読み込まれます。 また、ネットワーク固有の詳細を隠蔽するために、例えば ip::address_v4::any() のすべてのインターフェイスのアドレスを表します。 0.0.0.0 .

最後に、Boost 1.47+ では ハンドラートラッキング C++11のサポートと同様に、デバッグ時に有用であることが証明されています。

libuv

Node.jsの開発は、githubのグラフから、少なくとも以下の時期までさかのぼります。 2009年2月 libuv の開発年代は 2011年3月 . その ユーブック は libuv の紹介に最適な場所です。 API ドキュメントは こちら .

全体として、APIはかなり一貫性があり、使いやすいと思います。 混乱の元となりそうな異常事態のひとつは uv_tcp_listen はウォッチャー・ループを作成します。 これは、他のウォッチャーが一般的に uv_*_startuv_*_stop のペアで、ウォッチャー・ループの寿命を制御する関数です。 また、いくつかの uv_fs_* の操作には、それなりの数の引数があります(最大7個)。 コールバック(最後の引数)の有無で同期・非同期の動作が決まるため、同期動作の視認性が低下してしまうことがあります。

最後に、libuvをざっと見てみましょう。 コミット履歴 は、開発者が非常にアクティブであることを示しています。