FreeBSD QandA 1582

FreeBSD QandA

Q. LAN 経由での FreeBSD から Windows へのデータ転送が、その逆に比べ
   圧倒的に遅いです。Windows 同士だとこのような現象は起りません。

A. 一般的な解は、一般的な要因に依存するため、何とも判りませんが、次
   のような場合が Winsock2 の場合にあり得ることは判っています。

   一部の NIC では、NIC のせいなのか NIC Driver のせいなのかは不明で
   すが、Windows で使った場合に、時々 packet の取りこぼしが発生します。
   これは、NIC が用意している Recieve Buffer の大きさが、Winsock2 の
   Recieve Buffer よりも小さい場合、Winsock2 が NIC から packet を
   吸い出す前に NIC の Recieve Buffer が溢れてしまうために起るのでは
   ないか、と推測されます。

   Winsock2 の挙動を tcpdump などで見ると判るのですが、Windows 同士の
   通信において、Winsock は送信において次のようなルールに従っているよ
   うに見えます(これは、そう「見える」と言っているだけであって、そう
   「なっている」という意味で言っているのではありません。Winsock2 の
   実装がどうなっているのかは、そのソースコードを見ることができない以
   上、全く不明です)。

     I) 2つのマシン A,B があって、A->B というデータ転送しかない場合、
        A というマシンは
        Send, Send, Ack 待ち
        というパターンを忠実に守っているように見える。

    II) 2つのマシン A,B があって、双方向に転送すべきデータがある場合、
        A というマシンは

        Send(これには直前の Send に対する Ack を含んでいる),
        Data 待ち(これには直前の Send に対する Ack が含まれている)

        を繰り返す、というパターンを忠実に守っているように見える。

   III) I と II を切り替える際には 0.1 sec の wait が入る。


   もし、Windows 側の NIC のせいでこの現象が起っている場合は、
   Windows->FreeBSD 方向の通信には、この障害は出ていないはずです。
   (別の障害は出ているかも知れませんが :)


   さて、問題の解決方法ですが、ようするに上記の動きを FreeBSD 側で真
   似てやれば問題は解決します。具体的には TCP/IP の Send Buffer Size
   をどうにかして「IP Packet 2つ分」まで減らしてやれば、問題は解決す
   るはずなのです。そうすれば Winsock2 がそれを全て取り込み、Ack を返
   してくるまで先には進めません。

   「IP Packet 2つ分」が何バイトなのかを知るには、基本的には tcpdump
   を使うのが手だと思います。tcpdump は root であれば FreeBSD で使う
   ことができますし、
     WinDump <URL:http://windump.polito.it/>
   を使えば Windows 側で tcpdump を取ることもできます。これらを使って、
   mss の値を獲得します。これが「IP Packet 1つ分」の大きさだと思って
   ください。これを2倍すれば目的の値になります。

   あるいは、ethernet を使っている場合は、mss は大抵、536byte か 
   1460byte のどちらかです。ですので、その丁度2倍 1072byte かあるいは
   2920byte が「IP Packet 2つ分だ」という事ができます。ですので、
   2920 を使ってみて、駄目だったら 1072 を使ってみる、という手もあり
   ます。


   で、ここから先は FreeBSD の管理戦略にかなり依存します。


   1) システム全体を特定の Windows マシンに合わせる、という場合:

      この場合、 sysctl コマンドを使うと良いでしょう。root になって

        $ sysctl -w net.inet.tcp.sendspace=xxxx

      (xxxx は上記の「IP Packet 2つ分」の大きさ) を設定してみましょう。
      多分これでパフォーマンスは向上するはずです。

      ちなみに、
        $ sysctl -w net.inet.tcp.delayed_ack=0
      を設定すると Windows -> FreeBSD 方向の通信も改善される場合がある、
      という話があるそうです。


   2) Windows マシン毎に設定を変える場合:

      残念ながらマシン毎に FreeBSD の設定を変える場合、1 の場合のような
      汎用的戦略はありません。各アプリケーションが、それぞれ、自分が通信
      している相手を認識し、その適切な値をどこかのテーブルから lookup し
      なくてはいけませんが、そのようなサポートのないソフトもあるからです。

      もし、Samba に関して、というのであれば、smb.conf の中の
      [global] section の最後に

        include = /usr/local/etc/smb.conf.global.%m

      のような1行を加えておき、

        /usr/local/etc/smb.conf.global.<その問題の Windows マシン名>

      というファイル中で

        socket options = SO_SNDBUF=xxxx TCP_NODELAY
        (xxxx は上記の「IP Packet 2つ分」の大きさ) 

      を設定するとよいでしょう。上記の include 文は失敗しても smbd の
      起動に影響はありません。従って、特に特別な設定をする必要がない
      target に対しては

        /usr/local/etc/smb.conf.global.<その問題の Windows マシン名>

      というファイルを作成する必要はありません。このあたりは
        <URL:http://www.dd.iij4u.or.jp/~okuyamak/Documents/tuning.japanese.html>
      を参考にして下さい。

      この方法は Winsock2 についてしかチェックできていません。従って、
      Winsock1.1 の場合、あるいは将来できるであろう、Winsock2.1 (あるいは
      Winsock3) でうまく行くのかどうかは判りません。

      もし、現在すでにある程度パフォーマンスが良い場合、SO_SNDBUF の値を
      mss の整数倍にすることで、効率が向上する可能性があります。ですので、
      パフォーマンスを重視するマシンに対しては、そのマシン専用の

        /usr/local/etc/smb.conf.global.<その問題の Windows マシン名>

      ファイルを作った方がいいかも知れません。

      smb.conf には %a(Architecture) というオプションがあります。マルチ
      ブートマシンがある場合は smb.conf を

        include = /usr/local/etc/smb.conf.global.%m
        include = /usr/local/etc/smb.conf.global.%a.%M

      のように変更して、%a と %M で Client OS と Machine Name を指定して
      やると各 OS ごとの設定も可能になります。

同一グループへのリンク

グループ名: winsock


間違い・追加情報を見付けた場合は、 修正案の投稿のしかた を読んだ上で、
QandA@jp.FreeBSD.org まで お知らせください。