12コアを1つずつ固定して測ったら80%の差が出たが、コアの個体差ではなかった
Snapdragon X Elite X1E80100 の12コアに同じ整数ループを1つずつ固定して回したところ、最小値で0.1369秒から0.2469秒まで、80.4%の開きが出た。コアに当たり外れがあるのかと最初は思ったけれど、追いかけていくと理由は別のところにある。正直なところ、最初に疑ったのは製造ばらつきのほうだった。
2026-08-02 に Win32_Processor の値を読んだときは、12コア12論理プロセッサ、L2が36864KB、L3が0という構成に見えていた。個数は分かっても、その12個が同じ速さなのかまでは分からない。2026-06-02 に ProcessPoolExecutor で worker 数を振ったときも、8 worker と12 worker で頭打ちになる理由を、スケジューラ側の都合とコア側の都合に切り分けられないまま終わっている。1つずつ固定して測れば、そこがはっきりするはずだった。
固定できたことを毎回確かめてから測る
Windows のスケジューラ任せでは、どのコアで走るかをこちらで選べない。psutil の cpu_affinity で自分自身を1コアに縛ってから測ることにした。指定しただけで実際には載っていない場合があるので、GetCurrentProcessorNumber を測定の前後で読み、指示どおりのコアで走った回だけを数えている。
python scripts/core_affinity_bench.py --rounds 30 --json .bench/core_affinity_30.json
計測日は2026-08-05。機材は Surface Pro 11th Edition の Snapdragon X Elite X1E80100、Windows 11 Pro 26200 の ARM64 で、Python は 3.12.10 の ARM64 ビルド、psutil は 7.2.2 だ。1回あたり1200000回の整数ループを回し、配列は触らない。順番による有利不利を消すため、毎周コアの順序をシャッフルして30周した。
この機械は常用機で、測っている間も Defender や常駐サービスは止めていない。平均を取ると割り込まれた回に引きずられるので、同じコアを何周も回して最小値を「邪魔されなかったときの速さ」として採っている。
最初の結果は、きれいに番号順だった
| cpu | 最小 | 中央値 | 最大 | 最速比 |
|---|---|---|---|---|
| 0 | 0.2469 秒 | 0.4317 秒 | 0.7626 秒 | 1.804 |
| 1 | 0.2257 秒 | 0.3637 秒 | 0.6324 秒 | 1.649 |
| 2 | 0.1873 秒 | 0.3507 秒 | 0.4375 秒 | 1.368 |
| 3 | 0.1799 秒 | 0.3290 秒 | 0.8579 秒 | 1.314 |
| 4 | 0.1784 秒 | 0.3122 秒 | 0.5486 秒 | 1.303 |
| 5 | 0.1694 秒 | 0.2652 秒 | 0.7408 秒 | 1.238 |
| 6 | 0.1541 秒 | 0.2450 秒 | 0.4014 秒 | 1.126 |
| 7 | 0.1369 秒 | 0.2305 秒 | 0.3747 秒 | 1.000 |
| 8 | 0.2447 秒 | 0.3211 秒 | 0.4104 秒 | 1.788 |
| 9 | 0.1629 秒 | 0.3043 秒 | 0.3966 秒 | 1.190 |
| 10 | 0.1746 秒 | 0.2745 秒 | 0.3430 秒 | 1.276 |
| 11 | 0.1414 秒 | 0.2585 秒 | 0.3246 秒 | 1.033 |
固定に失敗した回は360回中0回で、指示したコアの上でちゃんと走っていた。ここは疑わなくてよさそうだ。
数字を眺めていて手が止まったのは、cpu0 から cpu7 までが階段のように並んでいた点だった。意外だったのはその並び方で、シリコンの個体差なら、速いコアと遅いコアが番号と関係なくばらけるほうが自然に思える。番号順に0.2469秒、0.2257秒、0.1873秒と落ちていく形は、製造ばらつきというより何か別の力が働いた結果に見えた。
もうひとつ、最小値の10%以内に収まった回が360回中22回しかない。ほとんどの回が何かに割り込まれている状態で、これを性能差として読むのは無理がある。
邪魔しているものを数えた
そこで、自分では何も走らせずに各コアの使用率だけを120秒ぶん採った。60秒ずつ2つの窓に分けたのは、偏りが時間帯によるものか、いつもそうなのかを見たかったからだ。
python scripts/core_load_profile.py --seconds 120 --json .bench/core_load_profile.json
| cpu | 窓1 | 窓2 | 通し |
|---|---|---|---|
| 0 | 100.0% | 100.0% | 100.0% |
| 1 | 100.0% | 100.0% | 100.0% |
| 2 | 100.0% | 100.0% | 100.0% |
| 3 | 100.0% | 100.0% | 100.0% |
| 4 | 74.3% | 70.7% | 72.5% |
| 5 | 60.7% | 57.3% | 59.0% |
| 6 | 42.1% | 33.7% | 37.9% |
| 7 | 25.5% | 18.3% | 21.9% |
| 8 | 17.7% | 9.4% | 13.5% |
| 9 | 9.6% | 4.9% | 7.2% |
| 10 | 5.9% | 2.5% | 4.2% |
| 11 | 2.9% | 1.1% | 2.0% |
きれいな階段になった。cpu0 から cpu3 までが100%で埋まり、cpu11 は2.0%しか使われていない。全体の平均は51.5%なので、均等にばらまけば全コアが50%前後になるはずのところ、実際には前のほうへ寄せられている。忙しい順に並べると窓1も窓2も 0,1,2,3,4,5,6,7,8,9,10,11 で、2つの窓で順位が完全に一致した。たまたまではなく、いつもこの向きに寄っている。
負荷の大きいプロセスは MsMpEng.exe が118.5%、System が59.2%、dwm.exe が53.1%、SearchIndexer.exe が41.0%だった。どれかが特定のコアに縛られているのかとも疑ったものの、affinity を読める範囲で確認したかぎり、いずれも12コア全部が許可されている。つまり誰かが cpu0 を指名しているのではなく、Windows がそこへ置いているという話になる。
背景の負荷と測定値の関係を見ると、中央値との相関係数は0.701まで出た。最小値との相関は0.459で、こちらは cpu8 が外れ値として効いている。
空いているコアだけで測り直した
cpu8 で詰まった。負荷は13.5%と軽いのに、最小値は0.2447秒と cpu0 並みに遅い。30周では、たまたま無傷の時間帯を1度も踏めなかっただけではないか。確かめるため、比較的空いている cpu8 から cpu11 の4つに絞って60周かけ直した。
python scripts/core_affinity_bench.py --cpus 8,9,10,11 --rounds 60
| cpu | 最小 | 中央値 | 最速比 |
|---|---|---|---|
| 8 | 0.1098 秒 | 0.3104 秒 | 1.000 |
| 9 | 0.1135 秒 | 0.3071 秒 | 1.033 |
| 10 | 0.1139 秒 | 0.2782 秒 | 1.037 |
| 11 | 0.1169 秒 | 0.2979 秒 | 1.065 |
差は6.5%まで縮んだ。そして cpu8 の最小値は0.2447秒から0.1098秒へ下がり、いちばん速いコアに変わっている。同じ石の同じコアが、周回数を増やしただけで倍以上速くなったことになる。最初の表で cpu8 を「遅いコア」と読んでいたのは、こちらの勘違いだった。1200000回を0.1098秒だから、毎秒10.93M回ぶん回っている計算になる。
測れたことと、測れていないこと
手元では、空いている4コアについて、邪魔が入らないときの速さが6.5%以内に収まったところまでしか言えない。cpu0 から cpu3 については何も言えていない。30周まわしても100%埋まったままで、無傷の時間帯を一度も踏めていないからだ。あの4つが遅いのか、単に順番が回ってこなかっただけなのかは、まだ分けられていない。
デュアルコアで高いクロックまで上がるという話も確かめられなかった。特定の2コアだけ速い、という兆候はこの測り方では出ていない。出ていないことと、無いことは別なので、そこは分けて書いておく。
パスをまたいだ比較にも気をつけたい。30周の表の cpu7 が0.1369秒、60周の表の cpu8 が0.1098秒でも、別の時間帯に別の負荷の下で採った値なので、cpu8 のほうが速いコアだと読むことはできない。同じ表の中でだけ比べている。
どう使うか
個人的にいちばん実用になったのは、常用機でシングルスレッドの時間を測るなら、コアを固定するだけでは足りない、という点だ。固定は効いていて、指示どおりのコアで走っている。それでも、そのコアが空いているかどうかで所要時間は倍近く動いた。番号の若いコアを選ぶと、いちばん混んでいる場所に自分から入っていくことになる。
短い処理を何十回も回して最小値を採る形にしたのは、この機械では平均が使いものにならなかったからだ。360回のうち22回しか無傷の時間帯を踏めていない以上、平均は「Defender がどれだけ動いていたか」を測っているのに近い。
ベンチマークの数字が番号順にきれいに並んだら、まず測っている対象を疑ったほうがいい。今回は、コアの性能を測っているつもりで、Windows のスレッド配置を測っていた。次にやるなら、負荷の載っていない状態を作れる時間帯を探して cpu0 から cpu3 を測り直したい。それができて初めて、12個が同じかどうかの話になる。