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