SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

CPUIDチュートリアル

CPUID命令によるプロセッサ環境の判別

プロセッサの種類の検出とパフォーマンスの検証

  • X ポスト
  • このエントリーをはてなブックマークに追加

本稿では、CPUID命令を利用して、現在動作しているプロセッサが論理プロセッサ(ハイパースレッディングまたはデュアルコア・マルチコア)なのか、物理プロセッサ(マルチプロセッサ)なのかを検出する方法を検討します。

  • X ポスト
  • このエントリーをはてなブックマークに追加

完成図(「sample.exe」の出力結果)
Brand Srting = [AMD Athlon(tm) 64 X2 Dual Core Processor 3800+]
Total processor            = [2]
Physical  Processor number = [1]
Multicore Processor number = [2]
HTT       Processor number = [0]

はじめに

 この記事は、現在動作しているプロセッサが論理プロセッサ(ハイパースレッディングまたはデュアルコア・マルチコア)なのか、物理プロセッサ(マルチプロセッサ)なのかを検出する方法について検討します。

 方針としては、特権命令(RDMSRなど)は使用せず、通常のアプリケーションからも呼び出し可能なCPUID命令を利用します。

対象読者

必要な環境

 Visual C++ version 6 SP6で開発を行っています。

 サンプルプログラムは、Visual C++ 2005 Express Edition 日本語版Platform SDK環境でも構築できることを確認しています。残念ながら今回、MFCの環境は構築することができませんでした。依然としてlittleCPUIDのbuildには、MFC開発環境が必要です。

様々なプロセッサ環境

 2005年12月現在、x86系のCPUには以下のプロセッサ環境が存在しています。

SMP(Symmetric Multiprocessing)

 SMPは、物理的に複数のCPUを搭載します。マルチプロセッサ環境の中では一般的な環境です。x86系では、1995年に出荷を開始したPentium ProにてSMP環境を実装しており、古株です(Pentiumでもマルチプロセッサ専用Pentiumを使用することでSMP環境を構築できた記憶があるのですが、あまり有名ではないかもしれません)。

 本稿では、「マルチプロセッサ」と用語を統一します。

SMT(Simultaneous Multi-Threading)

 SMT は、x86環境では、Intel社が2002年にハイバースレッディング技術(Hyper-Threading Technology, HTT)として実装しています。この技術は、1つのCPU上で複数のプロセッサとして振る舞います。

 この技術の利点は以下の通り。

  • システムの変更が少ない
  • SMP環境と比べSMT環境を構築する場合、システムの変更が必要無く、CPUの載せ替えのみでマルチプロセッサ環境を構築できる場合が多いです。
  • パフォーマンス面での優位性
  • 論理プロセッサに対しては、できるだけ使用されていないCPUリソースを割り当てようとします。よってメインの実行環境のパフォーマンスに極力影響を及ぼさず、1つのCPUでも効率的なマルチプロセッサ環境を構築できます。

 Intel社の発表では、ハイパースレッディング技術に対応したOS上で約2~3割のパフォーマンスアップが期待できるとしています。

 本稿では、「HTT」と用語を統一します。

マルチコア/デュアルコア

 マルチコア(Multicore)は、1 つのCPU上に複数の論理プロセッサ(Core)を搭載することでSMP環境を構築しています。デュアルコア(Dualcore)は、1つのCPU上に2つの論理プロセッサが搭載されている状態で、マルチコアの一種です。デュアルコア対応のCPUは、既にAMD社/Intel社が採用しており、出荷も始まっています。

 この技術の利点は以下の通り。

  • ハードウェアの変更が少ない
  • SMT環境と同じくシステムの変更が少ないため、CPUを載せ変えるだけでSMP環境を構築できる場合が多いです。
  • パフォーマンスの優位性
  • 1つのCPU上に論理プロセッサが複数実装されているので、SMP環境と同じ程度のパフォーマンス向上が期待できます。更にAMD社のマルチコア対応CPUの場合、チップセットを介することなく直接論理プロセッサ同士で互いに通信することで、効果的なメモリ活用も可能にしており、マルチコアならではのパフォーマンス向上を行っています。
  • パフォーマンス対消費電力
  • 現在、4GMzを境に動作周波数を上げることによるパフォーマンス向上は難しくなりつつあります。マルチコアの場合、特に消費電力面でシングルプロセッサと差が大きくなく、パフォーマンス向上と比較して消費電力が控えめであることにより、注目されています。

 本稿では、「Multicore」もしくは「Dualcore」と用語を統一します。

物理プロセッサ(SMP)と論理プロセッサ(HTT/Multicore)

 現在のプロセッサ環境は、物理プロセッサと論理プロセッサの技術が複数絡み合った状況になっています。

 例えばPentium4の最高峰であるPentium Processor Extreme Editionの場合、HTT(2つ)とDualcore(2つ)をサポートしており、計4つのプロセッサとして使用することができます。また、最近発表されたMulticore対応Xeonを2つ使用した構成の場合、SMP(2つ)とHTT(2つ)とDualcore(2つ)をサポートしており、計8つのプロセッサとして使用することができます。

各プロセッサのCPUID取得方法についての調査

 プロセッサ環境を判別するには各プロセッサのCPUID情報が必要と考えました。Windows NT系で実装されているGetProcessAffinityMask()SetProcessAffinityMask()関数を用いて、各プロセッサごとにCPUID情報を取得する方法を検討しました。上記の関数はプロセッサ・アフィニティを制御するための関数で、現在使用しているプロセッサを特定し再設定することを目的としています。

 まず、本当に動作するのかを確認するために、以下のサンプルプログラムを作成しました。このサンプルプログラムはタスクマネージャの「CPU 使用率の履歴」にて各プロセッサごとのCPU使用率が視覚的に確認することができます。

GetProcessAffinityMask()/SetProcessAffinityMask() 関数を用いたサンプルプログラム(looper)
#include <stdio.h>
#include <windows.h>
int main(int argc, char* argv[])
{
    HANDLE hCP;
    DWORD pamask, samask, patmp = 0;
    DWORD dwStart;
    int i, numcpu = 0;
    hCP = GetCurrentProcess();
    if( GetProcessAffinityMask(hCP, &pamask, &samask) ){
        printf("GetProcessAffinityMask lpProcessAffinityMask=[%d], 
lpSystemAffinityMask=[%d]\n", pamask, samask);
    }else{
        printf("ERR:GetProcessAffinityMask [%d]\n", GetLastError());
        return -1;
    }
    if( pamask == 0 ){
        printf("ERR:pamask is 0\n");
        return -2;
    }
    numcpu = 0;
    for(;;){
        patmp = pamask>>numcpu;
        // no more processor?
        if( 0 == patmp ){
            numcpu = 0;
            continue;
        }
        // Is this processor work?
        if( !(patmp&0x1) ){
            numcpu++;
            continue;
        }
        // Change processor...
        if( !SetProcessAffinityMask(hCP, 1<<numcpu) ){
            break;
        }

        Sleep(1);
        // Looper!
        dwStart = GetTickCount();
        printf( "processor(%d) :", numcpu );
        for(i=0; i<(0x10000000*4); i++){
            if( !(i%0x1000000*4) ){
                printf(".");
            }
        }
        printf("\ncounter (%d)\n", GetTickCount() - dwStart);
        // continue...
        numcpu++;
    }
    return 0;
}

 このプログラムの重要な個所は、一定回数のカウントループ(空ループ)です。

 空ループは、タイマーを持っていない組み込み機器で簡易なタイマーとして用いられることがありますが、近年のマルチプロセスなOS環境ではCPU資源を無尽蔵に奪い取ってしまう劣悪なプログラムの代表例です。今回は、現在使用しているプロセッサがどれなのかをタスクマネージャーで目視するために、意図的にプロセッサの使用率を100%まで上げる空ループを実装しました。

 以下はプロセッサが4つ見えている状態での出力結果です。確かに一定時間で順序良くプロセッサが切り替わっていることが目視出来ました。

タスク マネージャによる目視
タスク マネージャによる目視
サンプルプログラムの出力結果
GetProcessAffinityMask lpProcessAffinityMask=[15],
 lpSystemAffinityMask=[15]
processor(0) :................. 中略 .................
counter (1547)
processor(1) :................. 中略 .................
counter (1547)
processor(2) :................. 中略 .................
counter (1578)
processor(3) :................. 中略 .................
counter (1547)
processor(0) :................. 中略 .................
counter (1547)
processor(1) :................. 中略 .................
counter (1547)
processor(2) :................. 中略 .................
counter (1594)
processor(3) :................. 中略 .................
counter (1531)
プロセッサ・アフィニティ(Processor Affinity)
 プロセッサ・アフィニティ(親和性)には以下の2通りあります。
  • ソフトアフィニティ
  • ソフトアフィニティは、同一条件のスレッドやプロセスを生成する場合、なるべく同じプロセッサを使用するようスケジューリングします。同じプロセッサを使用することで、L2 cacheやmemory access(NUMA)を有効に利用することができ、パフォーマンスの向上に繋がります。
  • ハードアフィニティ
  • ハードアフィニティは、プロセスやスレッドに対して能動的に特定のプロセッサを割り当てる事ができます。複数の特殊な処理を特定のプロセスに割り当てる事で効率の良いスケジューリングを行う事を可能にします。ただし多くの場合は、ハードアフィニティによる独自のスケジューリングより、OSのスケジューラーにプロセッサ割り当てを管理させた方が良いパフォーマンスを叩き出すことがほとんどのようです。
 Windowsのプロセッサ・アフィニティは『Advanced Windows 改訂第4版』の「7.10 章 アフィニティ」が参考になります。Linuxのプロセッサ・アフィニティは『プロセッサー・アフィニティーの管理』の記事が参考になりました。

マルチプロセッサ/HTT/Multicore のパフォーマンス差

 マルチプロセッサ/HTT/Multicoreのどの環境も、CPU使用率が100%になるような重いプロセスが1つしかない場合、パフォーマンスには大きな差は生じませんでした。慢性的に重いプロセスが複数個存在した場合、パフォーマンスの違いが顕著に出ます。これを簡単に説明してみます。

 上記のサンプルプログラムを変更し、指定したプロセッサのみに負荷を与えるよう改造しました。

サンプルプログラム(looper)の変更プログラム(looper2)
#include <stdio.h>
#include <windows.h>
int main(int argc, char* argv[])
{
    HANDLE hCP;
    DWORD pamask, samask, patmp = 0;
    DWORD dwStart;
    int i, numcpu = 0, curr = 0;
    if( argc < 2 ){
        printf("usage : looper [select processor number]\n");
        printf("(ex.) : looper 2\n");
        return -3;
    }
    curr = atoi(argv[1]);
    hCP = GetCurrentProcess();
    if( GetProcessAffinityMask(hCP, &pamask, &samask) ){
        printf("GetProcessAffinityMask lpProcessAffinityMask=[%d],
 lpSystemAffinityMask=[%d]\n", pamask, samask);
    }else{
        printf("ERR:GetProcessAffinityMask [%d]\n", GetLastError());
        return -1;
    }
    if( pamask == 0 ){
        printf("ERR:pamask is 0\n");
        return -2;
    }
    numcpu = 0;
    for(;;){
        patmp = pamask>>numcpu;
        // no more processor?
        if( 0 == patmp ){
            printf("ERR: not found current cpu number [%d]",
 atoi(argv[1]));
            return -4;
        }
        // Is this processor work?
        if( !(patmp&0x1) ){
            numcpu++;
            continue;
        }
        if( curr > 0 ){
            numcpu++;
            curr--;
            continue;
        }
        // Change processor...
        if( !SetProcessAffinityMask(hCP, 1<<numcpu) ){
            break;
        }
        Sleep(1);
        printf( "processor(%d) :\n", numcpu );
        // Looper!
        for(;;){
            dwStart = GetTickCount();
            for(i=0; i<(0x10000000*4); i++){
                if( !(i%0x1000000*4) ){
                    printf(".");
                }
            }
            printf("counter (%d)\n", GetTickCount() - dwStart);
        }
    }
    return 0;
}

 ここで重要なのはcounterの出力です。counterは一定回数の空ループを処理する時間を出力しています。よってこの値が小さければ小さいほど速く空ループを処理したことになり、一種のベンチマーク的役割を果たしています。

シングルプロセッサとHTT対応プロセッサの比較(重いプロセスが1つの場合)

 まずHTT対応CPUにてHTT有効/無効とで差があるか確認してみました(BIOSにて有効/無効)。結果は以下の通り。

  • HTT有効の場合(looper2 0): 約 1344
  • HTT無効の場合(looper2 0): 約 1344

 重いプロセスが1つしかない場合(looper2を1つしか実行しない場合)、HTT有効とHTT無効による大きな差は見られませんでした。

シングルプロセッサとHTT対応プロセッサの比較(重いプロセスが2つの場合)

 次に重いプロセスを2つ実行した場合、どうなるのでしょうか。コマンドプロンプトを2つ開き、コマンドプロンプトごとにlooper2を実行します。

HTT有効 「looper2 0」の実行結果(1つ目)
.......... 中略 ..........counter (1360)
.......... 中略 ..........counter (1593) <--- 2つめ実行開始 (looper2 1)
.......... 中略 ..........counter (3047)
.......... 中略 ..........counter (3047)
HTT有効 「looper2 1」の実行結果(2つ目)
.......... 中略 ..........counter (3000)
.......... 中略 ..........counter (3000)
.......... 中略 ..........counter (3016)
.......... 中略 ..........counter (3000)

 上記の結果より推測される事項は以下の通り。

  • 各プロセスに対し均等にCPU使用率(仕事量)が配分されている
  • 理想値(1344*2)と比べると1割程度のパフォーマンスダウン?
  • 複数プロセスが動作していることやタスク切り替えでCPUパワーが消費されていると思われます。

 それでは同じCPUでHTTを無効にした場合、どうなるでしょうか。

HTT無効 「looper2 0」の実行結果(1つめ)
.......... 中略 ..........counter (1359)
.......... 中略 ..........counter (2563) <--- 2つめ実行開始
.......... 中略 ..........counter (4672)
.......... 中略 ..........counter (4765)
HTT無効 「looper2 0」の実行結果(2つめ)
.......... 中略 ..........counter (1875)
.......... 中略 ..........counter (1875)
.......... 中略 ..........counter (1906)

 この結果より推測される事項は以下の通り。

  • 各プロセスに対しまばらにCPU使用率(仕事量)が配分されている
  • HTT有効である状態と比べcounterの値は安定したパフォーマンス(数値)を示しませんでした。今回はWindows XPによる結果を出力しましたが、Windows 2000の場合、更に値がまばらになります。
  • HTT有効(3047+3000)と比べると 1割程度のパフォーマンスダウン?
  • 重いプロセスが複数実行されている環境では、シングルプロセッサよりマルチプロセッサの方が優位であることが分かります。

Multicoreの場合

 それではMulticoreの場合はどうでしょうか(CPUは「AMD Athlon(tm) 64 X2 Dual Core Processor 3800+」を使用しています)。

Multicore有効 「looper2 0」の実行結果(1つ目)
.......... 中略 ..........counter (3234)
.......... 中略 ..........counter (3234)<--- 2つめ実行開始
.......... 中略 ..........counter (3250)
.......... 中略 ..........counter (3235)
Multicore有効 「looper2 1」の実行結果(2つ目)
.......... 中略 ..........counter (3219)
.......... 中略 ..........counter (3218)
.......... 中略 ..........counter (3219)

 この結果より推測される事項は以下の通り。

  • 各プロセスに対し影響を与えない理想的なマルチプロセッサ環境
  • 2つのプロセスが何ら影響を与えることはありません。遜色のないパフォーマンスが得られています。
    若干注意が必要なのは「Athlon 64 X2 3800+」の動作周波数は2000MHzで、これはシングルプロセッサで言うと「Athlon 64 3200+」に相当します。looper2の1つだけの仕事量は「Athlon 64 3200+」とほぼ同じであると考えてよいでしょう。逆に言うとlooper2を複数動作させた時に初めて「Athlon 64 X2 3800+」本来のパフォーマンスが得られるのではないでしょうか。

次のページ
SMP/HTT/Multicoreの検出

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
CPUIDチュートリアル連載記事一覧

もっと読む

この記事の著者

Mc.N(エムシイエヌ)

SyncHack 管理人。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/247 2006/01/16 12:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング