優雅な生活の設計と実装

The Design and Implementation of the Gracious Days


LAST MODIFIED: 2009/03/01 09:58:39 UTC

新しい秩序の確立は、他の何にも増して難しく、
成功する可能性が低く、危険な事業である。
改革者は旧秩序から利益を得ている
全ての者を敵にまわし、
新秩序から利益を受けるはずの者からは
及び腰の支持しか集められない。
--- Niccolo Machiavelli, The Prince

この種の「保護」は初心者を保護するかも知れないが、
熟練ユーザを窮地に追い込むことになる。
というのは、何が親切であり、何が適切でないかかという
オペレーティングシステムの考え方の裏をかくことばかりに
かなりの労力を費やさなければならないからである。
--- A.S.Tannenbaum, Modern Operating Systems


不定期更新の日記です。ディスクスペースの関係上、 あまりに古くなったものは順次消していきます。 この日記の更新は、今野さんの *BSD Diary Links から取得することが可能です。

January 2009

感想はこちらまで (内容は匿名のメールで送られます)

コメント:

注: お返事が必要な場合は直接メールください。 ただし、確実にお返事するかどうかはわかりませんのであしからず。

発売中:「Absolute BSD〜FreeBSDシステム管理とチューニング」
[book image] [→ 書籍情報]
[→ amazon.co.jp]
[→ cbook24.com]
[→ 関連する日記のエントリ (09/24)]

Friday, January 9

* 7.1-RELEASE (cont'd)

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 と名前を付けます) とすると、

ということです。

% 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 な関数として) 組み込んでいるものもいくつかあります。

Monday, January 5

* 7.1-RELEASE

出ました。 em(4) を使っている人は、ご注意を。

* 7.1-RELEASE (cont'd)

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 を複数用意して、"setfib 1 netstat -nr" のように実行してみると、感じがつかめると思います。 適当に経路を追加・削除しても、 0 番をいじくらなければ、基本的に影響はありません。

FIB の数は、カーネルオプションの他、loader tunable でも変更できます。 主なものは次のとおり。

また、FIB は IPFW でも設定できます。allow の代わりに setfib というアクションルールを指定します。たとえば

# ipfw add 1000 setfib 番号 from any to any

とすると、この条件にマッチするパケットが指定された番号の FIB に割り当てられます。そしてルールの処理は、マッチしようがしまいが、 そのまま直後のルールに続きます。

Jail と組み合わせると、なかなか面白い応用ができます。

Sunday, January 4

* 7.1-RELEASE

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 サーバの実負荷パターンを用いています。

Thursday, January 1

* New Year

明けました。

* 7.1-RELEASE

やっとこさ SA が出せたので、すぐに出せるかと思いきや、 ATA まわりの問題 (ITE IT8211F なチップ) が 報告されたので、それを検証して変更点を元に戻す、 というごたごたが発生。

時間がとられたものの、 もう最終 build 体制に入りました。リリースは来週火曜日。 2008 年内に、という目標もあったのですが、 最後のリリースノート更新がきつくて (普段からさぼっているのが悪いのですが) それは達成ならず。 7.0R-7.1R 間の差分は 50MB くらいありますが、 それを頭から読んで変更点をまとめていく作業です。 一気にやると死ねます。

ハイライトは ULE (デフォルトで有効) と DTrace かと思います。 また、pcb まわりのロックが rwlock に変わっているので、 SMP でのネットワーク性能が向上しています (具体的な数字はただいま準備中)。 リリースノートに書いたものも含めて細かな fix がたくさんあるのですが、 基本的には性能と安定性の向上に力点を置いていますので、 驚くような新機能はありません。

ちなみに 8.X 系列には山ほど新機能が入っています。 7.X には、multiple FIB (ルーティングテーブルを複数持って、 プロセス単位で切替えられる機能) が、少なくともマージされる予定です。

感想はこちらまで (内容は匿名のメールで送られます)

コメント:

注: お返事が必要な場合は直接メールください。 ただし、確実にお返事するかどうかはわかりませんのであしからず。