===
本研究は並行・並列処理の学びを第一の目的とし、
一定時間内にできるだけ大量のHTTPリクエストを送る方法を模索するものです。
並行・並列処理についてはひよっこなので、上級者の方々から見れば「なにアホなことやってんの?」と思われる可能性があります。(いい方法を教えて下さい!)
ゆるいルール
- プロトコルはHTTPとする。
- 任意のホスト・パスに任意のリクエストを送ることができる。
- POST -> GET -> PUTなど
- あくまで目的は学びであり、インターネット社会を攻撃してはならない。
more
- 速いほうがなお良い
- リクエスト順は保証されている方がなお良い
- 言語的な特性よりも、アルゴリズム的な特性に注目したい
- 標準ライブラリーのみで実装するとなお良い
- 言語・ライブラリ等はできるだけ最新のものを使用する
実装
CRubyで実装してみました。全コードはこちら。
検証用server
検証用のサーバーは約10msでレスポンスを返す想定とする。
require 'sinatra'
post '/' do
sleep 0.01
'CREATED'
end
Sync
単純にループさせる方法。
実装は簡単だが、この実装だとTCPSocketについて、writeした後readできるようになるまでじっとCPUは待ち続ける。
よって、どんなにがんばっても検証用サーバーの処理時間0.01sの場合、1秒間に100回しかリクエストを飛ばせない。
Thread
CRubyのThreadはVMにGILがかかっているためCPU的な並列動作は難しい(やり方を知らない)。
だが、IO的な並行動作はOSに任せることができる。
よって、socketがreadで待っている間も他のThreadを動作させることができるので、高速化が期待できる。
そこで、共通のQueueを用意していくつかのThreadで手の空いたものから次々に処理していく実装にしてみた。
Select
同時に多数のTCPSocketを繋げ、read可能になったsocketから順に処理していくことにより、read時間の無駄を減らすことによって高速化を期待する実装。
IO.selectを用いて、シングルスレッド・多重IOな擬似イベント駆動処理としている。
CRuby標準のNet::HTTPライブラリーではwrite/readを分けて実行できるようにはなっていないので、既存クラスを拡張・privateメソッドの呼び出しなど大分無理をしている。
Epoll
select(2)と似ているが、より大量のファイルディスクリプタ監視に向いたAPI。
epollでの性能が知りたかったので、gem io-epollを自作。
Selectのほぼ置き換えとして、ループ毎にepoll_wait(2)を呼び読み込み可能になったものから読み込んで直後書き込みを行うようにした。
検証
$ ruby --version
ruby 2.2.0preview1 (2014-09-17 trunk 47616) [x86_64-darwin13]
$ uname -a
Darwin ***.local 13.4.0 Darwin Kernel Version 13.4.0: Sun Aug 17 19:50:11 PDT 2014; root:xnu-2422.115.4~1/RELEASE_X86_64 x86_64
検証用サーバーに対して一秒間に何リクエスト帰ってきたかを計測。
serverの処理時間(sleep時間) = 0.01sの場合
req/sec: 1秒間にリクエストを送った数
res/sec: 1秒間にレスポンスを受けた数
Sync 63req/sec
Sync 63res/sec
Thread 333req/sec
Thread 317res/sec
Select 437req/sec
Select 309res/sec
Epoll 447req/sec
Epoll 319res/sec
serverの処理時間 = 0.3sの場合
Sync 4req/sec
Sync 3res/sec
Thread 64req/sec
Thread 48res/sec
Select 319req/sec
Select 191res/sec
Epoll 345req/sec
Epoll 217res/sec
計測の結果、多重I/Oを用いた実装(Select)とepoll APIを用いた実装(Epoll)が優秀という結果となった。
2つは均衡しているが、同時に監視するファイルディスクリプタの数によって差が出てくる可能性はある。
考察
Threadの弱点は送信順が不確定なことなのでなんとか克服したいが、より実行速度が遅くなるものと思われる。
Thread数をIOの多重数に同じにすることでそこそこのパフォーマンスを発揮するが、それでもI/O多重化以上になることはなかった。(Thread 64個とかどうよ……。)
Selectでは俗に言われるI/O多重化は速いという現象が確認できた。
I/O多重化で問題なのは同時に開くSocketの数で、あまり多く開き過ぎると極端に動作が遅くなるかOSからErrno::ETIMEDOUT
が発生するため、同時に開くSocket数を制限する必要がある。
この制限値は実行環境に依存するものと思われる。
IO多重化は線形走査な実装なのでまだまだ早くなる余地がある。
またEpollではSelectと同等かそれ以上の性能を期待できることがわかった。
今後の課題
- 並行・並列処理の学び
- 多重プロセス法の実装
- epollの利用(eventmachine or 自作)
- hping砲とは?
- golangで書いてみる
- 速さが足りないッ!!
- もっと速い方法をお待ちしております