簡単に言うと、CPUアフィニティーとは、プロセスが他のプロセッサーに移動せずに、できるだけ長く特定のCPUで実行する傾向のことです。Linuxカーネル・プロセス・スケジューラーは、本質的に、一般にソフトCPUアフィニティーと呼ばれるものを強制します。これは、プロセスが一般に、プロセッサー間を頻繁に移動しないことを意味します。これは望ましい状態です。あまり移動しないプロセスは、オーバーヘッドが少ないからです。
2.6 Linuxカーネルは、開発者がハードCPUアフィニティーをプログラムによって実施できるメカニズムも備えています。これは、特定のプロセスを実行するプロセッサー(またはプロセッサーのセット)をアプリケーションが明示的に指定できることを意味します。
Linuxカーネルでは、すべてのプロセスにtask_structというデータ構造体が関連付けられます。この構造体はさまざまな理由から重要ですが、最も重要なのはcpus_allowedビットマスクです。このビットマスクは、システム内のn個の論理プロセッサーのそれぞれについて1ビットずつ、一連のnビットで構成されます。4つの物理CPUを持つシステムであれば、4ビットになります。それらのCPUがハイパースレッド対応の場合は、8ビットのビットマスクになります。
特定のビットが特定のプロセスに設定されると、そのプロセスはそれに対応するCPUで実行することができます。したがって、プロセスが任意のCPUで実行することが許され、必要に応じてプロセッサー間を移動することが許される場合、ビットマスクはすべてが1になります。実際には、これがLinuxでのプロセスのデフォルト状態です。
LinuxカーネルAPIには、ユーザーがビットマスクを変更したり、現在のビットマスクを確認するための手段が含まれています。
- sched_get_affinity() (for viewing the current bitmask)
- sched_set_affinity() (現在のビットマスクの確認)
cpu_affinityは子スレッドに渡されるので、適切にsched_set_affiityを呼び出す必要があります。
通常、Linuxカーネルは、プロセスがどこで実行するか、効率的にスケジューリングします(すなわち、使用可能なプロセッサーで実行して、全体として良好なパフォーマンスを達成します)。カーネルは、CPU間の作業負荷の不均衡を検出して、負荷の少ないプロセッサーにプロセスを移動するアルゴリズムを備えています。
経験則として、アプリケーションではスケジューラーのデフォルトの動作を使用するだけでよいでしょう。しかし、これらのデフォルト動作を変更して、パフォーマンスを最適化した方がよい場合があります。ハード・アフィニティーを使用する3つの理由を見てみましょう。
科学計算や学術研究でのコンピューティングでは、直感や予感に基づくシナリオは珍しいことではなく、おそらく民間部門のコンピューティングでも同様です。アプリケーションがマルチプロセッサー・マシンの大量の計算時間を消費する必要があると直感的にわかる場合があります。
複雑なソフトウェアのテストは、カーネル・アフィニティー・テクノロジーに興味を持つべきもう1つの理由です。リニア・スケーラビリティ・テストが必要なアプリケーションがあるとします。一部の製品では、投入するハードウェアが多いほどパフォーマンスが上がるという決まり文句が使われます。
単に複数のマシン(プロセッサー構成ごとに1台のマシン)を購入するのではなく、次のようにすることができます。
- マルチプロセッサー・マシンを1台購入する
- プロセッサーを1つずつ増やしていく
- 1秒間のトランザクション数を測定する
- 結果としてのスケーラビリティーをグラフにする
アプリケーションのパフォーマンスがCPUの増設につれて本当にリニアに増す場合、1秒間のトランザクション数とCPUの数は直線関係になるはずです(まっすぐな対角線のグラフなど ? 次の段落を参照)。このように動作をモデル化すると、土台となっているハードウェアをアプリケーションが効率的に使用できるかどうかがわかります。
アムダールの法則は、現実には、おそらくこのようにならないが、それに近くなることを示しています。一般的なケースでは、すべてのプログラムに逐次的な構成要素があると考えることができます。問題のセットが大きくなるほど、逐次的構成要素が最適解決時間の上限を高くすることになります。
アムダールの法則は、CPUキャッシュのヒット率を高く保ちたいときに特に重要です。特定のプロセスが移動されると、CPUキャッシュのメリットはなくなります。事実、使用しているCPUが特定のデータをキャッシュする必要がある場合、他のすべてのCPUは自分自身のキャッシュにある(そのデータの)エントリーを無効にします。
したがって、複数のスレッドが同じデータを必要とする場合は、特定のCPUにバインドして、それらのスレッドすべてがキャッシュ・データにアクセスできるようにする(または、少なくとも、キャッシュ・ヒットの確率が高くなるようにする)のは意味のあることです。さもなければ、各スレッドが異なるCPUで実行して、常に互いのキャッシュ・エントリーを無効にすることになります。
CPUアフィニティーに興味を持つべき最後の理由は、リアルタイム(時間依存の)プロセスです。たとえば、8wayマシン上の1つのプロセッサーを指定して、システムの通常のスケジューリング・ニーズのすべてを他の7つのプロセッサーで処理できるようにするには、ハード・アフィニティーを使用した方がよいでしょう。このようにすると、時間のかかる時間依存のアプリケーションを実行することができ、また、他のアプリケーションは残りの計算リソースを独占することができます。
次のサンプル・アプリケーションで、この仕組みを示します。
Linuxシステムを非常に忙しくさせるプログラムを工夫してみましょう。このプログラムは、すでに述べたシステム・コールと、システム上のプロセッサー数を示す他のいくつかのAPIを使用して作成できます。基本的に、システムの各プロセッサーを数秒間ビジーにするプログラムを書くことが目的です。下記の「ダウンロード」セクションからサンプル・アプリケーションをダウンロードしてください。
リスト1.プロセッサーをビジーにする
/* This method will create threads, then bind each to its own cpu. */
bool do_cpu_stress(int numthreads)
{
int ret = TRUE;
int created_thread = 0;
/* We need a thread for each cpu we have... */
while ( created_thread < numthreads - 1 )
{
int mypid = fork();
if (mypid == 0) /* Child process */
{
printf("\tCreating Child Thread: #%i\n", created_thread);
break;
}
else /* Only parent executes this */
{
/* Continue looping until we spawned enough threads! */ ;
created_thread++;
}
}
/* NOTE: All threads execute code from here down! */
|
ご覧のように、このコードは分岐生成によって一群のスレッドを作成するだけです。それぞれがメソッドの残りのコードを実行します。では、各スレッドにそれぞれのCPUとのアフィニティーを設定しましょう。
リスト2.各スレッドのCPUアフィニティーを設定する
cpu_set_t mask;
/* CPU_ZERO initializes all the bits in the mask to zero. */
CPU_ZERO( &mask );
/* CPU_SET sets only the bit corresponding to cpu. */
CPU_SET( created_thread, &mask );
/* sched_setaffinity returns 0 in success */
if( sched_setaffinity( 0, sizeof(mask), &mask ) == -1 )
{
printf("WARNING: Could not set CPU Affinity, continuing...\n");
}
|
ここまでプログラムを実行すると、スレッドに個別のアフィニティーが設定されます。sched_setaffinityの呼び出しによって、pidによって示されるプロセスのCPUアフィニティー・マスクが設定されます。pidがゼロの場合は、カレント・プロセスが使用されます。
アフィニティー・マスクは、maskに格納されたビットマスクによって表されます。最下位ビットはシステムの最初の論理プロセッサー番号に対応し、最上位ビットはシステムの最後の論理プロセッサー番号に対応します。
セットされているビットはスケジュール可能なCPUに対応し、セットされていないビットはスケジュール不能なCPUに対応します。言い換えると、プロセスは、該当するビットがセットされているプロセッサーにバインドされて、そのプロセッサーでだけ実行します。通常は、マスクのすべてのビットがセットされます。これらの各スレッドのCPUアフィニティーは、分岐生成された子スレッドに渡されます。
ビットマスクを直接変更しないでください。代わりに、次のようなマクロを使用してください。この例では、すべてのマクロを使用しているわけではなく、皆さんのプログラムで必要になりそうなものをリストしてあります。
リスト3.ビットマスクを間接的に変更するマクロ
void CPU_ZERO (cpu_set_t *set)
This macro initializes the CPU set set to be the empty set.
void CPU_SET (int cpu, cpu_set_t *set)
This macro adds cpu to the CPU set set.
void CPU_CLR (int cpu, cpu_set_t *set)
This macro removes cpu from the CPU set set.
int CPU_ISSET (int cpu, const cpu_set_t *set)
This macro returns a nonzero value (true) if cpu is
a member of the CPU set set, and zero (false) otherwise.
|
この記事の目的のために、各スレッドに計算に時間のかかる操作を実行させるサンプル・コードを以下に示します。
リスト4.各スレッドが数値計算操作を実行する
/* Now we have a single thread bound to each cpu on the system */
int computation_res = do_cpu_expensive_op(41);
cpu_set_t mycpuid;
sched_getaffinity(0, sizeof(mycpuid), &mycpuid);
if ( check_cpu_expensive_op(computation_res) )
{
printf("SUCCESS: Thread completed, and PASSED integrity check!\n",
mycpuid);
ret = TRUE;
}
else
{
printf("FAILURE: Thread failed integrity check!\n",
mycpuid);
ret = FALSE;
}
return ret;
}
|
2.6 LinuxカーネルのCPUアフィニティーの基本設定を説明しました。このメソッド・コールを、ビジーにさせるCPUの数を指定するユーザー指定パラメーターを取る面白いメインプログラムで包みましょう。もう1つのメソッドを使用して、システムのプロセッサー数を調べることもできます。
int NUM_PROCS = sysconf(_SC_NPROCESSORS_CONF);
このメソッドによって、プログラムは、デフォルトですべてを働かせて、ユーザーが指定できるのはシステム上で実際に使用可能なプロセッサーの範囲内に限るなど、いくつのプロセッサーをビジーにするかを決めることができます。
上記のサンプル・アプリケーションを実行すると、さまざまなツールを使用してCPUがビジーであることを確認できます。単純なテストには、Linuxのtopコマンドを使用します。topの実行中に"1"キーを押すと、実行中のプロセスがCPUごとに表示されます。
このサンプル・アプリケーションは、ささやかなものですが、Linuxカーネルに実装されているハード・アフィニティーの基本を知ることができます。(このコード・サンプルを使用したアプリケーションは、おそらく、さらに面白いことができるでしょう。)とにかく、CPUアフィニティー・カーネルAPIの基本を理解できれば、複雑なアプリケーションのパフォーマンスを最後の一滴まで搾り出すことができます。
| 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|---|---|---|
| Sample app using CPU affinity kernel API | thrasher.zip | 3KB | FTP |
学ぶために
- 「ハイパー・スレッド処理でLinuxを高速化」(developerWorks, 2003年1月)は、Linux SMPカーネルでのハイパースレッドの効果を調査した結果を解説しています。
- 「Identify performance bottlenecks with OProfile for Linux on POWER」(developerWorks, 2005年5月)は、パフォーマンス問題を特定するためにCPU親和性を使用するLinuxモジュール・ツールを解説しています。
- 「With Scheduler improvements in Linux 2.6, is it ready for the data center?」(CCR2, 2004年9月)は、良いスケジューラーであるために、CPU親和性を含めて何が必要かを説明しています。
-
developerWorksのLinuxゾーンには、Linux開発者のための資料が他にも豊富に用意されています。
製品や技術を入手するために
-
CPU affinity patch for niceを入手してください。
-
無料の2枚組DVDセット、SEK for Linuxをご注文ください。DB2®やLotus®、Rational®、Tivoli®、WebSphere® など、Linux用の最新IBMソフトウェア試用版が含まれています。
- 皆さんの次期Linux開発プロジェクトを、IBM trial softwareを使って構築してください。developerWorksから直接ダウンロードすることができます。
議論するために
-
How do I do cpu affinity in Linux?は、質の良い、実際的な議論が行われているフォーラムです。
-
developerWorks blogsに参加してdeveloperWorksコミュニティーに加わってください。
Eli Dowは、ニューヨーク州Poughkeepsieにある、IBM Linux Test and Integration Centerのソフトウェア技術者です。Clarkson Universityにて、コンピューター・サイエンスおよび心理学で学位を、またコンピューター・サイエンスで修士を取得しています。彼が関心を持っている領域は、GNOMEデスクトップや、人とコンピューターの対話動作、Linuxシステム・プログラミングなどです。連絡先はemdow@us.ibm.comです。