LAST MODIFIED: 2009/03/01 09:58:39 UTC
不定期更新の日記です。ディスクスペースの関係上、 あまりに古くなったものは順次消していきます。 この日記の更新は、今野さんの *BSD Diary Links から取得することが可能です。
[→
書籍情報] [→ amazon.co.jp] [→ cbook24.com] [→ 関連する日記のエントリ (09/24)] |
7.1R 新機能その二。cpuset(2) システムコール群の追加。 使い方はこんな感じ。
cpuset(2) は、プロセスやスレッドの、割り当て可能 CPU を制御するためのシステムコール群です。 割り当てる CPU は、直接プロセス単位・スレッド単位で設定することもできますし、 「プロセッサセット」と呼ばれる、割り当てる CPU のグループを設定して、 そのプロセッサセットをプロセスに割り当てることもできます。 プロセッサセットとは、プロセスの実行に使われる論理 CPU を集めたものです。
たとえば、4 個 CPU があるシステムで、1 番と 3 番だけ割り当てたい、という場合は、 1 と 3 だけを含むプロセッサセットを定義しておいて、 それを割り当てたいプロセスに指定します。 プロセスがひとつなら直接割り当てても手間は変わりませんが、 複数ある場合に、全部まとめて変更できるようになっているわけです。
さて、プロセッサセットには ID 番号が付いていて、FreeBSD はデフォルトで 2 個、 #0 と #1 のプロセッサセットが定義されています。 #0 のプロセッサセットは変更不可能なもので、 #1 は、何も指定せずに実行したプロセスが使う論理 CPU 群を表したものです。
FreeBSD は起動時に、搭載されているすべての論理 CPU を #0 と #1 に登録して、実行されるプロセスは #1 のプロセッサセットに割り当てられます。
プロセッサセットや割り当てられている CPU は、 cpuset(1) というコマンドで調べることが可能です。
% cpuset -g -s 1 cpuset 1 mask: 0, 1, 2, 3
これは 4 CPU のマシンで実行した例です。-g は情報の表示を指示するフラグ、 -s は、プロセッサセットの ID 番号を指定するオプションです。 プロセッサセット #1 には、0, 1, 2, 3 の 4 個の CPU が登録されていることが分かります。
% cpuset -g -s 1 cpuset 1 mask: 0, 1, 2, 3, 4, 5, 6, 7
これは CPU 8 個のシステム。単純に増えるだけです。
# cpuset -g -i -p 1 pid 1 cpuset id: 1
このコマンドは、特定プロセスに割り当てられているプロセッサセットを表示します。 -i はプロセッサセットを表示するためのフラグで、 -p の後ろの 1 は、PID です。FreeBSD (に限らず UNIX 系 OS のほとんど) は基本的に init プロセスからはじまりますので、これがデフォルトに対応することになります。 ちなみに、そのプロセスへのアクセス権がないと cpuset(1) がエラーになりますので、このコマンドの実行には root 権限が必要です。
# cpuset -g -p 1 pid 1 mask: 0, 1, 2, 3
-i を付けなかった場合、 その特定プロセスに割り当てられている CPU を直接表示します。 通常、これはプロセッサセットに含まれる CPU と一致する (この例では 0, 1, 2, 3) のですが、一致しない場合もあります。 それは、次のような理由からです。
プロセッサセットで割り当てられた CPU は、cpuset(1) コマンドを使ってプロセス単位で変更することができます。 その場合、そのプロセッサセットが変更されるのではなく、 指定したプロセスのみが変更されます。つまり、#1 が設定されているプロセスが複数ある (ここでプロセス A とプロセス B と名前を付けます) とすると、
プロセッサセット #1 を変更すると、 そのセットに属しているすべてのプロセス (A と B) の割り当て CPU が変わる。
プロセス A の割り当て CPU を変更すると、プロセス A のみ、割り当て CPU が変わる。ただし、 「cpuset -g -i -p NNN」で表示されるプロセッサセット ID は変わらない。
プロセス A の割り当て CPU を変更して、さらにプロセッサセット #1 を変更すると、 両方の変更が統合されて適用される。まず #1 が適用され、 その範囲でプロセス A の直接指定分が適用される。
ということです。
% tty /dev/ttypp % pgrep -t ttypp 1956 % cpuset -g -p 1956 pid 1956 mask: 0, 1, 2, 3
これは、自分の実行しているシェルのプロセッサセットを表示させています。 ちなみに、-p N を省略すると、cpuset(1) プロセスの PID が使われます。
つまり cpuset(1) ユーティリティを使うと、裏で cpuset(2) システムコール群が発行されて、プロセスやスレッドに割り当てられているプロセッサセットを 変えたり、先ほどの例のように表示させたりができるわけです。
プロセッサセット ID や、プロセス単位で設定されている論理 CPU は、子プロセスに継承されます。
さて、変更する例に移りましょう。変更方法は大きく分けて、 1) プロセッサセットに割り当てられている CPU を変える方法、 2) プロセッサセットを切り替える方法、 3) プロセスやスレッドの CPU 割り当てを直接変更する方法の 3 つです。
# cpuset -g -p 1 pid 1 mask: 0, 1, 2, 3 # cpuset -g -i -p 1 pid 1 cpuset id: 1 # cpuset -l 1-2 -s 1 # cpuset -g -p 1 pid 1 mask: 1, 2 # cpuset -l 0-3 -s 1 # cpuset -g -p 1 pid 1 mask: 0, 1, 2, 3
プロセッサセット #1 の CPU 割り当てを、1 と 2 のみに制限しました。 制限するには、-l N-N というオプションを使います。CPU の番号は、 「1」「0-1」「1,3」のように、単体の数値、ハイフンで結んだ範囲、 カンマで区切られたリストの形で指定できます。-s N は、 プロセッサセット ID の指定です。
PID 1 のプロセスの割り当て CPU が変化しているのが確認できると思います。 もちろん、#1 に割り当てられているすべてのプロセスに影響があります。
プロセッサセットを切り替えるには、次のように -s N を指定します。 N は、プロセッサセット ID です。
# cpuset -s 2 -p 1
ただし、ここで指定するプロセッサセット ID は、あらかじめ作成されていなければなりません。 #0, #1 以外のものの作成方法は、後述します。
直接変更する方法は、次のようになります。
% tty /dev/ttyp3 % pgrep -t ttyp3 33874 % cpuset -g -i -p 33874 pid 33874 cpuset id: 1 % cpuset -g -p 33874 pid 33874 mask: 0, 1, 2, 3 % cpuset -l 1-2 -p 33874 % cpuset -g -p 33874 pid 33874 mask: 1, 2 % cpuset -g -i -p 33874 pid 33874 cpuset id: 1 % cpuset -l 0-3 -p 33874 % cpuset -g -p 33874 pid 33874 mask: 0, 1, 2, 3
PID を調べるためにごそごそやってますが、 先ほどの例との本質的な違いは、-l オプションと一緒に -s ではなく -p を指定したところです。-g で表示されるプロセッサセット ID が変わらないので、 ちょっと混乱するかも知れません。
今までの例ではプロセッサセット #1 を使っていましたが、 新しいプロセッサセットを定義することも可能です。
# cpuset -c -l 1-2 /bin/sh # cpuset -g -s 2 cpuset 2 mask: 1, 2 # exit # cpuset -g -s 2 cpuset: getaffinity: No such process
新しいプロセッサセットを作成するには、-c フラグと、 -l N-N オプション、さらに新しいプロセスを生成するための実行ファイル名が必要になります。 ここでは /bin/sh を指定していますが、 新しいプロセッサセットは、この新しいプロセスに割り当てられます。
プロセッサセット ID の定義には、root 権限が必要です。 また、プロセッサセット ID は、2 から順番に増えていきます。 このプロセスが終了すると、そのプロセッサセットは破棄されます。
以上が、基本的な cpuset(1) コマンドの使い方です。 例ではプロセスしか扱いませんでしたが、-t N というオプションで N にスレッド ID を指定することも可能です。
最近のシステムはマルチプロセッサの対称性が低いので、 そのあたりを考慮したスケジューリングの制御が性能向上に欠かせないわけですが、 ULE がデフォルトになったのに合わせて、 とりあえず手動で制御できる枠組みが入った、というところです。
Linux では taskset(1) コマンドが近いのではないかと思います。 HP-UX や Solaris では psrset(1), IRIX は cpuset(1) と dplace(1), AIX では bindprocessor(1) ですね。ライブラリレベルでは、 PThread ライブラリに (non-portable な関数として) 組み込んでいるものもいくつかあります。
出ました。 em(4) を使っている人は、ご注意を。
7.1R 新機能その一。複数のルーティングテーブルのサポート。 使い方はこんな感じ。
GENERIC カーネルでは、(普通の方法では) 有効にできないようになっているので、 まずはカーネルを再構築しましょう。再構築する前に、 次のオプションを追加します。
options ROUTETABLES=2
この数字はルーティングテーブルの数を表します。 今のところ 16 個まで設定可能。再構築の手順等は、 ハンドブックを読んでください。
ルーティングテーブルと呼ぶのは長ったらしいので、ここからは もうちょっと抽象的な呼称である FIB と呼びます。
FIB には、0 番、1 番のように、順番に番号が付けられています。また、 FIB は、プロセス単位で割り当てることが可能です。割り当てるには setfib(1) というコマンドを使います。こんな感じ。
# setfib -3 ping target.example.com
この場合、ping には FIB 3 番が割り当てられます。
それぞれの FIB に経路を設定するには、"setfib 1 route..." のように、 route コマンドを setfib と組み合わせて使うわけです。
で、7.1R 以降における「FIB がひとつしかない状態」は、 「常に FIB 0 番を使い続けていること」と同じになります。 カーネルオプションを追加しないと FIB は複数にならないものの、 カーネルの内部処理としては、 「ひとつ」は「複数」の特別な場合にすぎません。
FIB が複数ある場合、次のようなルールで選択されます。
FIB が設定されているパケットは、その FIB が使われます。 設定されていなければ、FIB 0 番が使われます。
外部から到着して、IP フォワーディングの対象となるパケットには、 FIB 0 番が使われます。
あるプロセスが listen している TCP ソケットがあり、 そのプロセスに FIB が割り当てられていると、 accept したソケットにも同じ FIB に割り当てられます。
TCP 以外で FIB が割り当てられているパケットの場合、 そのパケットに対する応答パケットは、同じ FIB が割り当てられます。
gif(4) や tun(4) などのトンネルインタフェースで 生成されるカプセル化されたパケットは、 そのインタフェースを作ったプロセスの FIB が割り当てられます。 (つまり setfib 1 ifconfig gif create のように設定できる)
ルーティングメッセージは、 そのメッセージを生成したプロセスの FIB が割り当てられます。
とりあえず FIB を複数用意して、"setfib 1 netstat -nr" のように実行してみると、感じがつかめると思います。 適当に経路を追加・削除しても、 0 番をいじくらなければ、基本的に影響はありません。
FIB の数は、カーネルオプションの他、loader tunable でも変更できます。 主なものは次のとおり。
net.fibs: FIB の数。16 個まで指定可能。
net.my_fibnum: デフォルト FIB の番号。 何も指定しなければ 0 になる。今までの記述で「0 番」と表現していたものは、 この番号を変えると変わる。
net.add_addr_allfibs: デフォルト FIB 以外の FIB 全部に、 インタフェースの経路を自動的に追加するかどうか。 何も指定しなければ 1 になっている。0 にすると、 明示的に経路を追加しないと 0 番以外には経路が追加されなくなる。
また、FIB は IPFW でも設定できます。allow の代わりに setfib というアクションルールを指定します。たとえば
# ipfw add 1000 setfib 番号 from any to any
とすると、この条件にマッチするパケットが指定された番号の FIB に割り当てられます。そしてルールの処理は、マッチしようがしまいが、 そのまま直後のルールに続きます。
Jail と組み合わせると、なかなか面白い応用ができます。
FTP サイトに ISO イメージが行き渡りつつあります。 アナウンス準備中。もう 1-2 日くらいで出ます。
1/1 のエントリで書いた pcb まわりの変更ですが、 具体的には UDP の SMP スケーラビリティが改善されています。 前回のリリースでは MySQL や PostgreSQL の性能を調査しつつ向上させることを ひとつの目標にしていましたが、今回はそれに DNS query 性能を加えて調査・改善した成果になります。
まずはBIND 9.4.2 のベンチマークがこれ。Intel 8-core + 10GbE なマシンで計測した DNS query 数/sec の結果です。 開発途中のコードのベンチマークなのでわかりにくいですが、 8.0 UDP rwlock + ULE topo というのが、今回 7.1R に入った改善点に近いものになります (厳密には、CPU topology support が 8-CURRENT のものとは違う)。 7.0 ULE や改善前の 8.0 ULE (時期的には 7.0R が出たあたり) は、 今一つ伸びておらず、7.0 + 4BSD は性能としては悪くないのが確認できます。
BIND だけだとつまらないので、 NSD と比較したものはこちら。 全体的に性能は上になりますが、スケーラビリティは微妙な結果です。 4 CPU を超えるあたりから、横ばいか、速度が低下する傾向にあります。
とりあえず、性能向上に貢献している変更の一部が 7.1R に取り込まれたような形です。 上記ベンチマークでも ULE が遅くなっているデータがいくつか見られますが、 これはワークロードに強く依存しますので、この結果だけ見て ULE はダメだとか、 ULE は良いという話にならない点にご注意を。
たとえば、BIND のベンチマークでは 6.3 よりも 7.0 ULE のほうがずっと遅い結果 (ただし スケーラビリティは 7.0 ULE の方が良い) になっていますが、NFS サーバとして使う場合は 7.0 ULE の方がずっと高性能です。 具体的な数値では、サーバが 8 CPU, クライアントが 2 CPU で NFS アクセス (read のみ) した場合、 6.3 が 5 スレッド並列・280MB/sec 前後で飽和するのに対し、 7.0 ULE は 7 スレッド並列・430MB/sec 前後までスケールします。
ちなみに BIND の性能ベンチマークは、 ISC の管理しているルート DNS サーバの実負荷パターンを用いています。
明けました。
やっとこさ SA が出せたので、すぐに出せるかと思いきや、 ATA まわりの問題 (ITE IT8211F なチップ) が 報告されたので、それを検証して変更点を元に戻す、 というごたごたが発生。
時間がとられたものの、 もう最終 build 体制に入りました。リリースは来週火曜日。 2008 年内に、という目標もあったのですが、 最後のリリースノート更新がきつくて (普段からさぼっているのが悪いのですが) それは達成ならず。 7.0R-7.1R 間の差分は 50MB くらいありますが、 それを頭から読んで変更点をまとめていく作業です。 一気にやると死ねます。
ハイライトは ULE (デフォルトで有効) と DTrace かと思います。 また、pcb まわりのロックが rwlock に変わっているので、 SMP でのネットワーク性能が向上しています (具体的な数字はただいま準備中)。 リリースノートに書いたものも含めて細かな fix がたくさんあるのですが、 基本的には性能と安定性の向上に力点を置いていますので、 驚くような新機能はありません。
ちなみに 8.X 系列には山ほど新機能が入っています。 7.X には、multiple FIB (ルーティングテーブルを複数持って、 プロセス単位で切替えられる機能) が、少なくともマージされる予定です。
LAST MODIFIED: 2009/03/01 09:58:39 UTC