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命令を利用します。
対象読者
- 前回投稿した『ハードウェアDEP機能の調査』や『CPUID命令によるCPUの性能・機能の把握』に興味がある方
- CPUの働き、特に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上で複数のプロセッサとして振る舞います。
この技術の利点は以下の通り。
- システムの変更が少ない
- パフォーマンス面での優位性
Intel社の発表では、ハイパースレッディング技術に対応したOS上で約2~3割のパフォーマンスアップが期待できるとしています。
本稿では、「HTT」と用語を統一します。
マルチコア/デュアルコア
マルチコア(Multicore)は、1 つのCPU上に複数の論理プロセッサ(Core)を搭載することでSMP環境を構築しています。デュアルコア(Dualcore)は、1つのCPU上に2つの論理プロセッサが搭載されている状態で、マルチコアの一種です。デュアルコア対応のCPUは、既にAMD社/Intel社が採用しており、出荷も始まっています。
この技術の利点は以下の通り。
- ハードウェアの変更が少ない
- パフォーマンスの優位性
- パフォーマンス対消費電力
本稿では、「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使用率が視覚的に確認することができます。
#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)
- ソフトアフィニティ
- ハードアフィニティ
マルチプロセッサ/HTT/Multicore のパフォーマンス差
マルチプロセッサ/HTT/Multicoreのどの環境も、CPU使用率が100%になるような重いプロセスが1つしかない場合、パフォーマンスには大きな差は生じませんでした。慢性的に重いプロセスが複数個存在した場合、パフォーマンスの違いが顕著に出ます。これを簡単に説明してみます。
上記のサンプルプログラムを変更し、指定したプロセッサのみに負荷を与えるよう改造しました。
#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を実行します。
.......... 中略 ..........counter (1360) .......... 中略 ..........counter (1593) <--- 2つめ実行開始 (looper2 1) .......... 中略 ..........counter (3047) .......... 中略 ..........counter (3047)
.......... 中略 ..........counter (3000) .......... 中略 ..........counter (3000) .......... 中略 ..........counter (3016) .......... 中略 ..........counter (3000)
上記の結果より推測される事項は以下の通り。
- 各プロセスに対し均等にCPU使用率(仕事量)が配分されている
- 理想値(1344*2)と比べると1割程度のパフォーマンスダウン?
それでは同じCPUでHTTを無効にした場合、どうなるでしょうか。
.......... 中略 ..........counter (1359) .......... 中略 ..........counter (2563) <--- 2つめ実行開始 .......... 中略 ..........counter (4672) .......... 中略 ..........counter (4765)
.......... 中略 ..........counter (1875) .......... 中略 ..........counter (1875) .......... 中略 ..........counter (1906)
この結果より推測される事項は以下の通り。
- 各プロセスに対しまばらにCPU使用率(仕事量)が配分されている
- HTT有効(3047+3000)と比べると 1割程度のパフォーマンスダウン?
counter
の値は安定したパフォーマンス(数値)を示しませんでした。今回はWindows XPによる結果を出力しましたが、Windows 2000の場合、更に値がまばらになります。Multicoreの場合
それではMulticoreの場合はどうでしょうか(CPUは「AMD Athlon(tm) 64 X2 Dual Core Processor 3800+」を使用しています)。
.......... 中略 ..........counter (3234) .......... 中略 ..........counter (3234)<--- 2つめ実行開始 .......... 中略 ..........counter (3250) .......... 中略 ..........counter (3235)
.......... 中略 ..........counter (3219) .......... 中略 ..........counter (3218) .......... 中略 ..........counter (3219)
この結果より推測される事項は以下の通り。
- 各プロセスに対し影響を与えない理想的なマルチプロセッサ環境