Windows 8と2012 ServerのRegistered I/Oについての記事
IOCPとRIOのイメージを書いてみた。技術的にはあんまり正しくないと思うけど。
RIOは最初こう書いてみたものの
書き直してみた。どっちにしろ正しさは保証できない。
技術的にちゃんとしてる説明は、まずはMSDN。
- What's New for Windows Sockets
- https://msdn.microsoft.com/en-us/library/windows/desktop/ms740642.aspx#Updated_for__Windows_8_and_Windows_Server_2012
でも英語。
それから、Channel9にBUILD 2011のときの動画があるからそれを観ろと。
- New techniques to develop low-latency network apps
- https://channel9.msdn.com/events/Build/BUILD2011/SAC-593T
でも英語だし、時間かかるしなあ。
非公式だけどThe Server FrameworkというC++の有償フレームワークがあって、そこのブログがいろいろ解説記事を書いてる。
- Winsock Registered I/O Archives
- http://www.serverframework.com/asynchronousevents/rio/
やぱりこれも英語。
読み始めるならこのあたりからか。
- Windows 8 Registered I/O Networking Extensions
- http://www.serverframework.com/asynchronousevents/2011/10/windows-8-registered-io-networking-extensions.html
- Windows 8 Registered I/O and I/O Completion Ports
- http://www.serverframework.com/asynchronousevents/2011/10/windows-8-registered-io-and-io-completion-ports.html
後者の方について適当翻訳をしてみた。気が向いたら他の記事も見てみる。
Windows 8のRegistered I/OとI/O完了ポート
前回のブログ記事でWindows 8のRIOネットワーク拡張を紹介した。RIOで処理の完了を受け取るには3つの方法がある。ポーリング、イベントドリブン、そしてIOCPを使う方法。RIOは柔軟だからいろんな形で使える。ポーリングは高速なUDPとかHFTとかでCPUを使い切れてうれしい。イベントドリブンはスピンロックしなくていいから効率的だけど、今一番興味があるのはIOCPスタイル。普通のネットワーク処理ではこれが一番パフォーマンスがいいんじゃないかな。たぶんだけど。
ちなみにこれらの記事は俺の前提とか直感とかに基づいてるし、SDKとかと同期してるわけじゃないよ。まあ話半分で聞いてくれ。
で、RIOとIOCPを合わせて使うには。
RIOの場合は処理の完了はキューに入る。固定長のデータ構造で、ユーザー空間とカーネル空間で共用されるからユーザーモードでデキューできる(詳しくはBUILDのビデオ観れ)。前回も書いたけど、完了をどうやって受け取るかはキューを作るときに指定する。イベントを渡すか、IOCPを渡すか、何も渡さない場合は自分でポーリング。IOCPなら、あらかじめRIONotify()を呼んでおくと、GetQueuedCompletionStatus()でキューが空だったらI/Oスレッドがプールに戻されて、キューが空じゃなくなると通知が来て処理が続く。サンプルコードはこんな感じ。マジで使うならcompletionKeyとかいろいろやることあるかんね。
if (::GetQueuedCompletionStatus( hIOCP, &numberOfBytes, &completionKey, &pOverlapped, INFINITE)) { const DWORD numResults = 10; RIORESULT results[numResults]; ULONG numCompletions = rio.RIODequeueCompletion( queue, results, numResults); while (numCompletions) { for (ULONG i = 0; i < numCompletions; ++i) { // deal with request completion... } numCompletions = rio.RIODequeueCompletion( queue, results, numResults); } rio.RIONotify(queue); }
で、空じゃなくなったら通知されると言ったが、RIONotify()につき通知は1回きり。RIODequeueCompletion()でデキューした後で、次のIOCP完了を受け取りたければふたたびRIONotify()を呼ぶんだ、いいね?面倒なようだけどもこれはこれでいいんだよ。完了キューにアクセスするスレッドを自分で制御できる(訳者メモ:たぶん、RIONotifyを呼び出したスレッドと、引数として渡したキューとが紐づけられるんだろうと思った)から競合とか考えなくていいんだから。送信と受信で1個ずつキューを使って、それぞれ別スレッドにしてもいい。送受信でキューを1つしか使わなければ、あるソケットに紐づくのは同時には1スレッドだけってことになる。普通のIOCPはスレッドプールを使うから、そこが違う(訳者メモ:ひとつのI/Oが完了すると必ずひとつのスレッドが割り当てられるから、複数のI/Oが絡むと必ずマルチスレッドになる)。
RIOのメリット
順序制御が楽、マジで楽。完了キューには期待した通りの順序で入ってくるから、1スレッドでアクセスすれば期待した順序で取り出せるでしょ?IOCPでも通知は順番通りだけど、スレッドプールのせいで順序制御に余計な手間がかかることを考えれば、RIOの方が簡単。あとRIOには送受信バッファがない。(訳者メモ:そのあとも何かちょっと書いてあるけど読み飛ばし)
他のメリットは1スレッドで続けてデキューできること。デキューしてる間はカーネルモードに遷移する必要もないし、キューが空になってからカーネルモードに入る的なやつで、いい感じ。
RIOのデメリット
I/Oスレッドをフル活用するのは大変だよーん。キューの数とかスレッドの数とかは自分で制御することになるわけだし。一般的な用途ではRIOを生で触るのは辛いから、そういうときは、新しいコネクションをどのキューに割り振るかとか、キューの増減どうするかとか、そういうのをやってくれるフレームワーク的なものが欲しい。キューの数が増えてるにもかかわらずI/Oスレッドが足りない場合、1スレッドの処理を1つのキューにかかりきりにすると、スレッドが割り当たってないキューのコネクションはいつまでも待たされてしまうけどそれでいいの?って話もある。
それから、I/OスレッドがRIONotify()を呼ぶ前にうっかり別の処理でブロックしてしまったりすると、そのキューで扱うソケットは全部待ちになってしまうから、他の接続に迷惑をかけることになるよ。