2022-01-24 select v.s. epoll / ネイティブスレッド v.s. グリーンスレッド
select vs. epoll
select 及び select とよく対比される epoll を調べた。
Ruby IO.select
Ruby の IO.select
は select(2) を呼んでる。
IO.select (Ruby 3.1 リファレンスマニュアル)
select(2) を実行します。
与えられた入力/出力/例外待ちの IO オブジェクトの中から準備ができたものをそれぞれ配列にして、配列の配列として返します。タイムアウトした時には nil を返します。
Multiplexing Connections - Working With… より引用
# Given an Array of connections.
connections = [<TCPSocket>, <TCPSocket>, <TCPSocket>]
loop do
# Ask select(2) which connections are ready for reading.
ready = IO.select(connections)
# Read data only from the available connections.
readable_connections = ready[0]
readable_connections.each do |conn|
data = conn.readpartial(4096)
process(data)
end
end
Ruby epoll
The poll(2) system call provides some slight differences over select(2) but is more or less on par. The (Linux) epoll(2) and (BSD) kqueue(2) system calls provide a more performing, modern alternative to select(2) and poll(2). For example, a high-performance networking toolkit like EventMachine will favour epoll(2) or kqueue(2) where possible.
上記事でも引用されているように、 epoll
を ruby で使いたければ、ActionCable や puma 内部でもつかわれている nio4r gem を使うのがよい。
- gem: socketry/nio4r
- ソースコード箇所: ext/libev/ev_epoll.c
計算量の比較
select
: ファイルディスクリプタを一つ一つ見にいくので O(n) の計算量poll
も同様
epoll
: カーネルでファイルディスクリプタのステータスを管理してくれてどのファイルディスクリプタがreadyかをカーネルが返してくれるので O(1) の計算量
ノンブロッキングI/Oと非同期I/Oの違いを理解する
epoll API は Linux カーネル 2.5.44 に導入されたもので、2.6以降なら利用可能。epollはファイルディスクリプタの数に制限が無いのに加えて、ファイルディスクリプタの状態変化監視も改善されている。具体的には,ファイルディスクリプタの状態をカーネルで監視しており、変化したものを直接通知できるためselect、pollの時のようにループを使った監視をする必要がない。これにより、計算量はO(1)となり、よりパフォーマンスの高い多重化I/Oを実現できる。
まとめると下記のようになる。epoll
が一番モダンでパフォーマンスが良い。
epoll > poll > select
Rubyで学ぶWebサーバーアーキテクチャ(Preforking, ThreadPool, イベント駆動モデル) - Qiita
こうやって複数のI/Oを管理する技術が I/O多重化(I/O Multiplexing) と呼ばれる技術で、 select(2) poll(2) epoll(2) などのシステムコールに相当します。例えば slect(2) は引数に与えたIOの配列のうち、利用可能なもののみを返します。rubyでは以下のように書けます。
readable, writable, exceptable = IO.select(sockets)
ちなみに select(2) はブロッキングI/Oです。またO(N)であるため引数のコネクション数が増えると性能が悪化します。 O(1)で同じことを実現するのが epoll(2) となるのですが、rubyではデフォルトで利用できないため割愛します。
Go の epoll
epoll使用の流れとしては以下のようになります。
epoll_create1
関数でepollインスタンスを作り、返り値としてそのインスタンスのfdを受け取るepoll_ctl
関数で、epollの監視対象のfdを編集するepoll_wait
関数で、監視対象に何かイベントが起こっていないかをチェックする
ref. Goランタイムケーススタディ|Goでの並行処理を徹底解剖!
参考
- I/O多重化の方法(selectとepollの違い) - 脳汁portal
- I/Oを多重化するためのシステムコール(select, poll, epoll, kqueue) - $shibayu36->blog;
- Goのnet/httpのServer側デザインの良さについて語る(Listen編)
- epoll のサンプルを読んだ - かっこかり(仮)
ネイティブスレッド v.s. グリーンスレッド
スレッドには大きく分けて2つの種類があります。
- ネイティブスレッド
- グリーンスレッド
ネイティブスレッドはOSが提供するスレッドで、グリーンスレッドはOSではなく言語や仮想マシンが提供するスレッドです。
Ruby の場合
ネイティブスレッドを用いて実装されていますが、現在の実装では Ruby VM は Giant VM lock (GVL) を有しており、同時に実行されるネイティブスレッドは常にひとつです。ただし、IO 関連のブロックする可能性があるシステムコールを行う場合には GVL を解放します。その場合にはスレッドは同時に実行され得ます。
class Thread (Ruby 3.1 リファレンスマニュアル)
追記
RubyのM:Nスレッドの話はこの笹田さんとの深堀りインタビューが大変よかった。 #fukabori_rubykaigi_2022https://t.co/lCL9x55ymu
— toshimaru (@toshimaru_e) January 17, 2023