Minecraftのサーバーは何コア使えるか:チャンク生成のGPU化、モブ処理のGPU化、Paper・Folia・ShreddedPaperの比較
コアを増やせば速くなるのかを、1台のPCで測った。効いたのはマルチコア化で、GPUは地形生成でだけ上乗せになった。Foliaはプレイヤーが集まると1コアに戻る
Minecraft Java Edition 26.1.2のサーバーを使い、4つのことを同じPCで測った。1つ目はチャンク生成である。本家はCPUを約1コアしか使わず、1秒に22.8チャンクだった。C2MEを入れると約11.6コアに広がって15.1倍になり、GPUで計算するアドオンを足すと20.9倍になった。2つ目はモブの押し合いと経路探索で、普通の数ではGPUはCPUに勝てなかった。3つ目はマルチスレッドのサーバーの比較である。Foliaは、プレイヤーが1,536ブロック以内に集まると1つの地域にまとめ、TPSが0.25まで落ちた。これはPaperより遅い。ShreddedPaperは、集まっていてもTPS 17台を保った。4つ目として、処理の種類ごとにコアを分けた場合の上限を求めた。村人の頭脳の処理が本流の7割を占めるため、上限は約1.5倍だった。最後に、集まったときの主な重さだった村人の探索に小さな改造を当てると、集まったFoliaは約11倍、Paperは約1.6倍速くなり、村人の就職の早さは変わらなかった。
| 方法 | PCはi5-14600KF(14コア20スレッド)、メモリ32GB、RTX 3060 12GBで、Windows 11とJava 25を使った。チャンク生成は、Chunkyで半径1,024ブロック(16,641チャンク)を作り、4つの構成を交互に3回ずつ測った。地形が本家と同じかは、中心の625チャンク(6,144万ブロック)をブロック単位で突き合わせた。本家も同じシードで毎回少し違う地形を作るので、本家どうしのずれを基準にした。モブの実験では、本家の押し合いの式と、格子に分けて近くを探すやり方を、CPU(numba)とGPU(CuPyで書いたCUDA)で同じように実装した。サーバーの比較では、16か所にボットを2体ずつ置き、村人を/summonで出して、各サーバーが報告するTPSと使ったコア数を記録した。地点の間隔は1,920ブロックと256ブロックの2通りにした。Paperの本流スレッドの内訳は、Javaに入っている記録機能JFRで120秒ずつ4回取り、処理の種類ごとに数えた。 |
|---|---|
| 結果 | チャンク生成の速さは、本家相当が1秒に22.8、C2MEが343(外れ値1回を除く)、C2MEとGPUが477だった。使ったコアは本家相当が1.1、C2MEが11.6だった。地形の形(地面・液体・空気)のずれは、本家どうしで0.0076%、本家とGPU版で0.0146%だった。GPU版のほうが大きく、地表付近で差が目立った。押し合いでは、GPUがCPUの全コアに勝つのは約1万6千匹からで、float32では誤差が最大0.23に達した。経路探索は、全員が同じプレイヤーを追う場合、1回の共通の計算(フローフィールド)にするとCPU 1コアで0.16ミリ秒だった。1匹ずつA*で探すと、1,000匹で18.5ミリ秒かかった。村人4,800匹で比べると、散らばった配置ではFoliaとShreddedPaperがどちらもTPS 20近くを保ち、Paperは3前後だった。256ブロック間隔に集めると、Foliaは1地域でTPS 0.24〜0.26、ShreddedPaperは17.3〜17.7、Paperは2.5〜3.6だった。 |
| 知見 | マルチスレッドのサーバーを選ぶときに効くのは、プレイヤーがどれだけ離れて遊ぶかである。Foliaの初期設定では、1,536ブロック離れた2人は同じ地域にまとめられ、1,920ブロック離れると分かれた。同じ町の近くに人が集まるサーバーでは、Foliaに移しても速くならず、かえって遅くなった。もう1つは、GPUが効く範囲の狭さである。GPUが上乗せになったのは地形の計算だけで、モブの処理では、データを送る固定費(約0.7ミリ秒)がある数千匹まではCPUのほうが速かった。経路探索で大きく効いたのは、GPUではなく、計算のやり方を変えることだった。 |
| 限界 | どの条件も1台のPCで2〜3回ずつしか測っていない。ボットも同じPCで動いており、0.3〜0.6コアを使っていた。負荷は村人に偏っている。職業ブロックもベッドもない村人はそれらを探し続けるので、頭脳の処理の割合は最悪に近い値になっていると考えられる。取引所やレッドストーン装置が中心のサーバーでは、内訳は違うはずで、これは測っていない。Foliaの地域の分け方の設定(grid-exponent)を変えた場合も試していない。Canvasは入手できず、比べていない。Bを入れても、Foliaの1地域はBを入れたPaperより1回の更新が約2.5倍遅いままで、残りの差の原因は調べていない。モブのGPU実験は本家の外で式を再現したもので、Javaのサーバーに組み込んだときの通信や同期の手間は入っていない。 |
チャンク生成
| 構成 | 1秒あたりのチャンク数 | 本家比 |
|---|---|---|
| 本家相当(Fabricのみ) | 23.4 / 23.2 / 21.8 | 1.0倍 |
| C2ME | 355.4 / 331.4(1回目の136.4は外れ値) | 15.1倍 |
| C2ME + ScalableLux | 362.9 / 338.9(1回目の188.4は外れ値) | 15.4倍 |
| C2ME + OpenCL(GPU) | 483.1 / 457.6 / 489.8 | 20.9倍 |
外れ値とした2回は、本家の構成が止まるときの保存(6〜7分かかる)の直後に走った回である。最初の計測では、この保存が終わる前に次の構成を始めてしまい、計測が重なった。その回は捨てて取り直した。
地形は本家と同じか
| 比べた組 | ブロックの種類が違う | 地形の形が違う | 形の違い(高さ56〜79) |
|---|---|---|---|
| 本家 対 本家(基準) | 0.293% | 0.0076% | 0.011% |
| 本家 対 C2ME | 0.399% | 0.0116% | 0.044% |
| 本家 対 C2ME + GPU | 0.448% | 0.0146% | 0.071% |
| GPU版 対 GPU版 | 0.396% | 0.0108% | 0.026% |
ブロックの種類のずれの大半は、葉や落ち葉、鉱石、岩の塊など、地形を作ったあとに置かれる飾りだった。地形の形のずれは、GPU版で約7,000ブロックに1つである。
モブの処理とGPU
| 場面 | 匹数 | CPU 1コア | CPU 全コア | GPU(倍精度) |
|---|---|---|---|---|
| 4×4ブロックの囲い | 1,600 | 4.2 ms | 0.742 ms | 6.51 ms |
| 広い範囲に密集 | 16,000 | 5.3 ms | 1.92 ms | 1.77 ms |
| 広い範囲に密集 | 256,000 | 144 ms | 44.4 ms | 20.4 ms |
押し合いは、各モブの速度に足していくだけの計算なので、処理の順番に結果が左右されない。並列にしても安全である。float32では、判定の境い目(0.6ブロック)がひっくり返り、密集した6万4千〜25万6千匹で相対誤差0.11〜0.12、散らばった25万6千匹で0.23になった。
Foliaが地域を分ける距離
Foliaは、近くにいるプレイヤーを1つの地域にまとめて1つのスレッドで動かす。ボットを1体ずつ入れて地域の数を数えたところ、初期設定(grid-exponent 4、view-distance 6)では次のようになった。
- 768ブロック間隔と1,536ブロック間隔:1つの地域にまとまった
- 1,920ブロック間隔、2,304ブロック間隔、3,072ブロック間隔:別々の地域になった
最初の計測では地点の間隔を768ブロックにしていたため、Foliaは16か所を1つの地域として扱い、並列に動いていなかった。
見落としかけたこと
Foliaには、データパックの命令をまとめて実行する/functionと/datapackがない。村人をデータパックで出していたところ、Foliaだけはエラーも出さずに1匹も出ていなかった。各地域のモブの数が村人を増やしても変わらないことから気づき、/summonで1匹ずつ出して、ボットに実数を数えさせる形に直した。無効だった回のデータも、理由を書いてリポジトリに残している。
処理の種類でコアを分けた場合の上限
| 処理 | 本流スレッドに占める割合 |
|---|---|
| モブ:頭脳(行動を決める処理) | 65.9〜69.6% |
| モブ:その他 | 10.8〜12.6% |
| モブ:押し合い | 4.7〜5.5% |
| モブ:移動・衝突 | 4.3〜4.7% |
| モブ:経路探索 | 1.7〜4.1% |
| 自然発生・ランダムな更新 | 2.6〜3.4% |
| チャンクの仕組み・追跡 | 1.8〜2.2% |
| レッドストーンなどの予約された更新 | 0.2〜0.6% |
種類ごとに別のコアで動かしても、1回の更新は一番重い種類より短くはならない。上限は1.44〜1.52倍で、「モブ」と「それ以外」に分けるだけなら約1.1倍である。重いのは同じ種類の処理の大量のくり返しなので、分けるなら場所ごとかモブごとになる。ShreddedPaperが集まっていても速かったのは、チャンクの単位で鍵をかけて、この分け方をしているからだと考えられる。
集まったときのための改造(v1.1.0で追加)
集まった条件で1地域になったFoliaの記録を見ると、地域のスレッドの時間の約8割が、村人の職業ブロックやベッドの探索に使われていた。職業のない村人は1〜2秒ごとに縦横高さ48ブロックの範囲を探し、近くに1つもないと毎回範囲の全部を調べ直す。そこで2つの改造を作り、同じ負荷(村人4,800匹・256ブロック間隔)で2回ずつ測った。
- A:Foliaで、直前に確かめた「このチャンクは自分の地域のものか」の答えを、スレッドが同じ地域を受け持っているあいだだけ覚えておく。
- B:探索で何も見つからなかったとき、再挑戦の予定による絞り込みを外して48+8ブロックの範囲をもう一度だけ確かめる。それでも0件なら、村人がその場所から縦横高さとも8ブロック以内にいて、世界のどこでもPOIが変わっていないあいだは、探索を省く。POIの変化は、追加・削除・予約・予約の解除・読み直しがすべて通るPoiManager.setDirtyで数える。
| サーバー | TPS | 1回の更新(ミリ秒) |
|---|---|---|
| Folia(公式build 8) | 0.24 / 0.26 | 4,110 / 3,727 |
| Folia + A | 0.22 / 0.24 | 4,467 / 4,041 |
| Folia + A + B | 2.81 / 2.82 | 362 / 359 |
| Paper ver/26.1.2(改造なしで手元でビルド) | 4.10 / 4.40 | 244 / 229 |
| Paper ver/26.1.2 + B | 6.90 / 6.90 | 146 / 144 |
Bで、集まったFoliaは約11倍、Paperは約1.6倍速くなった。スレッドの記録のうち探索の中にいた割合は、Foliaで79.7%から9.3%に、Paperで33.6%から3.6%に減った。Aは、ねらった表の検索を記録から消した(15.8%から0%)が、1回の更新の時間は回ごとのばらつきを超えて縮まなかった。効かなかった結果として残している。
村人の動きは変わらないか
職業ブロックのない場所に村人を5匹出して90秒待ち、「見つからなかった」が覚えられた状態でコンポスターを隣に置いた。農家が生まれるまでの時間は、公式のPaperとFoliaで6秒、改造版で6・6・6・12秒だった。最初はどのサーバーでも就職しなかったが、原因はコンポスターを宙に浮いた場所に置いていたことで、床を張ってから測り直した。改造版が本当に探索を省いていたことは、上の探索の割合の減り方で確かめている。
訂正
GitHubのリポジトリの最初の版(v1.0.0)の集計表では、集まった条件のFoliaの1回の更新を727〜989ミリ秒としていた。このノートにはその数字を書いていないが、同じ計測から出したものなのでここに記す。Foliaはこの値を「3,828.71」のように3桁ごとのカンマ付きで出すが、読み取りの道具がカンマのあとしか読んでいなかった。正しくは3,727〜4,110ミリ秒で、同じ負荷のPaperの約10倍である。TPSと、Paper・ShreddedPaperの数字には影響がない。