【HW】2ソケットマシンでゲームをする際のNUMA/パフォーマンスチューニング 


 家の環境は所有しているメモリの量の問題で未だにXeon E5 v2環境から移行できないのですが、同じラインのCPUを長く使っているとアップグレードや筐体目当てのパーツ取りなどでサーバを買ったりして余剰パーツが増えた結果、使ってない資材同士が結合して新しいマシンが生えてきたりすることはよくあることだと思います。(そして日常的にハードウェアを入れ替えてると配線の綺麗さとかどうでも良くなってきて写真のような配線になります)

 そんなわけで、余ったパーツから生えてきたマシンを現在デスクトップ機として使っているのですが、2ソケットマシンをデスクトップ用途として使うとちょくちょくNUMAについて考えないといけない時があったのでまとめてみます。

環境


余ったパーツから生えてきたマシンは以下です。
マザボ  X9DAi
CPU Xeon E5 2667v2 *2
メモリ DDR3 14900R 8*16 =128GB
グラボ GTX 1080
その他NVMeとか10G-NICとか

 マザボについてはオークションで安かったので買ったのですが、サーバー用途で使おうとするとIPMIがないのはやっぱり使いにくいので結局本番稼働することなく部屋の隅に積まれていいました。
 しかし、Core i5 6600Kでマシンを組んだときに微妙に物足りなかったので、試しにX9DAiと余り物でデスクトップ環境を作ったらいい感じだったのでそのままこれを使っています。

 この環境を使っていても9割のことは困らないのですが、ゲーム時にパフォーマンスがばらつく時があったので、その対処法のメモです。

システムブロック図


 Xeon E5系/Ryzenから先はPCI-EがCPU直結になり、(AMD系ではThreadripper/Epicは)拡張スロットによって接続先のCPUが違う(AMDの場合はMCMのコアが違う)というのはパソコンオタクには常識だと思いますが、X9DAiのシステムブロック図は以下のようになっています。




 このことを意識してゲーム用GPUをCPU1に直結しているSlot1のPCIEx16に接続していたのですが、Windows/Linuxどちらであっても「実行するプロセスの使用する拡張ボードを考慮して」のNUMAの制御は今の所できません。

 そのため、CPUの混み具合によっては「本来ならCPU1で実行してほしいのにCPU2で実行されてしまって拡張ボードにアクセスするにはQPI/Infinifablicを跨いでCPU1経由でアクセスしなければならない」ということが起きます。

 Xeon 5600系まではどちらのCPUであってもPCH経由でPCI-EにアクセスしていたのでどちらのCPUもどのボードに同じコストでアクセスできたのですが、QPIの限界が来てしまったのでCPUに直結された結果、このような微妙に不便なことが起きます。

Windows10でのNUMA


 Windows10でもNUMAを意識してくれるのですが、だいたい重要なプロセスがCPU1で実行されるため、ゲームなどは空いていることが多いCPU2で実行されてしまいます。しかし、そうなってしまうとGPUからは遠くなってしまうので、できればCPU1側で実行してもらいたいのです。

 見ている限りだと、CPU1とCPU2両方をフルに使って実行されるゲームはなく、すべてのコアで実行を許可しても、どちらかのCPUのすべてのコアのみで実行されていました。

CPU affinity


 もともとはNUMAの出始めの頃のサーバープロセスやDBプロセスのパフォーマンスチューニング手段でしたが、現在でも40GbE/100GbEなどの広帯域インターフェースを使う際にインターフェースに近いCPUでの実行を強制するために有効です。
 ちなみに、現在のOSは賢いので極端なプロセスでなければ設定しなくてもNUMAノードのCPUやメモリの空き状況を見て、余裕がある方によしなにNUMAをまたがないようにプロセスを振り分けてくれます。

ゲームプロセスにCPU affinityをかけたい


 プロセスのアフィニティー自体はタスクマネージャーの詳細の中にある「関係の設定」より簡単にかけることができるのですが、ゲームプロセスなどの場合、チート防止などの理由により起動後にアフィニティーを変えることができないことが多々あります。

 その場合どうすればいいかと実験したところ、アフィニティーがかかったプロセスから子プロセスを実行するとアフィニティーが伝搬するので、steamなどを
start /affinity ffff steam.exe

 で呼び出し、その伝播したSteamから更にゲームを実行すると最初からアフィニティーがかかった状態でゲームを開始できるようです。

 しかし、これでうまくいくものもあればメニューの変なところで止まるものもあったため、結局Steamにアフィニティーをかけるのはやめました。

CPU2を混雑させたい


 ゲームにアフィニティーをかけるのが問題であれば、自動選出の結果CPU1が優先的に選ばれるようにしたいので、逆にゲームに関係しないどうでもいいプロセスをCPU2側で実行させて意図的にCPU2を混雑させることにしました。

十進数でアフィニティーを確認する


 このあとのPowershellでまとめてアフィニティーを設定したいので、まずnotepadなどを起動し、タスクマネージャーからCPU2を使うように手動でチェックを入れ、アフィニティーを設定します。


 CPU2(ノード1)側をすべて使用するように設定したら、PowerShellでアフィニティーの値を取得します。もちろん関数電卓で直接やってもいいのですが、間違うことがあるのでこの方法が楽です。

PS W:\> Get-Process notepad|Select ProcessorAffinity

ProcessorAffinity
-----------------
4294901760

 4294901760というのは32スレッドのうち、奥の16スレッドを使用するという定義で、関数電卓に入れバイナリ値を見るとわかりやすいです。




 ちなみに、PowerShellだとアフィニティーの指定は10進数ですが、start /affinityだと16進数になるので、古くからあるstartコマンドで同じことをしようとすると
start /affinity ffff0000 otepad.exe

 になります。

邪魔なものをどかす


 これから実行するプロセスをすべて特定のアフィニティーをかけて実行したい場合、まずcmd.exeをアフィニティーかけた状態で立ち上げ、一度explorerを taskkill /im explorer.exe /fなどで殺したあと、explorerと実行するとcmd.exeにかかっていたアフィニティーでデスクトップ環境が立ち上がります。

 そのエクスプローラから実行されたプロセスもアフィニティーが伝搬していくので、デスクトップのアイコン等をクリックして実行してもアフィニティーがかかった状態で実行されていきます。

 停止できないプロセスについては、管理者権限で実行したpowershellで
foreach($p in "SearchIndexer","OfficeClickToRun","sqlservr","sqlwriter"){ ForEach($PROCESS in GET-PROCESS $p) { $PROCESS.ProcessorAffinity=4294901760} }

 などと実行するとアフィニティーをまとめて変更できます。ただ、svchostやdwmなどはアフィニティーをかけるとOSが不安定になることがあるので、変更しないほうが良いようです。また、CrystalDiskMarkなどもアフィニティーがかかっている状態だと正しく実行できませんでした。

 また、どのコアでどのプロセスが暴れているかを見つけるには Process Explorer を使うといい感じに見つけられます。




 大体の場合、ブラウザ(ChromeやFirefoxなど)をCPU2側で実行しておけば(特にマシンをシャットダウンせずに放置するような環境の場合)メモリ使用量などの関係で「CPU2は混んでいる」となるようです。

ゲームを実行してみる


 アフィニティーのかかっていない状態のSteamなどからゲームを起動します。その後、CPU1側が優先的に使われるようになったら成功です。




 それでもまだCPU2が使われる場合はCPU2のしばきが足りないのでいろいろ実行してください。

 CPU2側でゲームを実行されると、特にオープンフィールドなゲームでマップが広くメモリ使用量が多いゲーム(GRWとかArkとか)を実行すると、CPU2側からQPI越しにGPUになんか転送してるんだろうなというような一瞬のフリーズが起こることがちょくちょくありました。しかし、CPU1側で実行を強制するとそれがだいぶ軽減されました。
 また、ブラウザをCPU2側で実行するとどれだけブラウザが重くなってもゲームに影響しないので、それはそれで2ソケットの使い分けとして便利な気がします。
 そもそもXeonはゲームするようなプロセッサではないというのは尤もですが…。

 マシンを新しくしようかどうか悩むところです。

[ コメントを書く ] ( 5 回表示 )   |  このエントリーのURL  |  $star_image$star_image$star_image$star_image$star_image ( 0 / 0 )  |  

| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 進む> 最後へ>>