ソケットの通信の効率化をネットワークカードレベルで考える


サーバーで沢山のコネクションを受け付ける場合に、スレッドや非同期で処理すると思います。

その際にソケットで単純にRecv()してSend()していますが、ネットワークカードに対して効率の良い書き込みや読み込みの作法はあるのでしょうか？

たとえばスレッドでソケットに大量に書き込んでも、けっきょくネットワークカードからビット情報を通信媒体へ転送する処理がシリアルに処理されているとしたらスレッドをある数以上立てて書き込んでもあまり意味がない(むしろよくない)、などの指標はありますか？

もちろんデバイスとソケットの間にバッファが存在するので、非同期で書けるだけ書いておいて、プログラムは次の処理をすればいいという考えもあると思います。

TCPスロースタート問題や遅延ACKなどのレイヤの話は一旦棚に上げて、単純にソケット書き込みとその先のデバイスの間の話の議論になります。

ネットワークのデバイスレベルの仕組みに詳しい方がいましたら教えていただけると助かります。

参考までに、今回の質問は下記のサイトのNICの物理層を見ていてなんとなく浮かんだ質問です。

<http://lantech.up.seesaa.net/subpage/nic.html>

PCS（Physical Coding Sublayer）  
PMA（Physical Medium Attachment）  
PMD（Physical Medium Dependent）




レイヤーの上から順に

  * socket関係のシステムコール(アプリ)
  * TCP
  * IP
  * Ethernet
  * NICハードウェア

となりますが、書き込み読み込みの作法ということなので、アプリによる読み書きが関係ありそうな範囲に絞って書きます。

## socket読み書きの工夫

ここで工夫できるのは、システムコールを呼ぶ回数を減らすことです。シンプルに無駄なsend/recv呼び出しを減らすことの他に、複数のread/writeをまとめるための、[scatter/gather
I/Oまたはvectored I/Oと呼ばれる](http://en.wikipedia.org/wiki/Vectored_I/O)機構が存在します。

[readに対してはreadv、writeに対してはwritev](http://linuxjm.sourceforge.jp/html/LDP_man-
pages/man2/readv.2.html)。sendとrecvにも対応するものがありますが、send系では、[Linuxではsendmsg()](http://linuxjm.sourceforge.jp/html/LDP_man-
pages/man2/sendmsg.2.html)、WindowsのWinsockでは[WSASend()](https://msdn.microsoft.com/en-
us/library/windows/desktop/ms742203\(v=vs.85\).aspx)、Javaだと[GatheringByteChannel#write()](https://docs.oracle.com/javase/7/docs/api/java/nio/channels/GatheringByteChannel.html#write\(java.nio.ByteBuffer\[\]\))等々。ばらばらで列挙するのが辛いので、必要ならばお使いの言語/フレームワーク名+gather
writeなどのそれっぽいキーワードで調べてください。

## socket読み書きの工夫(普通ではない方法)

中間レイヤーを飛ばして、アプリからNICに直接どーんとアクセスして読み書きしてしまえば最速です。デバイスのメモリをユーザー空間にマップしてしまって、いちいちOSに頼まないでも動かせるようにします。  
当然そういった実装はあって、最も新しめのものだとIntelの[DPDK](http://dpdk.org/)、それと数年前の[netmap](http://info.iet.unipi.it/~luigi/netmap/)が有名でしょうか。技法自体はゆうに10年以上前からあり、各自勝手に実装して速度自慢をしてきました。(そして汎用化されずそのまま消えて…)

中間を端折るということは、当然本来そこにあったTCPスタック等の機能はまったく使えません。可能なのは生データを読み書きすることのみです。重要なことなのに、技術の紹介記事にはあまり書かれませんが…。TCP等を使いたければユーザー空間に独自実装するか、OSのプロトコルスタックをユーザー空間に移植したものを使うことになります。先に紹介したnetmapでは、NetBSDのスタックを移植したもの([Rump](http://wiki.netbsd.org/rumpkernel/))を使うようです。




レイヤーの上から順に

  * socket関係のシステムコール(アプリ)
  * TCP
  * IP
  * Ethernet
  * NICハードウェア

となりますが、書き込み読み込みの作法ということなので、アプリによる読み書きが関係ありそうな範囲に絞って書きます。

## socket読み書きの工夫

ここで工夫できるのは、システムコールを呼ぶ回数を減らすことです。シンプルに無駄なsend/recv呼び出しを減らすことの他に、複数のread/writeをまとめるための、[scatter/gather
I/Oまたはvectored I/Oと呼ばれる](http://en.wikipedia.org/wiki/Vectored_I/O)機構が存在します。

[readに対してはreadv、writeに対してはwritev](http://linuxjm.sourceforge.jp/html/LDP_man-
pages/man2/readv.2.html)。sendとrecvにも対応するものがありますが、send系では、[Linuxではsendmsg()](http://linuxjm.sourceforge.jp/html/LDP_man-
pages/man2/sendmsg.2.html)、WindowsのWinsockでは[WSASend()](https://msdn.microsoft.com/en-
us/library/windows/desktop/ms742203\(v=vs.85\).aspx)、Javaだと[GatheringByteChannel#write()](https://docs.oracle.com/javase/7/docs/api/java/nio/channels/GatheringByteChannel.html#write\(java.nio.ByteBuffer\[\]\))等々。ばらばらで列挙するのが辛いので、必要ならばお使いの言語/フレームワーク名+gather
writeなどのそれっぽいキーワードで調べてください。

## socket読み書きの工夫(普通ではない方法)

中間レイヤーを飛ばして、アプリからNICに直接どーんとアクセスして読み書きしてしまえば最速です。デバイスのメモリをユーザー空間にマップしてしまって、いちいちOSに頼まないでも動かせるようにします。  
当然そういった実装はあって、最も新しめのものだとIntelの[DPDK](http://dpdk.org/)、それと数年前の[netmap](http://info.iet.unipi.it/~luigi/netmap/)が有名でしょうか。技法自体はゆうに10年以上前からあり、各自勝手に実装して速度自慢をしてきました。(そして汎用化されずそのまま消えて…)

中間を端折るということは、当然本来そこにあったTCPスタック等の機能はまったく使えません。可能なのは生データを読み書きすることのみです。重要なことなのに、技術の紹介記事にはあまり書かれませんが…。TCP等を使いたければユーザー空間に独自実装するか、OSのプロトコルスタックをユーザー空間に移植したものを使うことになります。先に紹介したnetmapでは、NetBSDのスタックを移植したもの([Rump](http://wiki.netbsd.org/rumpkernel/))を使うようです。

