前回紹介したように、手元のCore i7 CPUを使った開発機ではカーネルやGCCといった大規模なソフトウェアをmakeの-jオプションを使って並列度を上げてビルドしようとするとCPUの温度が危険なほど上昇してしまいます。この問題が起きるのは「大規模なソフトウェアを並列度を上げてビルドする」というかなり限定された場合のみなので、並列度を下げることで問題を回避することは可能ですが、そうするとビルドにかかる時間がずいぶん長くなってしまって不便です。
何かいい方法はないかなぁ……、と考えているうちにCpufreqと呼ばれる動作周波数数管理機能のことを思い出しました。
Linuxの動作周波数管理機能
去年買ったCore i7の動作周波数は2.93GHzだったように、最近のCPUの動作周波数は2GHzを大きく超えています。CPUの動作周波数は高ければ高いほど、1秒あたりの処理回数が増えて性能は向上しますが、その分、発熱も大きくなると共に消費電力も大きくなり、ノートPCのような環境では使いづらくなってしまいます。
そのような問題に対処するために、最近のCPUでは、従来は固定だった動作周波数を可変にして、必要に応じて動作中でも動作周波数を変更できるようになっています。この機能を使えば、CPUの処理がそれほど必要ない文字入力等の対話的な作業の際には周波数を下げて消費電力や発熱を抑制し、コンパイルや動画再生といったCPUパワーが必要な作業の際には周波数を上げて処理能力を高め、ユーザにストレスを感じさせずに電力消費や発熱を抑制することが可能です。
ただし、CPUの側からすると、ユーザの入力を待っている間でもプログラム中のウエイトループを全速力で回っているだけなので、作業負荷の高低を監視するにはOS側の協力が必要となります。Linuxで動作周波数を管理しているのはカーネル内部のcpufreq とgovernor と呼ばれるドライバで、ユーザ領域で動作するcpufreqd がそれらを制御しています。
動作周波数設定用ドライバcpufreq
CPUの動作周波数を変更するにはカーネルレベルの権限が必要となるので、そのための機能はカーネル内部のcpufreq というドライバが担っています。また、動作周波数の変更方法はメーカやCPUによって異なるため、cpufreqドライバはいくつかの種類が用意されています。
現在、手もとで使っているlinux-2.6.38.4カーネルでは以下のようなcpufreqドライバが用意されていました。
acpi-cpufreq.ko Core i7を含む、新しい世代のIntel製CPU用のドライバ。
p4-clockmod.ko Pentium 4世代のIntel製CPU用ドライバ。あまり細かい制御はできない。
speedstep-centrino.ko Pentium-M世代のIntel製CPU用ドライバ。機能はacpi-cpufreq.koにマージされたので現在はdeprecated(使用すべきでない)とされている
powernow-k8.ko AMD製CPU用のドライバ。
これら以外にもx86互換CPUで主に組み込み向けに使われているAMD ElanやVIA Cyrix、あるいはx86系以外でもARMやSuperH、BlackfinといったさまざまなCPU用にcpufreqドライバは開発されているようです。
動作周波数制御用ドライバgovernor
cpufreqドライバがそれぞれのCPUに応じた方法で動作周波数を指定された値に設定する機能を担当しているのに対し、状況に応じて動作周波数をどのような値にするかを管理しているのがgovernor と呼ばれるドライバです。
状況に応じて動作周波数をどう選択するかにはさまざまな方針が考えられるため、governorドライバも複数の種類が用意されており、linux-2.6.38.4ではperformance 、powersave 、ondemand 、conservative 、userspace という5種類のgovernorが用意されています。それぞれのgovernorの動作周波数設定方針は以下の通りです。
performance CPUの状況にかかわらず、指定された範囲で最大の動作周波数を選ぶ
powersave performanceの反対で、指定された範囲で最小の動作周波数を選ぶ
ondemand CPUの負荷状況を見ながら、指定された範囲内で動作周波数を上下させる
conservative ondemandと同じだが、周波数の変動をゆっくりめに行う
userspace CPUの周波数の設定は常にユーザ領域のソフトウェアから行う
userspace以外のgovernorでは動作周波数を0から100%の間の一定の範囲で指定し、performance governorはそのうちの最大値を、powersave governorはそのうちの最小値を、それぞれ選ぶようになっています。
一方、ondemand governorは、ごく短い間隔(デフォルトでは10ms)ごとにCPUの使用率を調べ、その期間中のCPU使用率があらかじめ設定されている閾値(デフォルトでは95%)を超えていれば、指定された範囲内で動作周波数を上昇させ、閾値以下ならば動作周波数を下降させるように働きます。
conservative governorは、周波数を上下させる考え方はondemand governorと同じですが、ondemand governorが条件を満せば即座に周波数を変更するのに対し、周波数の変化が多少ゆっくりめになるように設定されています。
周波数の変動はそのまま消費電力の変動につながりますが、消費電力の変動は、急峻よりもなだらかな方がバッテリーに優しいので、ノートPCのような環境ではconservative governorが適しているそうです。
userspace governorは他のgovernorとは異なり、自分で周波数を設定することはせず、ユーザ領域のソフトウェアや手動設定で指定された周波数をcpufreqドライバに反映させる役割を持ちます。
これらのgovernorは必要に応じて切り替えることが可能 で、普段はpowersave governorで動作周波数を最低限に抑えてバッテリーの消費を抑えつつ、動画再生用のソフトウェアを動かす時にはondemand governorに切り替えて必要なレベルまで動作周波数を高める、といった使い方が可能です。システムの動作状況を監視して、このような切り替え処理をするのがcpufreqd の役割です。
動作周波数管理デーモンcpufreqd
cpufreqやgovernorがカーネルレベルで動作するのに対し、cpufreqd はユーザ領域で動作して、あらかじめ指定された条件に応じてgovernorを切り替え、それを通じてcpufreqでCPUの動作周波数を調整します。
cpufreqdの設定ファイルは/etc/cpufreqd.conf です。このファイルには、システムがどのような状況になればどのgovernorを適用するのかというルールを複数記述でき、cpufreqdはシステムの動作状況を監視しながら、それらのルールの中からもっとも状況に適合するものを選んで、そのルールに従ってgovernorを切り替えます。
もともとcpufreqdは、AC電源使用時とバッテリー使用時でCPUの動作速度を自動的に切り替えて、バッテリー使用時の動作時間を少しでも延ばそう、というニーズから生まれてきたソフトウェアなので、電源回りの状況(電源はACかバッテリーか、バッテリーの残量はどれくらいか、等)をACPIやAPMを使って監視する機能を豊富にもっています。
加えて最近では、CPUの負荷状態や特定のソフトウェアの動作の有無、lm-sensorsを経由した温度情報なども監視することができるようになっており、一定の温度を上まわれば動作周波数を落して発熱を抑える、といった設定も可能です。そこで今回は、cpufreqdにCPUの温度を監視させ、高温時には動作周波数を抑えることにしました。
cpufreqdの設定
cpufreqdのソースコードに付属しているサンプルのcpufreqd.confにはlm-sensorsを用いた条件の記述例が無いので、どう記述したものかとしばし悩みましたが、manページ等を参考にとりあえず最低限の設定のみを施した設定ファイルを作ってみました。
リスト1 cpufreqd.confの記述例
[General]
pidfile=/var/run/cpufreqd.pid
poll_interval=2
verbosity=4
enable_remote=1
remote_group=root
[/General]
[sensors_plugin]
sensors_conf=/etc/sensors3.conf
[/sensors_plugin]
[Profile]
name=Performance
minfreq=100%
maxfreq=100%
policy=performance
[/Profile]
[Profile]
name=OnDemand
minfreq=20%
maxfreq=80%
policy=ondemand
[/Profile]
[Rule]
name=Default
sensor=Core 0:0-60
profile=Performance
[/Rule]
# CPU Too hot!
[Rule]
name=CPU Too Hot
sensor=Core 0:61-100
profile=OnDemand
[/Rule]
ご覧のようにcpufreqd.confは、[General]...[/General]や[Rule]...[/Rule]といった部分に分かれ、各部ごとに必要な設定を記述していく形式になっています。
[General]部にはcpufreqd全体の動作に関する設定を行います。今回指定したのは、
cpufreqdが動作中か否かを示すプロセスIDを記録するファイルの位置(pidfile=/var/run/cpufreqd.pid)
cpufreqdが指定された条件を監視する間隔(poll_interval=2で2秒ごと)
表示するメッセージの多さ(verbosity=4 7が最も詳しい)
cpufreqdに外部からの接続を許可するか(enable_remote=1)
許可する場合は接続元のグループをどうするか(remote_group=root)
といった項目です。
[sensors_plugin]部には、前回紹介したlm-sensorsの設定ファイルの位置を記述しておきます。
[Profile]部には適用すべきgovernorの設定を記述します。それぞれの設定は name=.. を使って区別され、今回は"Performance"というProfileでperformance governorを指定してCPUを全速(100%)で動作させるように、"OnDemand"というProfileでon demand governorを指定して、20%~80%の範囲で必要に応じて動作周波数を変化させるようにしてみました。
最後の[Rule]部が、それぞれの状況の定義とその状況で適用すべきProfileの指定になります。今回は"Default"と"CPU Too Hot"という2つのルールを設定しました。
"Default"というルールでは、lm-sensorsが返すCPUの温度が60℃以下(sensor=Core 0:0-60)の場合、"Performance" Profileを適用する(profile=Performance) 、という設定にしています。Performance Profileの設定では、"maxfreq=100%"の指定で動作周波数を最大値にしているので、このルールが適用されるとCPUは全速の2.93GHzで動作するはずです。
一方、CPUの温度が61℃以上(sensor=Core 0:61-100)になった場合用に"CPU Too Hot"というルールを定義しました。このルールでは、"OnDemand"のProfileを適用して(profile=OnDemand) 、動作周波数の上限を最大値の80%(約2.39GHz)に制限し、CPUの発熱を抑制します。
このような設定で発熱問題が解決するか、さっそく試してみました。
動作周波数管理による発熱抑制
前述のように、Linuxの動作周波数管理はカーネル領域で動作するcpufreqとgovernor、ユーザ領域で動作するcpufreqdの3者が組み合わさって機能します。そこで、まずカーネル領域で動作するcpufreqとgovernor用のモジュールドライバをロード しておきます。
# for i in acpi-cpufreq cpufreq_ondemand mperf cpufreq_stats freq_table ; do
# modprobe $i
# done
ここで読み込んだモジュールドライバのうち、acpi-cpufreqがCore i7用のcpufreqドライバで、cpufreq_ondemandがon demand governorになります。残りのmperfやcpufreq_stats, freq_tableは動作周波数の現状や変遷を表示する機能を提供するドライバです。
なお、performace governorは現在使っているカーネルではモジュールドライバではなくカーネル組み込みになっているので、上記操作ではロードしていません。
次にcpufreqdを起動 します。今回は動作状況の確認のためにフォアグラウンドで動かし続け(-Dオプション)ログも最大限出力する(-V7)ようにオプションを指定しました。
# cpufreqd -D -V7
get_kversion : kernel version is 2.6.38.4-plamo64.
get_kversion : kernel version is 2.6.
get_cpu_num : found 8 CPUs
main : Limits for cpu0: MIN=1197000 - MAX=2927000
main : Limits for cpu1: MIN=1197000 - MAX=2927000
....
rule_score : Rule "Default": sensor matches.
update_rule_scores : Rule "Default" score: 101%
update_rule_scores : Considering Rule "CPU Too Hot"
sensor_evaluate : called 61.000-100.000 [Core 0:46.000]
update_rule_scores : Rule "CPU Too Hot" score: 0%
cpufreqd_loop : New Rule ("Default"), applying.
cpufreqd_set_profile : Profile "Performance" set for CPU0
cpufreqd_set_profile : Profile "Performance" set for CPU1
cpufreqd_set_profile : Profile "Performance" set for CPU2
...
cpufreqdは/etc/cpufreqd.confの [Rule]...[/Rule] 部に指定された条件を評価してそれぞれのルールにスコアを付け、もっともスコアの高いルールを採用するようになっています。今回指定した条件はCPUの温度が60℃以上か以下かという二択でしたので、負荷のかかっていない状態ではcpufreqdは60℃以下の場合の"Default"ルールを採用して、"Performance" Profileを適用し、CPUは全速(2.93GHz)で動作しています。
それではこの状態でCPUに負荷をかけてみます。別のターミナルを開いてカーネルのビルドを流してみると、CPUの温度は急上昇し、60℃を超えるとcpufreqdが"CPU Too Hot"ルールを採用して、"OnDemand" Profileを適用し、CPUの動作周波数が動的に切り替わるようになりました。
図1 CPU高温時の動作周波数調整
図1 のスナップショット例では動作周波数は"1.20GHz"と表示されていますが、この値は"CPU Too Hot"ルールが採用されている間は1.20GHzから2.39GHzの間で変化し、"Default"ルールが採用されると2.93GHzになります。
cpufreqd.confで指定したminfreq=20%に従うと、2.93GHz×0.2=0.586GHzになるはずですが、Core i7のハードウェア的な動作周波数の下限が1.20GHzなので、その値が下限になっています。
この状態でカーネルのビルドを何度か実行してみたところ、CPUの温度は最高でも70℃程度に収まって、80℃を超えた際の警告音は聞かずにすみました。また、ビルドにかかる時間を計測したところ、cpufreqdを動かさず常に2.93GHzで動作している時は約9分かかっていたのに対し、cpufreqdを動かして動作周波数を変動させた場合は約10分40秒という結果になりました。9分(540秒)の2割増しで約10分50秒(648秒)ですから、ondemand governorで動作周波数の上限を最大値の8割にしたことにより、性能もほぼ2割減程度になっているようです。
カーネルのように並列コンパイルが効果的なソフトウェアの場合、コンパイル作業の並列度を下げると作業にかかる時間も2倍や4倍になりますから、並列度は保ったままで動作周波数を下げてやる方が作業時間への悪影響は少ないでしょう。もう少しcpufreqd.confを調整してやれば、この夏はcpufreqdで乗り越えられそうです。