読者です 読者をやめる 読者になる 読者になる

Rubyでepollできるgem 'epoll'を作ってみた

epoll | RubyGems.org | your community gem host

ksss/epoll · GitHub

epollはnginxやnode(libuv)などでも使われている、大量のファイルディスクリプタを効率よく監視するためのAPIのことです。

イベントドリブンI/O

epoll APILinuxでのみ(たぶん)動作し、Mac等のBSDでは似たAPIとしてkqueueがあります。

例えばepollを軸にサーバーを作るなら、serverが作った各クライアント間とのI/Oをできるだけ待ちをなくして読み書きできるときに実行する事が可能です。並行で低リソースに処理できるので、大量のリクエストを同時に処理することが期待されます。

epollではレベルトリガ通知(今なら大丈夫通知!)、エッジトリガ通知(前から変わったよ通知!)のどちらにも対応し、いわゆるイベントドリブンI/Oを実現します。

Rubyでのepollを使っているgemといえば、eventmachinefluentdで有名なcool.ioなどがあります。

今回はローレベルAPIだけを持つ小さなライブラリとしてのAPIを作りました。 CでがんばるよりRubyでがんばりたい方にオススメです。

検証

簡単なサーバーを作ってab($ ab -k -n 10000 -c 1000 http://127.0.0.1:4000/)で性能を測ってみました。

webrick

性能比較用に誰でも使えるwebrickから。

webrickは内部的にはselectで待ってthreadを立てる方式のようです。

require 'webrick'
srv = WEBrick::HTTPServer.new(DocumentRoot: './',
                              BindAddress: '127.0.0.1',
                              Port: 4000)
srv.start
Requests per second:    5132.49 [#/sec] (mean)

epoll

エッジトリガ通知で各I/Oに指定した通知が来たら処理という風に書くことができます。 APIがローレベルなのでコードが長くなりがち。

require 'epoll'
ep = Epoll.create
ep.add server, Epoll::IN
loop do
  ep.wait.each do |ev|
    data = ev.data
    events = ev.events

    if data == server
      socket = server.accept
      ep.add socket, Epoll::IN|Epoll::ET
    elsif (events & Epoll::IN) != 0
      data.recv(1024)
      ep.mod data, Epoll::OUT|Epoll::ET
    elsif (events & Epoll::OUT) != 0
      data.puts response
      ep.del data
      data.close
    else
      raise IOError
    end
  end
end
Requests per second:    5601.02 [#/sec] (mean)

まとめ

Webrickは素晴らしく汎用的で大量の機能を持ち、正しくRequestをパースしてResponseを組み立てて……としているので一概には言えませんが、epollのserverもまあ速いとは言いがたい感じなので、ラッパーを書くなりするとよいのかもしれません。

あとLinuxプログラミングインターフェースが参考図書として最高です。

Linuxプログラミングインタフェース

Linuxプログラミングインタフェース

io-epollでは、GVL対応もしたのでtimeout{}で囲ったりもできます。

しかし、まだまだ実験的な実装段階なので、「使ってるよ!」とかでよいので @_ksss_で皆様の声・アイデアをお待ちしております。

see also

TODO

ffiってなんだ。