続・HyperThreadingとAMD Bulldozerとカーネルスケジューリング

はじめに

本稿は、Intel HyperThreadingTechnologyとAMD Bulldozerアーキテクチャとカーネルスケジューリングの関係をテーマとしている。

前回の記事 にて、FreeBSDはBulldozerアーキテクチャのCPUのトポロジーを正しく認識していない、という点を指摘した。

今回は、その問題の改善を試みる。

ちなみに、この種類の作業は当然のことながら実機がないとできない。前回の記事でのOpteronのCPUトポロジーは、知人に取得してもらったものであり、記事執筆時点では私はBulldozerアーキテクチャのマシンを所持していなかった。

……ええ、買いましたよ。この記事のためだけに。

まさか今のタイミングでAMDのCPUを買う羽目になるとは思ってなかった。この一年くらいは、Core iシリーズのマシンばかり買っていたのだが(何台買ったのかは聞かないで欲しい)。

しかし、気になったら調べておきたいし、調べるには実機使うしかないじゃん?

というわけで、今回の記事のターゲットCPU(APU)は、AMD A10-6800Kである。

CPUトポロジーの検出

何はともあれ、CPUトポロジーの検出を正しく行うようにしなければならない。

A10-6800Kは、ふたつのPiledriver(Bulldozerの後継アーキテクチャ)モジュールを搭載した、4コアのAPUである。Piledriverモジュールはそれぞれ2MBのL2 キャッシュを内蔵している。HTTはなく、Piledriverモジュール内のコアは、独立した L1キャッシュを内蔵している。

L3キャッシュは内蔵していない。L3キャッシュを内蔵していない替わりなのか、同世代のCore iシリーズよりも高速なDDR3-2133規格のメモリまでサポートしている(まあ、高速なメモリはその文高価なのだが)。また、動作クロックも定格4.1GHzと、Core iシリーズよりも高速である。

そんな差異がありつつも、ベンチマークではCore iシリーズに全く勝てていないので、この世代のAMD(つまりBulldozer)に関しては、負けを認めてもらうしかない。ところが、JaguarベースのAPUをPS4とXbox 360に供給していることもあり、AMDはそこそこ儲かっているのではないか。だからPC向けのCPUはわりとどうでもいいのかもしれない。

話が逸れた。CPUトポロジーの検出の話だ。

FreeBSD-CURRENT(r281991)では、A10-6800KのCPUトポロジーは以下のように認識される。

kern.sched.topology_spec: <groups>
 <group level="1" cache-level="0">
  <cpu count="4" mask="f,0,0,0">0, 1, 2, 3</cpu>
  <children>
   <group level="2" cache-level="2">
    <cpu count="4" mask="f,0,0,0">0, 1, 2, 3</cpu>
   </group>
  </children>
 </group>
</groups>

4つのコアがL2キャッシュを共有しているものと認識されているが、実際にはL2キャッシュを共有する2つのコアからなるモジュールが 2つ搭載されているので、これは誤りである。

そこで、sys/amd64/amd64/mp_machdep.c内の処理を修正し、BulldozerのCPUトポロジーを正しく認識するようにしてみた。修正後は以下のように認識するようになった。L2キャッシュを搭載する2つのSMTが、2モジュール存在する構成である。

kern.sched.topology_spec: <groups>
 <group level="1" cache-level="0">
  <cpu count="4" mask="f,0,0,0">0, 1, 2, 3</cpu>
  <children>
   <group level="2" cache-level="0">
    <cpu count="4" mask="f,0,0,0">0, 1, 2, 3</cpu>
    <children>
     <group level="3" cache-level="2">
      <cpu count="2" mask="3,0,0,0">0, 1</cpu>
      <flags><flag name="THREAD">THREAD group</flag><flag name="SMT">SMT group</flag></flags>
     </group>
     <group level="3" cache-level="2">
      <cpu count="2" mask="c,0,0,0">2, 3</cpu>
      <flags><flag name="THREAD">THREAD group</flag><flag name="SMT">SMT group</flag></flags>
     </group>
    </children>
   </group>
  </children>
 </group>
</groups>

今回行った修正では、Bulldozerしか想定しないコード(具体的には物理コアに大して論理コアが2つと決め打ちにしている)となっているため、以下のケースで問題が生じる。

  • LlanoはFam 10hだが、Bulldozerではない
  • 省電力系SoCやPS4に搭載されているJaguarは、Bulldozerと異なり、4コアで L2キャッシュを共有する

また余談になるが、CPUトポロジーの検出において、AMD, Intelのどちらの場合でも、L3キャッシュの共有情報がCPUトポロジーに反映されないことが分かった。

ベンチマーク結果

UnixBench 5.1.3とkernelのbuildの時間を計測した。kernelのbuild時間は以下の方法で計測した。/tmp/はtmpfsとし、ccacheは用いていない。kernelのbuildは1回のみの計測のため、あくまで参考と考えて欲しい。

# date >> /tmp/build.txt ; make -j6 buildkernel KERNCONF=GENERIC-NODEBUG ; date >> /tmp/build.txt

CPUトポロジー修正前の結果

UnixBench 5.1.3:

System Benchmarks Index Values               BASELINE       RESULT    INDEX
Dhrystone 2 using register variables         116700.0   18268167.3   1565.4
Double-Precision Whetstone                       55.0       3364.3    611.7
Execl Throughput                                 43.0       2215.5    515.2
File Copy 1024 bufsize 2000 maxblocks          3960.0     203463.5    513.8
File Copy 256 bufsize 500 maxblocks            1655.0      81806.7    494.3
File Copy 4096 bufsize 8000 maxblocks          5800.0      57863.8     99.8
Pipe Throughput                               12440.0    1001231.1    804.8
Pipe-based Context Switching                   4000.0     170021.8    425.1
Process Creation                                126.0       5924.7    470.2
Shell Scripts (1 concurrent)                     42.4       7212.0   1700.9
Shell Scripts (8 concurrent)                      6.0       1847.4   3078.9
System Call Overhead                          15000.0     817823.9    545.2
                                                                   ========
System Benchmarks Index Score                                         650.5

kernelのbuild時間: 680秒

CPUトポロジー修正後の結果

UnixBench 5.1.3:

System Benchmarks Index Values               BASELINE       RESULT    INDEX
Dhrystone 2 using register variables         116700.0   18297288.1   1567.9
Double-Precision Whetstone                       55.0       3367.0    612.2
Execl Throughput                                 43.0       2227.9    518.1
File Copy 1024 bufsize 2000 maxblocks          3960.0     171241.3    432.4
File Copy 256 bufsize 500 maxblocks            1655.0      83807.8    506.4
File Copy 4096 bufsize 8000 maxblocks          5800.0      51990.0     89.6
Pipe Throughput                               12440.0    1009910.8    811.8
Pipe-based Context Switching                   4000.0     153763.7    384.4
Process Creation                                126.0       5907.4    468.8
Shell Scripts (1 concurrent)                     42.4       7158.4   1688.3
Shell Scripts (8 concurrent)                      6.0       1842.0   3070.1
System Call Overhead                          15000.0     820000.9    546.7
                                                                   ========
System Benchmarks Index Score                                         631.8

kernelのbuild時間: 735秒

考察

結論としては、CPUトポロジーを正しく認識させたことにより、むしろ性能が低下している。

原因を考えてみよう。

まず、ほとんどの処理は整数演算であり、モジュール内のコア間で FPUを共有していることがそれほどデメリットになっていない可能性である。UnixBenchでの浮動小数点演算の結果は、わずかだが向上しており、浮動小数点の計算については変更の効果は出ている。

BulldozerではL1キャッシュは独立しているので、L1キャッシュを共有する HTTほどは差が出ないのかもしれない。

また、異なるモジュールにスレッドを割り当てると、異なるL2キャッシュにスレッドを割り当てることになる。ベンチマークやコンパイルのような同じ処理を並列して行う場合、キャッシュの利用効率が落ちることになる。ただし、AMD FXシリーズはL3キャッシュを搭載しているため、この問題は緩和されるのかもしれない。

C6ステートの罠

Windows 7用に提供されたBulldozer対応修正は2つあり、それぞれ以下の問題への対策となっている。

  • KB2645594 (Windows 7とWindows Server 2008 R2がBulldozerアーキテクチャの『Bulldozer Module』に対して適切にスケジューリングを行えないため,いくつかのアプリケーションでシステム性能の低下を招く可能性がある)
  • KB2646060は (Bulldozer Moduleが頻繁にC6ステートに入ってしまい,結果,マルチスレッド化があまり進んでいない環境で性能が低下する)

このうち、KB2645594はこれまで述べてきたスケジューリングの問題を解消するものである。

KB2646060について以下で補足する。BulldozerではCPUに新たに C6ステートという状態が導入された。OS側がC6ステートの扱いに対応していない場合、UEFIメニューでC6ステートが有効にされていると、CPUはモジュールの負荷が低い場合自動的にクロックを1.4GHzまで落してしまう。ひとつのモジュールの片方のコアだけを利用するようなスケジューリングを行うと、モジュールの負荷は平均して高くないとみなされて、頻繁にC6ステートに入ってしまうようなのだ。この不具合を修正するのがKB2646060である。

言い替えれば、C6ステートの扱いに対応していないOSの場合は、UEFIメニューでC6ステートを無効にしなければならない。

改めてベンチマークの結果

UEFIメニューで、「Core C6 State」の項目を「Disable」にした上で、改めてベンチマークを計測する。

CPUトポロジー修正前の結果

System Benchmarks Index Values               BASELINE       RESULT    INDEX
Dhrystone 2 using register variables         116700.0   23624690.7   2024.4
Double-Precision Whetstone                       55.0       4454.5    809.9
Execl Throughput                                 43.0       2576.9    599.3
File Copy 1024 bufsize 2000 maxblocks          3960.0     182212.0    460.1
File Copy 256 bufsize 500 maxblocks            1655.0      94960.3    573.8
File Copy 4096 bufsize 8000 maxblocks          5800.0      55109.0     95.0
Pipe Throughput                               12440.0    1297949.8   1043.4
Pipe-based Context Switching                   4000.0     192096.2    480.2
Process Creation                                126.0       6344.5    503.5
Shell Scripts (1 concurrent)                     42.4       7760.0   1830.2
Shell Scripts (8 concurrent)                      6.0       1800.5   3000.8
System Call Overhead                          15000.0    1064837.1    709.9
                                                                   ========
System Benchmarks Index Score                                         733.5

kernelのbuild時間: 737秒

CPUトポロジー修正後の結果

System Benchmarks Index Values               BASELINE       RESULT    INDEX
Dhrystone 2 using register variables         116700.0   23665393.4   2027.9
Double-Precision Whetstone                       55.0       4456.7    810.3
Execl Throughput                                 43.0       2576.1    599.1
File Copy 1024 bufsize 2000 maxblocks          3960.0     205390.8    518.7
File Copy 256 bufsize 500 maxblocks            1655.0      94015.5    568.1
File Copy 4096 bufsize 8000 maxblocks          5800.0      53823.9     92.8
Pipe Throughput                               12440.0    1305173.0   1049.2
Pipe-based Context Switching                   4000.0     154689.4    386.7
Process Creation                                126.0       5891.7    467.6
Shell Scripts (1 concurrent)                     42.4       7602.8   1793.1
Shell Scripts (8 concurrent)                      6.0       1803.1   3005.2
System Call Overhead                          15000.0    1061759.1    707.8
                                                                   ========
System Benchmarks Index Score                                         720.2

kernelのbuild時間: 742秒

改めて考察

C6ステートを無効にすることで、UnixBenchの結果は大幅に向上した。特に、シングルスレッド性能が上がっている。モジュールの片方のコアだけに負荷がかかっていた時にクロックが下がることがなくなったため、これは妥当な結果であるといえる。

CPUトポロジーの修正前後での傾向は、C6ステート有効時と同じである。全体としては、CPUトポロジーの修正を行わないほうがベンチマークの結果がよい。浮動小数点演算については、わずかだが修正後のほうが結果がよい。

この原因としては、L2キャッシュを有効利用できていない可能性がもっとも有力ではないかと考えている。

kernelのbuild時間については、C6ステートを無効にすることで、カーネル修正前後ともに、むしろ遅くなっている。UnixBenchの結果でも並列処理のスコアが落ちている。可能性としては、すべてのコアを全力で動作させた結果放熱が追い付かず温度が上がって自動的にクロックダウン(ダウンさせたのがどの機構なのかは分からない)というのがもっともらしい説明だが、正確な原因は不明である。これについては、build中のCPUのクロックや状態をモニタするなどの方法で調べる必要があるだろう。

まとめ

FreeBSDでは、AMD BulldozerのCPUトポロジーを正しく認識していない。この問題に対する、カーネルの変更を行い、トポロジーを正しく認識するようにした。

しかし、変更前後でのベンチマークの結果を比較すると、トポロジーを正しく認識したほうがむしろ悪い結果になった。

この原因としては、整数演算を主とした場合、Bulldozerモジュール内のふたつのコアは、L2キャッシュを共有しつつ独立したL1キャッシュを持つため、ふたつのスレッドを同じモジュールにスケジュールしても、それほど性能低下が問題にならないのではないかと考える。

また、C6ステートに対応していないOSを使う場合は、UEFIメニューでC6ステートを無効にして利用するべきである。

AMDは次世代アーキテクチャであるZenにおいて、Bulldozerのような構成を捨てると同時に、一般的な意味でのSMTを導入すると発表している(詳細は未公開である)。Zenアーキテクチャにおいては、改めてトポロジー検出とスケジューリング戦略を見直す必要があるだろう。

参考までに

今回行った修正内容は以下である。/usr/src/sys/amd64/amd64/mp_machdep.c に対するパッチになっている。

--- mp_machdep.c.dist 2015-05-13 11:44:19.000000000 +0900
+++ mp_machdep.c      2015-05-13 11:44:19.000000000 +0900
@@ -210,6 +210,8 @@
                      continue;
              cpu_cores++;
      }
+     cpu_logical = 2; /* Llano: 1, Jaguar: 4 */
+     cpu_cores = cpu_cores / cpu_logical;
 }

 /*
@@ -412,8 +414,13 @@
      /*
       * Both HTT and multi-core.
       */
-     return (smp_topo_2level(CG_SHARE_L2, cpu_cores,
-         CG_SHARE_L1, cpu_logical, cg_flags));
+     if (hyperthreading_cpus) {
+             return (smp_topo_2level(CG_SHARE_L2, cpu_cores,
+                                     CG_SHARE_L1, cpu_logical, cg_flags));
+     } else {
+             return (smp_topo_2level(CG_SHARE_NONE, cpu_cores,
+                                     CG_SHARE_L2, cpu_logical, cg_flags));
+  }
 }

 /*
記事執筆者: 木本雅彦
記事公開日:2015年06月24日