目次


プロセッサー・アフィニティーの管理

ハード (対ソフト) CPU アフィニティーを使用する 3 つの理由と方法

Comments

簡単に言うと、CPU アフィニティーとは、プロセスが他のプロセッサーに移動せずに、できるだけ長く特定の CPU で実行する傾向のことです。Linux カーネル・プロセス・スケジューラーは、本質的に、一般にソフト CPU アフィニティーと呼ばれるものを強制します。これは、プロセスが一般に、プロセッサー間を頻繁に移動しないことを意味します。これは望ましい状態です。あまり移動しないプロセスは、オーバーヘッドが少ないからです。

2.6 Linux カーネルは、開発者がハード CPU アフィニティーをプログラムによって実施できるメカニズムも備えています。これは、特定のプロセスを実行するプロセッサー (またはプロセッサーのセット) をアプリケーションが明示的に指定できることを意味します。

Linux カーネルのハード・アフィニティーとは

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. 予感がするから

科学計算や学術研究でのコンピューティングでは、直感や予感に基づくシナリオは珍しいことではなく、おそらく民間部門のコンピューティングでも同様です。アプリケーションがマルチプロセッサー・マシンの大量の計算時間を消費する必要があると直感的にわかる場合があります。

理由 2. 複雑なアプリケーションをテストするから

複雑なソフトウェアのテストは、カーネル・アフィニティー・テクノロジーに興味を持つべきもう 1 つの理由です。リニア・スケーラビリティ・テストが必要なアプリケーションがあるとします。一部の製品では、投入するハードウェアが多いほどパフォーマンスが上がるという決まり文句が使われます。

単に複数のマシン (プロセッサー構成ごとに 1 台のマシン) を購入するのではなく、次のようにすることができます。

  • マルチプロセッサー・マシンを 1 台購入する
  • プロセッサーを 1 つずつ増やしていく
  • 1 秒間のトランザクション数を測定する
  • 結果としてのスケーラビリティーをグラフにする

アプリケーションのパフォーマンスが CPU の増設につれて本当にリニアに増す場合、1 秒間のトランザクション数と CPU の数は直線関係になるはずです (まっすぐな対角線のグラフなど ? 次の段落を参照) 。このように動作をモデル化すると、土台となっているハードウェアをアプリケーションが効率的に使用できるかどうかがわかります。

アムダールの法則は、現実には、おそらくこのようにならないが、それに近くなることを示しています。一般的なケースでは、すべてのプログラムに逐次的な構成要素があると考えることができます。問題のセットが大きくなるほど、逐次的構成要素が最適解決時間の上限を高くすることになります。

アムダールの法則は、CPU キャッシュのヒット率を高く保ちたいときに特に重要です。特定のプロセスが移動されると、CPU キャッシュのメリットはなくなります。事実、使用している CPU が特定のデータをキャッシュする必要がある場合、他のすべての CPU は自分自身のキャッシュにある (そのデータの) エントリーを無効にします。

したがって、複数のスレッドが同じデータを必要とする場合は、特定の CPU にバインドして、それらのスレッドすべてがキャッシュ・データにアクセスできるようにする (または、少なくとも、キャッシュ・ヒットの確率が高くなるようにする) のは意味のあることです。さもなければ、各スレッドが異なる CPU で実行して、常に互いのキャッシュ・エントリーを無効にすることになります。

理由 3. 時間依存の決定性プロセスを実行するから

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 の基本を理解できれば、複雑なアプリケーションのパフォーマンスを最後の一滴まで搾り出すことができます。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux
ArticleID=226645
ArticleTitle=プロセッサー・アフィニティーの管理
publish-date=09292005