CPU のクロックスピード
開発環境: Visual Studio 2008
1.目的
最近のプロセッサは、負荷の状態によって、CPUクロックのスピードを調整することができます。最近、eee PC
を買ったのですが、実際にどの程度この機能が動いているのかどうか、知りたくなりました。
通常は、Task Managerでプロセッサの状態を知ることができますが、このTask
Managerでは実際のプロセッサのクロックを知ることができません。実際のプロセッサのクロックスピードは、WMIを使って知ることができるようなので、試してみました。
2.参考書
-
C# WMI
3.Windows Forms での実装
3.1 クロックスピードを得る方法
CPUのクロックスピードを得るためには、WMIの
Win32_Processorクラスを利用して、次のように呼び出します。
Pentium D
を使用した場合、CPUは1つしか認識されませんでした。複数のCPUをマザーボード上に搭載している場合は、複数のデータが返される可能性がありますので、注意が必要です。
同様に ExtClock (外部クロック)、MaxClockSpeed(最大クロックスピード)も取得することができます。
なお、WMIを使用する場合には、ソリューションエクスプローラから参照の追加により、System.Managementを追加してあげる必要があります。
この参照設定を行わないと、"型または名前空間名 'ManagementObjectSearcher'
が見つかりませんでした。using ディレクティブまたはアセンブリ参照が不足しています。 "というエラーが発生します。
using
System.Management;
...
ManagementObjectSearcher query1 =
new
ManagementObjectSearcher("SELECT
* FROM Win32_Processor");
var res =
from
ManagementObject
item in
query1.Get()
select new
{
ExtClock = item["ExtClock"],
CurrentClockSpeed = item["CurrentClockSpeed"],
MaxClockSpeed = item["MaxClockSpeed"]
};
Console.WriteLine(res.First().CurrentClockSpeed.ToString());
3.2 タイマーで定期的にデータを取得する
クロックスピードは、CPUの負荷状態により変更される可能性があるため、Timer
で定期的にデータを更新する必要があります。
そこで、ツールボックスより TextBox, Label
(MHzの表示)タイマーコントロールを追加し、定期的にデータを取得するようにします。Timerのプロパティウィンドウより、Enabled
は Trueとし、TimerのIntervalを1000、つまり取得間隔は1秒にしてみました。
すると、ManagementObjectの処理に意外と時間がかかり、ウィンドウのリサイズ、移動などの処理ができず、ウィンドウが固まってしまう状態になってしまいました。
対応方法としては、2つあります。
1つは、WMI処理中はカーソルを砂時計にして、処理の間ユーザーに待たせる方法。
もう一つが、バックグラウンド処理にして、別スレッドで処理をすることにより、少なくともウィンドウを動かすことができるようにすることです。
ここでは、バックグラウンド処理で対応することにします。
3.3 バックグラウンド処理
|
ツールボックスより、BackgroundWorker をフォーム上にドラッグアンドドロップします。
backgroundWorker1.DoWork +=
new
DoWorkEventHandler(UpdateData);
backgroundWorker1.RunWorkerCompleted +=
new
RunWorkerCompletedEventHandler(Completed);
private
void
UpdateData(object
sender, DoWorkEventArgs
e) { var res =
from
ManagementObject item in
query1.Get() select new
{ ExtClock = item["ExtClock"],
CurrentClockSpeed = item["CurrentClockSpeed"],
MaxClockSpeed = item["MaxClockSpeed"]
}; e.Result = res.First().CurrentClockSpeed.ToString(); }
private
void
Completed(object
sender, RunWorkerCompletedEventArgs
e) { uint
res = (uint)e.Result;
textBox1.Text = res.ToString(); }
なお、バックグラウンド処理では、メインウィンドウの別スレッドで動いているため、直接
TextBoxに値を書くことはできません。無理やりバックグラウンド処理の中でテキストボックスに値を書こうとしても"有効ではないスレッド間の操作:
コントロールが作成されたスレッド以外のスレッドからコントロール 'textBox1' がアクセスされました。"というエラーになります。
このため、Completedイベントハンドラに処理が戻ってきた段階でTextBoxに書き込む必要があります。
しかし、バックグラウンド処理にしても、タイマーが1秒では短すぎて、"BackgroundWorker
は現在ビジー状態であるため、複数のタスクを同時に実行できません。"というエラーになってしまいます。そこで、タイマーのインターバルは5秒にしました。
|
3.4 ソースコード
|
以上より、次のようなアプリケーションができます。
using
System; using
System.ComponentModel; using
System.Linq; using
System.Windows.Forms; using
System.Management; namespace
WindowsFormsApplication14 {
public
partial
class
Form1 :
Form {
public Form1()
{ InitializeComponent();
//
InitBackgroundWorker(); }
ManagementObjectSearcher
query1 = new
ManagementObjectSearcher("SELECT
* FROM Win32_Processor");
private
void
InitBackgroundWorker() {
backgroundWorker1.DoWork += new
DoWorkEventHandler(AsyncGetData);
backgroundWorker1.RunWorkerCompleted +=
new
RunWorkerCompletedEventHandler(Completed);
}
private
void
timer1_Tick(object
sender, EventArgs
e) {
if
(backgroundWorker1.IsBusy)
return;
backgroundWorker1.RunWorkerAsync(); }
private
void
AsyncGetData(object
sender, DoWorkEventArgs
e) {
var res =
from
ManagementObject item
in
query1.Get()
select
new
{
CurrentClockSpeed = item["CurrentClockSpeed"]
}; e.Result = res.First().CurrentClockSpeed;
}
private
void
Completed(object
sender, RunWorkerCompletedEventArgs
e) {
if (e.Error !=
null)
{
MessageBox.Show(e.Error.Message);
}
else if (e.Cancelled)
{
MessageBox.Show("Canceled");
} else
{
uint res = (uint)e.Result;
textBox1.Text = res.ToString(); }
} } }
あと少し見た目を変えてあげて、次のような簡単なアプリケーションの出来上がりです。
なお、Vista では、[コントロール
パネル]→[システムとメンテナンス]→[電源オプション]から、お気に入りのプランで、[プロセッサの電源管理]項目から最大のプロセッサの状態を変更することができます。
[省電力]→[プラン設定の変更]を見てみると、規定値で最大で50%となっています。
実際に試してみると、50%に落ちました。Task
Managerを見てみると、クロックのスピードが半分になったとたんに、CPUの負荷が倍になっているので、実際にクロックが半分になっているようです。
|
4. WPFに移植する
|
ところで、Windows Forms
はメインテナンスモードに入っています。今後はWPFがメインストリームになるということですね。というわけで、Windows Forms
からWPFで作ってみようと思ったら、あれ?タイマーがない、バックグラウンドワーカーがないということに気がつきました。
じゃあどうするの?ということで、ついでにWPFに移植してみることにしました。
|
4.1 WPFへの移植
|
単純にコードをWPFのプロジェクトに移植すると、Timer,
BackgroundWorkerは、WPFのツールバーの中に存在しません。このため、単純にコードを移植しただけでは動きません。しかし、System.Timers.Timerも、BackgroundWorkerのインスタンスを実装してあげ、WPFでも動きそうです。
しかし、System.Timers.Timer を WPF アプリケーションで使用する場合は、System.Timers.Timer
は
UI スレッドとは別のスレッドで実行されます。つまり、今回のケースで行くと、バックグラウンドワーカーで処理された結果をGUIに返そうとすると、"このオブジェクトは別のスレッドに所有されているため、呼び出しスレッドはこのオブジェクトにアクセスできません。"という、InvalidOperationException
という実行時例外が発生して、動きません。
WPFでは、レンダリング用とUI管理用の2 つのスレッドがあります。通常、開発者が扱うのは UI
スレッドだけです。WPF では、ほとんどのオブジェクトが UI スレッドに関連付けられています。したがって、WPFのUI
オブジェクトは、そのオブジェクトを作成したUIスレッド上でのみ使用できます。他のスレッドで使用すると、実行時例外が発生します。この点はWindows
Formsと同じです。
しかし、System.Timers.Timer
で時間が経過した後に呼ばれるイベントハンドラーは、UIスレッド上で呼び出してくれないため、別の方法で対応する必要があります。
WPFでは、このために System.Windows.Timers.DispatcherTimer
クラスが用意されています。コードは、ほとんど同じような形で書くことができます。具体的には、
using System.Windows.Threading;
private
DispatcherTimer timer1 =
new
DispatcherTimer();
timer1.Interval = new
TimeSpan(0,0,5);
timer1.Tick += new
EventHandler(timer1_Tick);
timer1.Start();
というようになります。
これでWPFでも動作するようになるはずです。
4.2 WPFのソースコード
WPFでのソースコードは次のようになります。
using
System; using
System.Linq; using
System.Windows; using
System.ComponentModel; using
System.Management; using
System.Windows.Threading;
namespace WpfCPUClock {
public
partial
class
Window1 :
Window {
public
Window1() {
InitializeComponent(); }
private
BackgroundWorker
backgroundWorker1 = new
BackgroundWorker();
private
DispatcherTimer
timer1 = new
DispatcherTimer();
private
void
Window_Loaded(object
sender, RoutedEventArgs
e) {
InitBackgroundWorker();
timer1.Interval = new
TimeSpan(0,0,5);
timer1.Tick += new
EventHandler(timer1_Tick);
timer1.Start(); }
ManagementObjectSearcher
query1 = new
ManagementObjectSearcher("SELECT
* FROM Win32_Processor");
private
void
InitBackgroundWorker() {
backgroundWorker1.DoWork += new
DoWorkEventHandler(AsyncGetData);
backgroundWorker1.RunWorkerCompleted +=
new
RunWorkerCompletedEventHandler(Completed);
}
private
void
timer1_Tick(object
sender, EventArgs
e) {
if (backgroundWorker1.IsBusy)
return;
backgroundWorker1.RunWorkerAsync(); }
private
void AsyncGetData(object
sender, DoWorkEventArgs
e) {
var res =
from
ManagementObject item
in
query1.Get()
select
new
{
CurrentClockSpeed = item["CurrentClockSpeed"]
}; e.Result = res.First().CurrentClockSpeed;
}
private
void
Completed(object
sender, RunWorkerCompletedEventArgs
e) {
if (e.Error !=
null)
{
MessageBox.Show(e.Error.Message);
}
else if
(e.Cancelled) {
MessageBox.Show("Canceled");
}
else {
uint res = (uint)e.Result;
label1.Content = res.ToString() + "
MHz"; }
} } }
4.3 バイナリー
|
バイナリーは次のとおりです。
WpfCPUClock.exe
あっ、なまけてる!って感じがいいですね。
ただし、CPU-Zとの動きと比較してみると、WMI
の返す値があまり正確ではないようなので、数字は目安程度に考えたほうがよさそうです。CPU-Zは、どうやってデータを取っているんだろう・・・
|