9409


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

その際にソケットで単純に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）


CC BY-SA 3.0
2015-04-21T09:04:06.733
9375
2015-04-22T14:38:06.223
2015-04-21T16:52:08.530
799
3496
question
network tcp
ソケットの通信の効率化をネットワークカードレベルで考える


存在しないと思います……ネットワークのデバイスレベルの仕組みに詳しくなくて恐縮ですが、以下に理由を挙げます。

基本的にプログラマに出来ることは、可能な限り大きなデータをRecv や Send
関数に渡し、OSが用意したプロトコルスタックが判断した送受信可能なバイト数（Recv/Sendの返り値）に従って処理を続けるプログラムを書く事だけです。

通信に使用するバッファの取り方も、プロトコルスタックが判断します。

1つの接続に対して、少しずつバッファを配分するような設定になっていると、プログラム側でスレッドを作り、沢山接続を作ることで性能が向上します……しかしこれはもう、ソフトウェアの工夫というよりは、プロトコルスタックの実装がそうなっているから、というだけです。


CC BY-SA 3.0
2015-04-21T10:27:10.890
9379
2015-04-21T10:27:10.890
4391
9375
answer


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

@Lightenさんの回答にもありますが、「可能な限りデータを一括してRecv/Sendする」が原則です。アプリ側で確保するユーザランドのメモリ領域と、ネットワークドライバ側のメモリ領域との間でのメモリ転送は、一般にコスト高となるため極力避けるべきです。

また、直接の回答ではありませんが、下記資料も参考になるかと思います。

  * [10GbE時代のネットワークI/O高速化](http://www.slideshare.net/syuu1228/10-gbeio)
  * [Ethernetの受信処理](http://www.slideshare.net/syuu1228/ethernet-39611199)
  * [マルチコアとネットワークスタックの高速化技法](http://www.slideshare.net/syuu1228/ss-19602947)


CC BY-SA 3.0
2015-04-21T10:41:39.580
9380
2015-04-21T10:41:39.580
49
9375
answer


レイヤーの上から順に

  * 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/))を使うようです。


CC BY-SA 3.0
2015-04-21T16:40:17.320
9409
2015-04-21T16:40:17.320
799
9375
answer


高レイヤから低レイヤでの効率を左右できるのか、と限定された質問ですので、それに沿って回答します

L5以上の立場からL1の効率性をどうこうすることはできません。レイヤ構造である以上、直接干渉できるのは自分の一つ下のレイヤまでです。(間接的にはもう一つ下のレイヤにも影響しますが)

「大きな単位でSend/Recv」するというのが効果を及ぼすのはL4の効率性のことであり、L1にはほぼ無関係な話です。L4に対してどのような操作をしようが、L1で効いてくるのは低レイヤの実装です。

10オクテットずつ1000回Sendするのと10000オクテット1回Sendするのでは効率が違う、というのはL1の話ではありませんね。

極端なことを言えばアプリケーションが直接 NIC
を操作すればL1の効率を左右できますし他の回答の通りそのような仕組みはあります。が、これはアプリケーションそのものが低レイヤを実装しているだけの話で、高レイヤからデバイスレベルの効率を操作できるという仕組みではありません。

まとめると、高レイヤから(階層を飛ばした)低レイヤの効率を左右することはできないし、低レイヤの効率をコントロールしたいのであればそのレイヤを直接操作しなければならない、ということです。


CC BY-SA 3.0
2015-04-22T01:03:57.133
9426
2015-04-22T01:03:57.133
5793
9375
answer


低レイヤでの操作の話が出ていますが、それとは逆の話を。  
最近のネットワークカードは[オフロード処理](http://ja.wikipedia.org/wiki/%E3%82%AA%E3%83%95%E3%83%AD%E3%83%BC%E3%83%89_%28%E3%82%B3%E3%83%B3%E3%83%94%E3%83%A5%E3%83%BC%E3%82%BF%E7%94%A8%E8%AA%9E%29#.E3.83.8D.E3.83.83.E3.83.88.E3.83.AF.E3.83.BC.E3.82.AF.E3.82.A2.E3.83.80.E3.83.97.E3.82.BF.E3.81.AB.E3.82.88.E3.82.8B.E3.82.AA.E3.83.95.E3.83.AD.E3.83.BC.E3.83.89)に対応しています。IPヘッダのチェックサムだけでなく、TCPのセグメント化までしてくれるものも普及しています。  
ですので、あまり低レイヤのことは意識せず、ユーザーランドからは可能な限り書き込んでおいて、OS及びNICに任せるのもひとつの手かと思います。

他に出てきていない話題として、Windows SocketのWSASend()はscatter/gather I/Oだけでなく、Overlapped
I/Oもサポートします。これは先行するI/Oの完了を待つことなく次のI/Oを発行することができます。


CC BY-SA 3.0
2015-04-22T14:38:06.223
9473
2015-04-22T14:38:06.223
4236
9375
answer