バージョン2.4および2.5に採用されている現在のLinuxの対称マルチプロセシング (SMP) カーネルは、ハイパー・スレッド処理を考慮して設計されており、マルチスレッド型のベンチマークでは、性能の向上が観測されています (これについての詳細を紹介している記事については、稿末の参考文献を参照してください)。
本稿では、Linux SMPカーネルに対するハイパー・スレッド処理 (HT) の効果についてのわれわれの調査結果を紹介します。ハイパー・スレッド処理対応のLinux SMPカーネルの性能を、この機能に非対応なカーネルのものと比較します。テストされたシステムは、マルチスレッド処理が可能なシングルCPUのXeonです。今回の調査では、カーネルの中でも、ハイパー・スレッド処理の影響を受ける可能性のある部分、すなわちスケジューラー、低レベルのカーネル・プリミティブ、ファイル・サーバー、ネットワーク、スレッドのサポートなどを対象とするベンチマークを使用しました。
Linuxカーネル2.4.19での結果からは、ハイパー・スレッド処理テクノロジーがマルチスレッド型アプリケーションの性能を30%向上させうることが示されます。現在Linuxカーネル2.5.32に対する変更作業が進められており、それによって、速度性能は51%も向上する可能性があります。
Intelのハイパー・スレッド処理テクノロジーとは、IntelのNetBurstマイクロアーキテクチャー・パイプライン内の資源を複製、区分け (partition)、共有することで、物理的には1個のプロセッサーで2個の論理プロセッサーを実現するものです。
資源の複製では、2個のスレッドをサポートするために、以下の資源がコピーされます。
- CPUごとのアーキテクチャー上のすべての状態
- 命令ポインター、名前変更ロジック
- いくつかの、もっと小さな資源 (リターン・スタック・プレディクターやITLBなど)
資源の区分けでは、実行中のスレッドの間で以下の資源を分割します。
- 各種バッファー (並べ換えバッファー、ロード/ストア・バッファー、キューなど)
資源の共有では、実行中の2個のスレッドの間で、必要に応じて、以下の資源を利用します。
- アウト・オブ・オーダー実行エンジン
- キャッシュ
通常、物理プロセッサーは、それぞれ1個のプロセッサー・コアに1個のアーキテクチャー状態を保持しながら、スレッドへのサービスを行います。HTでは、それぞれの物理プロセッサーが、1個のコアに2個のアーキテクチャー状態を保持することで、それを2個の論理プロセッサーに見せながら、スレッドへのサービスを行います。システムBIOSは、物理プロセッサーのそれぞれのアーキテクチャー状態を個々に記録・管理 (enumerate) します。ハイパー・スレッド処理対応のオペレーティング・システムは、論理プロセッサーを利用しますので、2倍の資源を保持しながら、スレッドへのサービスを行うことになります。
Xeonは、汎用プロセッサーとして同時多重スレッド処理 (SMT: Simultaneous Multi-Threading) を実装した最初のプロセッサーです (プロセッサー・ファミリーXeonの詳細については、参考文献を参照してください)。1個の物理プロセッサーで2個のスレッドを実行するという目的を達成するために、Xeonは、複数のスレッドのコンテキストを同時に管理し、スケジューラーが基本的に独立な2個のスレッドを並行してディスパッチできるようにします。
オペレーティング・システム (OS) は、SMPシステムで行うように、コード・スレッドを各論理プロセッサーにスケジュールし、ディスパッチします。スレッドがディスパッチされない場合には、それに対応する論理プロセッサーはアイドル状態にされます。
スレッドが、論理プロセッサーLP0にスケジュールされ、ディスパッチされる場合、ハイパー・スレッド処理テクノロジーは、そのスレッドを実行するために必要なプロセッサー資源を利用します。
2個目のスレッドが2つ目の論理プロセッサーLP1にスケジュールされ、ディスパッチされると、その2個目のスレッドを実行するために必要な資源が複製、分割、共有されます。各プロセッサーは、パイプライン内の各ポイントで、スレッドの制御と処理を行うためのいろいろな選択を行います。スレッドが終了すると、オペレーティング・システムは、使用されていないプロセッサーをアイドル状態にし、資源を解放して、実行中のプロセッサーがそれを利用できるようにします。
OSは、デュアル・プロセッサー・システムやマルチ・プロセッサー・システムで行うように、スレッドを各論理プロセッサーにスケジュールし、ディスパッチします。システムがスレッドをスケジュールして、パイプラインに送り込むと、2個のスレッドを処理するために必要な資源が利用されます。
Linuxカーネル2.4におけるハイパー・スレッド処理のサポート
Linuxカーネルの下では、2個の仮想的なプロセッサーを備えたハイパー・スレッド処理プロセッサーは、本当の物理プロセッサーが1対存在するかのように扱われます。したがって、SMPをサポートするスケジューラーは、ハイパー・スレッド処理もサポートできるはずです。Linuxカーネル2.4.xがハイパー・スレッド処理をサポートするようになったのは2.4.17からで、以下の機能拡張を行っています。
- 128バイトのロック・アラインメント
- スピン・ウェイト・ループ最適化
- 非実行ベースの遅延ループ
- ハイパー・スレッド処理対応プロセッサーの検出、およびSMPマシンとして論理プロセッサーを始動する機能
- MTRRのシリアライゼーションとマイクロコード更新ドライバー (共有される状態に影響を及ぼす機能)
- システムがアイドル状態にあるときに、論理プロセッサーのスケジューリングよりも物理プロセッサーのスケジューリングを優先させるためのスケジューラーの最適化
- 64Kのエイリアシングを回避するためのオフセット・ユーザー・スタック
Linuxカーネルでのハイパー・スレッド処理の効果を評価するために、HT対応のIntel Xeonプロセッサーを搭載するシステムで、カーネルのベンチマーク性能を測定してみました。ハードウェアは、SMT機能搭載の1.6 GHz Xeon MPプロセッサーのシングルCPU、2.5 GBのRAM、および9.2 GBのSCSIディスク・ドライブ2基としました。測定に使用したカーネルは、標準的なバージョン2.4.19で、SMPが有効になるように構成、構築したものです。カーネルによるハイパー・スレッド処理のサポートは、ブート・オプションで指定しました。ハイパー・スレッド処理を有効にするときにはacpismp=force とし、ハイパー・スレッド処理を無効にするときにはnoht としました。ハイパー・スレッド処理をサポートしているかどうかは、cat /proc/cpuinfo コマンドを使って調べることができ、サポートしていれば、このコマンドは、プロセッサー0とプロセッサー1の2個のプロセッサーを表示します。リスト1 のCPU 0と1にht フラグが立っていることに注意してください。ハイパー・スレッド処理がサポートされていない場合には、プロセッサー0のデータだけが表示されます。
リスト1. ハイパー・スレッド処理をサポートしていることを示すcat /proc/cpuinfoの出力
processor : 0
vendor_id : GenuineIntel
cpu family : 15
model : 1
model name : Intel(R) Genuine CPU 1.60GHz
stepping : 1
cpu MHz : 1600.382
cache size : 256 KB
. . .
fpu : yes
fpu_exception: yes
cpuid level : 2
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr
pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht
tm
bogomips : 3191.60
processor : 1
vendor_id : GenuineIntel
cpu family : 15
model : 1
model name : Intel(R) Genuine CPU 1.60GHz
stepping : 1
cpu MHz : 1600.382
cache size : 256 KB
. . . . .
fpu : yes
fpu_exception: yes
cpuid level : 2
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr
pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht
tm
bogomips : 3198.15
|
Linuxカーネルの性能を測定するために、LMbench、AIM Benchmark Suite IX (AIM9)、chat、dbench、およびtbenchの5種類のベンチマークを使用しました。LMbenchベンチマークは、基本的なシステム・コール、コンテキスト・スイッチングの待ち時間、メモリーのバンド幅など、さまざまなLinuxアプリケーション・プログラミング・インターフェース (API) の時間計測を行います。AIM9ベンチマークは、ユーザー・アプリケーションのワークロードを測定します。chatベンチマークは、チャット・ルームにならってモデル化されたクライアント/サーバーのワークロードです。dbenchベンチマークは、ファイル・サーバーのワークロード、tbenchは、TCPのワークロードです。Chat、dbench、tbenchはマルチスレッド型のベンチマークであり、それ以外はシングル・スレッド型のベンチマークです。
LinuxのAPIに対するハイパー・スレッド処理の効果は、LMbenchで測定しました。これは、バンド幅と待ち時間を測定するテスト・スイートからなるマイクロベンチマークです。この中には、キャッシュ経由のファイル読み出し、メモリー・コピー (bcopy)、メモリーの読み書き (および待ち時間)、パイプ、コンテキスト・スイッチング、ネットワーク処理、ファイルシステムの作成と削除、プロセス生成、シグナル処理、およびプロセッサー・クロックの待ち時間が含まれます。LMbenchは、スケジューラー、プロセス管理、通信、ネットワーク処理、メモリー・マップおよびファイルシステムといったカーネル・コンポーネントの性能を調べるためのものです。低レベルのカーネル・プリミティブは、その下部にあるハードウェアの機能や性能を示す良い指標となります。
ハイパー・スレッド処理の効果を調べるために、メッセージ制御にかかる時間 (すなわち、システムがある命令をどの程度高速に実行できるか) を表す待ち時間の測定値に焦点を当てました。待ち時間は、命令ごとにマイクロ秒単位で示されます。
表1は、LMbenchでテストされたカーネル機能の一部を示したものです。各データ値は、3回の実行結果を平均したものです。データは、収斂し、同じテスト環境で実行された場合に再現可能なものであることを保証できるかどうかがテストされました。一般に、これらの機能がシングル・スレッドとして実行される場合、ハイパー・スレッド処理を有効にしたときと無効にしたときとで性能に差はありません。しかし、パイプの待ち時間のテストやプロセス待ち時間の3つのテストなど、スレッドを2個実行しなければならない場合には、ハイパー・スレッド処理を有効にすると、待ち時間の性能が低下するようです。ここでは、SMPが有効となるように構成された標準カーネルを2419sで表しています。ハイパー・スレッド処理をサポートしない構成にしたカーネルは、2419s-nohtで表しています。ハイパー・スレッド処理をサポートしているカーネルは、2419s-htと表しています。
表1. LinuxのAPIに対するハイパー・スレッド処理の効果
| カーネルの機能 | 2419s-noht | 2419s-ht | 速度の向上 |
|---|---|---|---|
| 単純なsyscall | 1.10 | 1.10 | 0% |
| 単純なread | 1.49 | 1.49 | 0% |
| 単純なwrite | 1.40 | 1.40 | 0% |
| 単純なstat | 5.12 | 5.14 | 0% |
| 単純なfstat | 1.50 | 1.50 | 0% |
| 単純なopen/close | 7.38 | 7.38 | 0% |
| 10個のfdの中からの選択 | 5.41 | 5.41 | 0% |
| 10個のtcp fdの中からの選択 | 5.69 | 5.70 | 0% |
| シグナル・ハンドラーのインストール | 1.56 | 1.55 | 0% |
| シグナル・ハンドラーのオーバーヘッド | 4.29 | 4.27 | 0% |
| パイプの待ち時間 | 11.16 | 11.31 | -1% |
| プロセスのfork+exit | 190.75 | 198.84 | -4% |
| プロセスのfork+execve | 581.55 | 617.11 | -6% |
| プロセスのfork+/bin/sh -c | 3051.28 | 3118.08 | -2% |
| 注意: データの単位はマイクロ秒。小さい値ほど性能が良いことを意味する。 | |||
パイプの待ち時間テストは、UNIXのパイプを介して通信を行う2個のプロセスを使用して、ソケット経由でのプロセス間通信の待ち時間を測定します。このベンチマークは、2個のプロセスの間でトークンをやりとりします。性能の低下は1%で、無視できるほどのものです。
3つのプロセス・テストでは、Linux下でのプロセスの生成と実行が行われます。これは、基本的な実験用スレッドを生成するのに要する時間を測定するためのテストです。プロセスのfork+exitのテストのデータは、1個のプロセスを2個の (ほとんど) 同一のコピーに分割し、一方を終了させるのに要する待ち時間を表します。これは、新しいプロセスを生成するための方法ですが、両方のプロセスが同じことを行うわけですから、あまり有用な方法とは言えません。このテストでは、ハイパー・スレッド処理は、性能を4%低下させています。
プロセスのfork+execveのデータは、新しいプロセスを生成し、その新しいプロセスで新しいプログラムを実行させるのに要する時間を表します。これは、すべてのシェル (コマンド・インタープリター) の内部ループで行われていることです。このテストでは、ハイパー・スレッド処理を有効にすることで、性能が6%低下しています。
プロセスのfork+/bin/sh -cのテストのデータは、新しいプロセスを生成し、その新しいプロセスで新しいプログラムを実行させるときに、システム・シェルにプログラムを見つけ出させて実行させる場合に要する時間を表します。これは、systemというCのライブラリー・インターフェースが実装している方法です。これは、最も一般的に利用されている呼び出しであるとともに、最も高価な呼び出しでもあります。ハイパー・スレッド処理を有効にした場合、無効にした場合に比べ、このテストの実行速度は、2%遅くなります。
Linuxのシングル・ユーザー・アプリケーション・ワークロードに対するハイパー・スレッド処理の効果
ベンチマークAIM9は、ハードウェアとオペレーティング・システムの性能を測定するために設計されたシングル・ユーザーのワークロードです。結果は、表2のとおりです。このベンチマークのテストは、各種の同期的ファイル操作と整数のふるい分け (Integer Sieves) を除き、ほとんどが、ハイパー・スレッド処理を有効にしたときと無効にしたときとで、同じ性能を示しました。ランダムな同期的ディスク書き込み (Sync Random Disk Writes)、シーケンシャルな同期的ディスク書き込み (Sync Sequential Disk Writes)、および同期的ディスク・コピー (Sync Disk Copies) の3つの操作は、ハイパー・スレッド処理を有効にした場合、約35%遅くなっています。他方、整数のふるい分けでは、ハイパー・スレッド処理を有効にしたときのほうが、無効にしたときよりも速度を60%向上させています。
表2. AIM9のワークロードに対するハイパー・スレッド処理の効果
| 2419s-noht | 2419s-ht | 速度の向上 | ||
|---|---|---|---|---|
| add_double | 1秒あたりの倍精度加算の回数 (単位は1,000回) | 638361 | 637724 | 0% |
| add_float | 1秒あたりの単精度加算の回数 (単位は1,000回) | 638400 | 637762 | 0% |
| add_long | 1秒あたりの長整数 (Long Integer) 加算の回数 (単位は1,000回) | 1479041 | 1479041 | 0% |
| add_int | 1秒あたりの整数 (Integer) 加算の回数 (単位は1,000回) | 1483549 | 1491017 | 1% |
| add_short | 1秒あたりの短整数 (Short Integer) 加算の回数 (単位は1,000回) | 1480800 | 1478400 | 0% |
| creat-clo | 1秒あたりのファイルの生成/クローズの回数 | 129100 | 139700 | 8% |
| page_test | 1秒あたりのSystem Allocations & Pagesの回数 | 161330 | 161840 | 0% |
| brk_test | 1秒あたりのシステム・メモリー割り当て (System Memory Allocations) の回数 | 633466 | 635800 | 0% |
| jmp_test | 1秒あたりのローカルでないgotoの回数 | 8666900 | 8694800 | 0% |
| signal_test | 1秒あたりのシグナル・トラップの回数 | 142300 | 142900 | 0% |
| exec_test | 1秒あたりのプログラム・ロードの回数 | 387 | 387 | 0% |
| fork_test | 1秒あたりのタスク生成回数 | 2365 | 2447 | 3% |
| link_test | 1秒あたりのLink/Unlink対 (つい) の回数 | 54142 | 59169 | 9% |
| disk_rr | 1秒あたりのランダムなディスク読み出しの回数 (K) | 85758 | 89510 | 4% |
| disk_rw | 1秒あたりのランダムなディスク書き込みの回数 (K) | 76800 | 78455 | 2% |
| disk_rd | 1秒あたりのシーケンシャルなディスク読み出しの回数 (K) | 351904 | 356864 | 1% |
| disk_wrt | 1秒あたりのシーケンシャルなディスク書き込みの回数 (K) | 154112 | 156359 | 1% |
| disk_cp | 1秒あたりののディスク・コピーの回数 (K) | 104343 | 106283 | 2% |
| sync_disk_rw | 1秒あたりのランダムな同期的ディスク書き込みの回数 (K) | 239 | 155 | -35% |
| sync_disk_wrt | 1秒あたりのシーケンシャルな同期的ディスク書き込みの回数 (K) | 97 | 60 | -38% |
| sync_disk_cp | 1秒あたりの同期的ディスク・コピーの回数 (K) | 97 | 60 | -38% |
| disk_src | 1秒あたりのディレクトリー検索の回数 | 48915 | 48195 | -1% |
| div_double | 1秒あたりの倍精度除算の回数 (単位は1,000回) | 37162 | 37202 | 0% |
| div_float | 1秒あたりの単精度除算の回数 (単位は1,000回) | 37125 | 37202 | 0% |
| div_long | 1秒あたりの長整数 (Long Integer) 除算の回数 (単位は1,000回) | 27305 | 27360 | 0% |
| div_int | 1秒あたりの整数 (Integer) 除算の回数 (単位は1,000回) | 27305 | 27332 | 0% |
| div_short | 1秒あたりの短整数 (Short Integer) 除算の回数 (単位は1,000回) | 27305 | 27360 | 0% |
| fun_cal | 1秒あたりのの関数呼び出し (引数なし) の回数 | 30331268 | 30105600 | -1% |
| fun_cal1 | 1秒あたりの関数呼び出し (引数1個) の回数 | 112435200 | 112844800 | 0% |
| fun_cal2 | 1秒あたりの関数呼び出し (引数2個) の回数 | 97587200 | 97843200 | 0% |
| fun_cal15 | 1秒あたりの関数呼び出し (引数15個) の回数 | 44748800 | 44800000 | 0% |
| sieve | 1秒あたりの整数ふるい分け (Integer Sieves) の回数 | 15 | 24 | 60% |
| mul_double | 1秒あたりの倍精度乗算の回数 (単位は1,000回) | 456287 | 456743 | 0% |
| mul_float | 1秒あたりの単精度乗算の回数 (単位は1,000回) | 456000 | 456743 | 0% |
| mul_long | 1秒あたりの長整数 (Long Integer) 乗算の回数 (単位は1,000回) | 167904 | 168168 | 0% |
| mul_int | 1秒あたりの整数 (Integer) 乗算の回数 (単位は1,000回) | 167976 | 168216 | 0% |
| mul_short | 1秒あたりの短整数 (Short Integer) 乗算の回数 (単位は1,000回) | 155730 | 155910 | 0% |
| num_rtns_1 | 1秒あたりの数値関数の回数 | 92740 | 92920 | 0% |
| trig_rtns | 1秒あたりの三角関数の回数 | 404000 | 405000 | 0% |
| matrix_rtns | 1秒あたりの点変換 (Point Transformations) の回数 | 875140 | 891300 | 2% |
| array_rtns | 1秒あたりの解の得られた線型システム (Linear Systems Solved) の個数 | 579 | 578 | 0% |
| string_rtns | 1秒あたりの文字列操作の回数 | 2560 | 2564 | 0% |
| mem_rtns_1 | 1秒あたりの動的メモリー操作の回数 | 982035 | 980019 | 0% |
| mem_rtns_2 | 1秒あたりのブロック・メモリー操作の回数 | 214590 | 215390 | 0% |
| sort_rtns_1 | 1秒あたりの並べ換え処理の回数 | 481 | 472 | -2% |
| misc_rtns_1 | 1秒あたりの補助ループ (Auxiliary Loops) の回数 | 7916 | 7864 | -1% |
| dir_rtns_1 | 1秒あたりのディレクトリー操作の回数 | 2002000 | 2001000 | 0% |
| shell_rtns_1 | 1秒あたりのシェル・スクリプト数 | 95 | 97 | 2% |
| shell_rtns_2 | 1秒あたりのシェル・スクリプト数 | 95 | 96 | 1% |
| shell_rtns_3 | 1秒あたりのシェル・スクリプト数 | 95 | 97 | 2% |
| series_1 | 1秒あたりの級数計算 (Series Evaluations) の回数 | 3165270 | 3189630 | 1% |
| shared_memory | 1秒あたりの共有メモリー操作の回数 | 174080 | 174220 | 0% |
| tcp_test | 1秒あたりのTCP/IPメッセージ数 | 65835 | 66231 | 1% |
| udp_test | 1秒あたりのUDP/IPデータグラム数 | 111880 | 112150 | 0% |
| fifo_test | 1秒あたりのFIFOメッセージ数 | 228920 | 228900 | 0% |
| stream_pipe | 1秒あたりのストリーム・パイプ・メッセージ数 | 170210 | 171060 | 0% |
| dgram_pipe | 1秒あたりのデータグラム・パイプ・メッセージ数 | 168310 | 170560 | 1% |
| pipe_cpy | 1秒あたりのパイプ・メッセージ数 | 245090 | 243440 | -1% |
| ram_copy | 1秒あたりのメモリー間コピー | 490026708 | 492478668 | 1% |
Linuxのマルチスレッド型アプリケーション・ワークロードに対するハイパー・スレッド処理の効果
Linuxのマルチスレッド型アプリケーションに対するハイパー・スレッド処理の効果を測定するためには、チャット・ルームにならってモデル化されたchatベンチマークを使用します。このベンチマークには、クライアントとサーバーの両方が関係してきます。ベンチマークのクライアント側は、1秒あたりに送られてくるメッセージの数を報告します。チャット・ルームとメッセージの数によってワークロードが制御されます。このワークロードは、大量のスレッドとTCP/IP接続を生成し、大量のメッセージを送受信します。デフォルトのパラメーターは、以下のとおりです。
- チャット・ルームの数 = 10
- メッセージの数 = 100
- メッセージのサイズ = 100バイト
- ユーザーの数 = 20
デフォルトでは、各チャット・ルームのユーザー数は20人です。全部でチャット・ルームの数は10ですので、ユーザー数は20x10 = 200人になります。チャット・ルームのユーザーそれぞれについて、クライアントがサーバーに接続します。ユーザー数は200ですので、サーバーへの接続数は200になります。チャット・ルームの各ユーザー (すなわち、接続) について、「送信」スレッドと「受信」スレッドが生成されますので、チャット・ルーム数が10の場合、10x20x2 = 400個のクライアント・スレッドと400個のサーバー・スレッドが生成されることになり、合計で800個のスレッドが生成されることになります。それだけではありません。
各クライアントの「送信」スレッドは、指定された数のメッセージをサーバーに送信します。チャット・ルーム数が10で、メッセージ数が100の場合、クライアントは、10x20x100 = 20,000個のメッセージを送信することになります。サーバーの「受信」スレッドは、それと同じ数のメッセージを受信することになります。チャット・ルームのサーバーは、それら1個1個のメッセージを、同じチャット・ルームの他のユーザーにエコー・バックします。したがって、チャット・ルーム数10、メッセージ数100の場合、サーバーの「送信」スレッドは、10x20x100x19すなわち380,000個のメッセージを送信することになります。クライアントの「受信」スレッドは、それと同じ数のメッセージを受信することになります。
テストは、コマンド・ライン・セッションでチャット・サーバーを始動し、また別のコマンド・ライン・セッションでクライアントを始動することで、開始されます。クライアントがワークロードをシミュレートし、その結果は、クライアントから送信されたメッセージの数を表します。クライアントがテストを終了すると、サーバーはループし、クライアントからの別の開始メッセージを受け付けます。今回の測定では、チャット・ルームの数を20、30、40、50にしてベンチマークを実行しました。表3には、それに対応する接続数およびスレッド数を示してあります。
表3. テストされたチャット・ルーム数およびスレッド数
| チャット・ルームの数 | 接続の数 | スレッドの数 | 送信されたメッセージの数 | 受信されたメッセージの数 | メッセージの総数 |
|---|---|---|---|---|---|
| 20 | 400 | 1,600 | 40,000 | 760,000 | 800,000 |
| 30 | 600 | 2,400 | 60,000 | 1,140,000 | 1,200,000 |
| 40 | 800 | 3,200 | 80,000 | 1,520,000 | 1,600,000 |
| 50 | 1000 | 4,000 | 100,000 | 1,900,000 | 2,000,000 |
表4は、chatのワークロードに対するハイパー・スレッド処理による性能的効果を示しています。各データ値は、5回の実行の幾何平均を表しています。これらのデータから、チャット・ルームの数によって違いはありますが、ハイパー・スレッド処理がワークロードのスループットを22%から28%向上させていることがはっきりとわかります。全体として、ハイパー・スレッド処理は、4通りのチャット・ルーム数のサンプルの幾何平均で、チャット性能を24%を向上させています。
表4. チャットのスループットに対するハイパー・スレッド処理の効果
| チャット・ルームの数 | 2419s-noht | 2419s-ht | 速度の向上 |
|---|---|---|---|
| 20 | 164,071 | 202,809 | 24% |
| 30 | 151,530 | 184,803 | 22% |
| 40 | 140,301 | 171,187 | 22% |
| 50 | 123,842 | 158,543 | 28% |
| 幾何平均 | 144,167 | 178,589 | 24% |
| 注意: データは、クライアントから送信されたメッセージ数: 大きい値ほど性能が良いことを意味する。 | |||
図1. チャットのワークロードに対するハイパー・スレッド処理の効果
Linuxのマルチスレッド型ファイル・サーバー・ワークロードに対するハイパー・スレッド処理の効果
ファイル・サーバーに対するハイパー・スレッド処理の効果は、dbenchおよびそれと同類のテストtbenchで測定しました。dbenchは、Ziff-Davis Mediaベンチマーク・プログラムに含まれている、よく知られているNetBenchベンチマークと同様のテストで、クライアントからのネットワーク・ファイル・リクエストを処理する場合のファイル・サーバーの性能を測定するためのものです。ただし、NetBenchが実際の物理的なクライアントを綿密にセットアップする必要があるのに対して、dbenchは、client.txtという4 Mバイトのファイルを見つけ出し、通常NetBenchクライアントで実行される命令90,000個ぶんのワークロードを発生させることで、それをシミュレートします。このファイルには、SMBopenx、SMBclose、SMBwritebraw、SMBgetatrなどのファイル操作指令が書き込まれています。これらのI/O呼び出しは、SAMBAのSMBDサーバーがnetbenchの実行の際に発生させるServer Message Protocol Block (SMB) に対応しています。SMBプロトコルは、Microsoft Windows 3.11、NTおよび95/98で、ディスクやプリンターを共有するために使用されているものです。
われわれのテストでは、ファイルのオープン、読み出し、書き込み、ロック、アンロック、ファイル属性の取得、ファイル属性の設定、クローズ、ディスクの空きスペースの獲得、ファイルの時刻の取得、ファイルの時刻の設定、検索オープン (find open)、次の検索 (find next)、検索クローズ (find close)、ファイル名の変更、ファイルの削除、ファイルの新規作成、ファイル・バッファーのフラッシュの合計18種類のI/O呼び出しを使用しました。
dbenchでは、物理的なセットアップに手間をかけることなく、任意の数のクライアントをシミュレートすることができます。dbenchは、ファイルシステムの負荷を発生させるだけで、ネットワーク呼び出しを行うことはありません。実行中、各クライアントは、転送されたデータのバイト数を記録し、この値をデータの転送に要した時間で割った値を求めます。そして、すべてのクライアントのスループット得点を合計し、サーバーの全体的なスループットを決定します。全体的なI/Oのスループット得点は、テスト中に転送された1秒あたりのMバイト数を表します。これは、サーバーがクライアントからのファイル・リクエストをどれだけ効率よく処理できるかの測定値となります。
dbenchは、CPUおよびI/Oスケジューラーに対して高い負荷と処理量を生成しますので、ハイパー・スレッド処理を評価するのに適したテストです。dbenchでは、クライアントによって数多くのファイルが同時に作成されたり、アクセスされますので、マルチスレッド型ファイル・サーバー機能をサポートするハイパー・スレッド処理の能力が厳格にテストされます。各クライアントは、約21 Mバイトぶんのテスト・データ・ファイルを作成します。クライアント数が20のテスト・ランでは、約420 Mバイトのデータが作成されることになります。dbenchは、Linuxのファイルシステムで使用されているエレベーター・アルゴリズムの性能を測定するのに適したテストだと考えることができます。dbenchは、このアルゴリズムの稼働時の有効性およびエレベーターが充分強力な働きをしているかどうかをテストするために使用されます。また、ページ交換 (page replacement) の性能を調べるのにも面白いテストです。
表5は、dbenchのワークロードに対するHTの効果を示しています。各データ値は、5回の実行の幾何平均を表しています。これらのデータから、ハイパー・スレッド処理が、dbenchの性能を最低9%、最大29%改善していることがわかります。全体的な改善は、5回のテストの幾何平均で18%です。
表5. dbenchのスループットに対するハイパー・スレッド処理の効果
| クライアントの数 | 2419s-noht | 2419s-ht | 速度の向上 |
|---|---|---|---|
| 20 | 132.82 | 171.23 | 29% |
| 30 | 131.43 | 169.55 | 29% |
| 60 | 119.95 | 133.77 | 12% |
| 90 | 111.89 | 121.81 | 9% |
| 120 | 99.31 | 114.92 | 16% |
| 幾何平均 | 118.4 | 140.3 | 18% |
| 注意: データは、Mバイト/秒を単位としてのスループット: 大きい値ほど性能が良いことを意味する。 | |||
図2. dbenchのワークロードに対するハイパー・スレッド処理の効果
tbenchも、dbenchと同様のファイル・サーバーのワークロードです。ただし、tbenchが発生させるのは、TCPとプロセスの負荷だけです。tbenchは、netbenchの負荷でSMBDが行うのと同じソケット呼び出しを行いますが、tbenchは、ファイルシステムの呼び出しは行いません。tbenchの思想は、SMBDのコードを高速化できることが前提であるかのように、netbenchのテストからSMBDを省くことにあります。tbenchのスループットの結果は、ファイルシステムI/OやSMBパケット処理をすべて省いてやると、netbenchの実行がいかに高速になるかを示しています。tbenchは、dbenchパッケージの一部として構築されています。
表6は、tbenchのワークロードに対するハイパー・スレッド処理の効果を示しています。先と同様、各データ値は、5回の実行の幾何平均を表しています。ハイパー・スレッド処理がtbenchのスループットを22%~31%改善することは明らかです。全体的な改善は5回のテストの幾何平均で27%です。
表6. tbenchのスループットに対するハイパー・スレッド処理の効果
| クライアントの数 | 2419s-noht | 2419s-ht | 速度の向上 |
|---|---|---|---|
| 20 | 60.98 | 79.86 | 31% |
| 30 | 59.94 | 77.82 | 30% |
| 60 | 55.85 | 70.19 | 26% |
| 90 | 48.45 | 58.88 | 22% |
| 120 | 37.85 | 47.92 | 27% |
| 幾何平均 | 51.84 | 65.77 | 27% |
| 注意: データは、Mバイト/秒を単位としてのスループット: 大きい値ほど性能が良いことを意味する。 | |||
図3. tbenchのワークロードに対するハイパー・スレッド処理の効果
Linuxカーネル2.5.xでのハイパー・スレッド処理のサポート
Linuxカーネル2.4.xは、リリース2.4.17以降でHTに対応するようになりました。カーネル2.4.17は、論理プロセッサーを認識し、ハイパー・スレッド処理プロセッサーを2個の物理プロセッサーとして扱います。しかし、標準カーネル2.4.xに採用されているスケジューラーは、2個の論理プロセッサーと2個の別個の物理プロセッサーの間の資源の衝突の問題を区別できていないという点で、まだ洗練されていないと考えられています。
Ingo Molnarは、現在のスケジューラーが正しい判断をできない状況を指摘しています (リンク先については参考文献参照)。物理的なCPUを2個装備するシステムがあり、それぞれのCPUでは2個の仮想プロセッサーが使えるものとします。実行中のタスクが2個ある場合、現在のスケジューラーは、一方のプロセスを別の物理的なCPUに移行させたほうが、はるかに高い性能が得られるようになる場合でも、両方のタスクを1個の物理プロセッサーで実行させようとします。また、このスケジューラーは、1個の仮想プロセッサーからその同胞 (同一の物理的CPU上の別の論理CPU) にプロセスを移行させたほうが、物理的プロセッサー間でそれを移行させるよりも (キャッシュ・ロードの関係から) 安価であることを理解していません。
これを解決するには、実行キューの処理方法を変更する必要があります。2.5のスケジューラーは、プロセッサーごとに実行キューを1個ずつ管理し、キューの間でのタスクの移動を回避しようとしています。しかし、変更すべきなのは、物理プロセッサーごとに実行キューを1個ずつ設け、すべての仮想プロセッサーにタスクを振り分けることができるようにするという点です。なぜアイドルCPU (すべての仮想プロセッサーがアイドルになっている) が出てくるのかについてのもっと明晰な判断を導入すれば、ハイパー・スレッド処理システムでのスケジューリングの必要性を「奇麗に満たしてくれる」コードになるはずです。
2.5のスケジューラーの実行キューに変更を加えることの他にも、HTを利用してLinuxカーネルの性能が最適なものとなるようにするための変更も必要です。これらの変更点についてのMolnarの論は、以下のとおりです (詳しくは、参考文献を参照してください)。
-
HT対応の受動的なロード・バランシング:
IRQ駆動のバランシングは、論理CPUごとにではなく、物理CPUごとに行うべきです。そうしないと、一方の物理CPUがタスクを何も実行していないのに、もう一方の物理CPUが2個のタスクを実行するということが起こり得ます。標準のスケジューラーは、この状態を「不均衡 (imbalance)」と認識していません。スケジューラーからは、最初の2個のCPUが1-1のタスクを実行させ、2つ目の2個のCPUが0-0のタスクを実行させているかのように見えます。標準のスケジューラーは、2個の論理CPUが同じ物理CPUに属していることを理解していません。
-
「能動的な」ロード・バランシング:
これは、ある論理CPUがアイドルとなり、物理CPUを不均衡にさせる場合です。これは、もともと標準の1:1スケジューラーには存在しないメカニズムです。アイドルCPUによって招かれる不均衡は、通常のロード・バランサーによって解決することができます。HTの場合、元の物理CPUが実行可能な2個のタスクを実行させている可能性がありますので、これは特別な状況です。実行中のタスクを移行させるのは難しいため、これは、標準のロード・バランサーでは処理することのできない状況です。この移行は不可欠です。そうしないと、一方の物理CPUは2個のタスクを実行させ続け、他方の物理CPUはアイドルのままであり続けるということが起こり得ます。
-
HT対応のタスク選択:
スケジューラーが新しいタスクを選択する場合、他のCPUからタスクを引き抜いてくる前に、同じ物理CPUを共有するタスクのほうを優先させるべきです。標準のスケジューラーは、その論理CPUにスケジュールされていたタスクだけを選択するようになっています。
-
HT対応の親和性:
タスクは、論理CPUにではなく、物理CPUに「結合」しようとすべきです。
-
HT対応のウェイクアップ:
標準のスケジューラーは、「現在の」CPUを認識するだけで、その同胞は認識しません。HTでは、すでにタスクを実行している論理CPU上でスレッドがウェイクアップされ、かつ同胞のCPUがアイドルである場合、同胞のCPUがウェイクアップされ、新たにウェイクアップされたタスクを即座に実行しなければなりません。
本稿執筆時点で、Molnarは、実行キューを共有する (複数のCPUが同じ実行キューを共有できる) という概念を導入することで、標準カーネル2.5.32に対して上のすべての変更内容を実装するパッチを提供しています。物理CPUごとに実行キューを共有することで、上記のHTのスケジューリングに関するニーズをすべて実現しています。当然ながら、これによってスケジューリングやロード・バランシングは複雑なものとなり、SMPや単一プロセッサーのスケジューラーに対する効果は、まだ、わかっていません。
Linuxカーネル2.5.32での変更内容は、XeonシステムでCPUを2個以上使用する場合に、とくにロード・バランシングやスレッド親和性の面での効果を期待して設計されたものです。われわれは、ハードウェア資源の制約から、CPU 1個のテスト環境でしか、その効果を測定することができませんでした。2.4.19で使用したのと同じテスト・プロセスを使用して、2.5.32でchat、dbench、tbenchの3つのワークロードを実行してみました。chatについては、チャット・ルーム数40の場合で、60%もの速度向上を得ることができました。全体的な改善は、約45%でした。dbenchについては、高いほうで27%の速度向上となり、全体的な改善は約12%でした。tbenchについては、全体的な改善が約35%でした。
表7. Linuxカーネル2.5.32に対するハイパー・スレッド処理の効果
| ワークロードchat | |||
|---|---|---|---|
| チャット・ルームの数 | 2532s-noht | 2532s-ht | 速度の向上 |
| 20 | 137,792 | 207,788 | 51% |
| 30 | 138,832 | 195,765 | 41% |
| 40 | 144,454 | 231,509 | 47% |
| 50 | 137,745 | 191,834 | 39% |
| 幾何平均 | 139,678 | 202,034 | 45% |
| ワークロードdbench | |||
| クライアントの数 | 2532s-noht | 2532s-ht | 速度の向上 |
| 20 | 142.02 | 180.87 | 27% |
| 30 | 129.63 | 141.19 | 9% |
| 60 | 84.76 | 86.02 | 1% |
| 90 | 67.89 | 70.37 | 4% |
| 120 | 57.44 | 70.59 | 23% |
| 幾何平均 | 90.54 | 101.76 | 12% |
| ワークロードtbench | |||
| クライアントの数 | 2532s-noht | 2532s-ht | 速度の向上 |
| 20 | 60.28 | 82.23 | 36% |
| 30 | 60.12 | 81.72 | 36% |
| 60 | 59.73 | 81.2 | 36% |
| 90 | 59.71 | 80.79 | 35% |
| 120 | 59.73 | 79.45 | 33% |
| 幾何平均 | 59.91 | 81.07 | 35% |
| 注意: chatのデータは、クライアントから送信される1秒あたりのメッセージ数。dbenchとtbenchのデータの単位はMバイト/秒。 | |||
Intel Xeonのハイパー・スレッド処理は、明らかに、Linuxカーネルおよびマルチスレッド型アプリケーションに好ましい効果を与えています。ハイパー・スレッド処理による速度の向上は、標準カーネル2.4.19で30%にもなり、カーネル2.5.32では、スケジューラーでの実行キューのサポートおよびハイパー・スレッド処理への対応といった大きな変更を行ったことから、51%に達しました。
- chatベンチマークは、Linuxベンチマーク・スイートのホームページからダウンロードできます。
- LMbenchについては、LMbenchのホームページに詳しい説明があります。
-
Linuxのエレベーター・アルゴリズム は、Linux Weekly News のKernel Developmentセクション、2000年11月23日号で紹介されています。
- カーネル・リストにIngo Molnarが寄稿したハイパー・スレッド処理についての2002年8月 の文書は、Linux Weekly Newsに再掲されています。
- 別の2002年8月 のLWNの記事でも、スケジューラーとハイパー・スレッド処理 (など) が取り上げられています。
- 筆者は、2002年8月にサンフランシスコで開催されたLinuxWorldコンファレンスでのセッションPerformance tuning for threaded applications -- with a look at Hyper-Threading で貴重な情報を収集できたことについて、IntelのSunil Saxenaにも謝意を表したいと思います。
-
developerWorks のLinuxゾーンには、他にもLinux 開発者向けの参考文献が多数掲載されています。