『CK3』開発日記#187ーパフォーマンスと最適化
『Crusader Kings III』(以下『CK3』)の開発日記#187はパフォーマンスと最適化についてです。

アジア方面も増えますし、処理することが多くなりますしね。とりあえず見ていきましょう。前回の記事は以下のリンクから。
- 1. パフォーマンスと最適化
- 1.1. 要点を押さえた要約
- 1.2. 定義
- 1.3. パフォーマンスの測定
- 1.4. 適応
- 1.5. 千里の道も一歩から
- 1.6. 最も高価なシステムに焦点を当てる
- 1.7. 並列更新と順次更新
- 1.8. 特定のシステムへの最適化の適用
- 1.9. システムレベルの最適化から学ぶこと
- 1.10. 全体像
- 1.11. 読み込み中
- 1.12. メモリ
- 1.13. パフォーマンス
- 1.14. レンダリング
- 1.15. 複雑さ
- 1.16. シンプルさ
- 1.17. 敗北
- 1.18. 脚本とデザインの観点から見たパフォーマンス
- 1.19. スクリプトの最適化
- 1.20. スクリプト順序最適化ツール
- 1.21. 柔軟な頻度
- 1.22. 迷える無名人を排除する
- 1.23. 結論
- 1.24. 最小ハードウェア仕様の更新
パフォーマンスと最適化
Crusader Kings III チームのテクニカルリード、Joelです。Crusader Kings では様々な役割を担いながら、CK2 のデザイナーとしてキャリアをスタートし、20年目を迎えています。
シミュレーション速度の向上に向けた取り組みの詳細に入る前に、成果の簡単な概要をお伝えします。
要点を押さえた要約
では、事前に簡単な情報と結論を知りたいですか? 要約してみます!
All Under Heaven を含めると、プレイ可能な土地とプレイ可能な生きているキャラクターの量の意味で、ゲームは約 30% ~ 40% 大きくなります。

CK3 の現在のエクスペリエンスにできるだけ近づけるために、シミュレーションのティック期間を短縮することに重点を置きました。
低スペックと高スペックの両方のマシンで測定した数値は、現在のライブ バージョンに匹敵するティック速度に到達したことを示しています。
匹敵するというのは、ゲーム序盤ではわずかに遅くなりますが、ゲーム中盤から終盤では同等かわずかに速く、1066 年から 150 ~ 250 年間、速度 5 で実行していることを意味します。
次のグラフは、現在のリリースと比較した 150 年間のティック期間を示しています (高いほどティック期間が長くなり、したがって遅くなります)。
GUI、3Dグラフィック、メモリ使用量の改善も実装しましたが、シミュレーション速度よりも優先度は低かったです。
注意:結果は、作成する世界と、シミュレーションによって周囲に作り出される世界によって異なります。すべてのゲーム、ハードウェア、プレイスタイルを網羅する単一の数値やグラフはありませんが、All Under Heavenではプレイしやすく、安定して高速な体験を提供することを目指しました。
これが短いバージョンです。より詳しい情報、グラフ、洞察が必要な場合は、ぜひ読み進めてください。
定義
さて、通常のスケジュールに戻って詳細を掘り下げていきましょう。
まず、速度やパフォーマンスという言葉の意味を定義する必要があります。通常、パフォーマンスについて話すときは、レンダリングとシミュレーションという 2 つのカテゴリを指します。
グランドストラテジー ゲームの DLC を開発する場合、時間の経過とともにシミュレートするシステムとオブジェクトが増えていきます。開発が進むにつれてグラフィックの複雑さも増しますが、DLC 開発中に新しいボトルネックが発生することはほとんどありません。
ただし、シミュレーションの要求が増えると、新しい機能やシステムを追跡する必要があるため、計算とデータ変換で CPU の負荷が高くなります。
そのため、シミュレーションのティック速度を最適化する場合、CPU が最も一般的なボトルネックになります。
ゲームのコードを最適化する際の最も一般的なタスクは、CPU サイクルが費やされている場所と、プレイスルーの平均ティックあたり時間を短縮するためにより効率的にできる場所を確認することです。
これにより、ティックあたりの時間の測定は、開発全体を通じて追跡する最も重要な指標となり、この開発日記で最も多く取り上げることになります。
パフォーマンスの測定
では、複雑なシミュレーションでティック速度を正確に測定するにはどうすればよいでしょうか?
最終的な結果に影響を与える変数は数多く存在します。結果に影響を与える変数の例としては、グラフィック設定、ハードウェア、テストの長さ、ゲーム序盤または終盤の状態、バックグラウンドタスク、ランダム変動などが挙げられます。
これらの変数はすべてゲームのパフォーマンスを分析する上で重要ですが、パフォーマンス改善のほとんどは、異なるハードウェア間でも同様の効果をもたらすことを強調しておきたいと思います。
そのため、最も価値のあるテストは、固定設定と固定ランダムシードを用いた固定ハードウェアでのゲーム序盤のティックパフォーマンスです。
このテストは簡単に繰り返し実行できるため、開発中の異なるコードバージョンにおけるティック速度の傾向を経時的に追跡し、パフォーマンスの低下を迅速に特定することができます。
ただし、低コアハードウェアではそれほど効果が得られない最適化もいくつかあります。
ゲーム序盤のシミュレーションは一般的にコードのスループットの問題に過ぎませんが、ゲーム終盤の最適化は、複雑さの増加を抑制することに重点を置きます。
ゲームの後半で、例えばキャラクター数やゲームの舞台が拡大するなど、シミュレーションの規模が大きくなるにつれて計算が適切にスケールすることを確認することで、これを実現しています。
そのため、低スペックマシンでのスポットチェックや、エンドゲームのセーブデータのプロファイリングを実施し、計算を並列化するためにCPUコア数を増やす必要があるソリューションにのみ注力しないようにすることが重要です。
適応
ソフトウェアエンジニアリング(そして他の多くの専門職)には、「完璧は良きものの敵」という格言があります。最初から最も完璧で合理化されたコードを使用しなければならないと決めつけてしまうと、機能の開発に長い時間を費やすことになってしまいます。
これは特にゲーム開発に当てはまります。ゲーム開発では、ある機能の設計から始めて、後からゲームの他の部分やゲームプレイのフィードバックとより適合するように調整や修正を加え、プレイヤーにとって楽しい体験とは何かを探求することがよくあります。
経験から将来パフォーマンスの問題が発生する場所を予測し、それを軽減するためにより綿密に検討されたデータアーキテクチャを計画できる場合もありますが、ほとんどの場合、最も重要なのは機能を実際に稼働させてみて、それが楽しいものであることを確認することです。
その後、どのシステムが要件どおりに動作していないかを特定し、一貫性と効率性の両方の観点から改善を開始できます。
千里の道も一歩から
ここまでは、DLCプロジェクトのパフォーマンス向上に対する私たちの通常のアプローチについて説明してきました。
しかし、「All Under Heaven」では、そのスケールの大きさによってすべてが覆されます。これまでのDLCよりも多くの個別の要素が追加されていることに加え、シミュレーションのティック速度における最大の課題は、2つの亜大陸の追加です。東アジアと東南アジアはどちらも、ゲームでシミュレーションする必要がある世界への大規模な追加です。
これは、CK3でこれまで行ったどの拡張よりも大きな課題となるはずでした。最適化されていない機能追加によって通常発生する約20%の速度低下に加えて、ゲーム内の男爵領(および統治者)が32%増加するという問題にも対処する必要がありました。
私たちのシミュレーションでは、統治者は世界を前進させるための最小の構成要素であり、これはCPUが実行する必要がある作業量と線形相関しています。つまり、アジアの残りの地域をマップに追加するだけで、サイズの増加分だけゲームが即座に遅くなるということです。
AUHの企画段階では、通常の拡張版に比べて、ゲームの高速化とシミュレーション範囲の拡大を相殺する方法の検討に、より多くの時間とリソースを費やしました。
また、新機能をベストプラクティスに沿って維持するだけでは、安易な勝利に頼ることはできないことも認識していました。今回は、これまでのシステムのどこが私たちの足を引っ張っていたのか、より深く掘り下げて検討する必要がありました。
そこで、コードベース内でパフォーマンスの低いコンポーネントを見つける方法と、「速く動かす」という原則に沿ってゲームのパフォーマンスを向上させるために採用している手法について、才能豊かなエンジニアであるAntonとCarl-Henrikという二人に詳しく説明してもらいます。
こんにちは。長年Crusader Kings IIIチームに所属しているシニアプログラマーのアントンです。コードの構造と、パフォーマンス向上の測定方法や考え方について少しお話ししたいと思います。
最も高価なシステムに焦点を当てる
パフォーマンス改善へのアプローチの一つは、様々なゲームシステムを検討し、各システムを1ティックごとにシミュレーションするのに必要な時間を検討することです。
まずは最もコストのかかるシステムから始めます。これは、最適化にかかる時間に対して、そのシステムへの最適化による効果が最も大きいためです。
また、個々のシステムはより自己完結的であり、ゲームの他の部分との関連性も限られています。
これにより、システムについてより深く理解しやすくなり、今まさに重要なことに集中しやすくなります。社内ツールは、様々なゲーム機能を視覚化し、それぞれのパフォーマンスへの影響を比較するのに役立ちます。
次に例を示します。25 年間のゲーム時間を集計し、ゲームの最も負荷の高い部分の平均時間を示します。63 の異なるシステムが毎日更新され、そのうち 44 はそれぞれ 0.3 ミリ秒未満で、無視できます。
残りの 19 については、その名前とともに、1 日あたりの平均時間と割合を示します。一部のゲーム システムは常に負荷が高いです。
開発中は、さまざまなシステムがグラフ内でより多くの時間またはより少ない時間の割合で表示される場合があります。このシステムまたはそのシステムが 1 日にこれほど多くの時間をかけることがどの程度妥当であるかを評価します。
継承システムまたは状況システムがこのグラフの上部に表示されていた期間がありましたが、これは明らかに過剰な問題が発生しており、調査する必要があることを示していました。
実際には何も変更されていないのに状況が頻繁に更新され、継承システムがあまりにも多くの人に対して非常に負荷の高いスクリプトを実行していました。
ご覧のとおり、両方のシステムは現在「その他」のカテゴリに含まれており、ほぼ問題ありません。キャラクターと修飾子は通常上部にあります。
これらはゲームの中核であり、常に他の何よりも時間がかかります。これは常に難しい質問です。
常にコストがかかるものをどれだけ速く作ることができるか。数日多く費やす価値があるのか、それともこれで十分で、他のより簡単な利益を探すべきなのか。
平均だけが重要な数字ではありません。この特定のグラフは別の問題を示しています。薄いオレンジ色の列 (アクティビティ) が不均衡に急上昇することがあり、特定の月が平均よりもはるかに遅くなっていることがわかります。
平均時間は短く、重要なことが起こったときに大きな急上昇がないことが望まれます。これは、集計値だけを見る場合に比べて、このような視覚化手法を使用する大きな利点です。アクティビティは、次の最適化の第一候補となります。
並列更新と順次更新
前のグラフは日足の連続更新部分を示しました。次の2つのグラフは並行更新を示しています。
シングルスレッドアプローチでは、システムの理解と開発が非常に容易です。物事が順番に起こり、予測可能で繰り返し実行できることが分かっています。開発期間が短縮され、機能のテストとバランス調整もより早く行えます。
通常、機能の骨組みが完成し、ゲーム内で動作し始めてから、他のシステムと接続されます。その後、再度処理を行い、一部の作業を並列化します。
この2つのグラフは、1日のティックにおける並列処理を示しています。ティックごとに、アップデートに費やされた合計実時間を確認できます。各グラフとも20ミリ秒未満です。各グラフの下部に太い赤い線で示されています。
並列処理によって、合計でどれだけ多くの作業が行われているのかに注目してください。この例は、約20個の論理コアを搭載したPC上で実行されています。
なぜすべてを並列処理しないのでしょうか?
マルチプレイヤーが機能するには、ゲーム内で観測可能な変更が決定論的な順序で発生し、すべてのクライアントが他のユーザーと同じゲーム状態になる必要があるからです。
もう一つの重要な部分は、やはりゲームの正しさを推論する能力です。ゲームにおけるほとんどの変更には、波及効果があります。君主が新たな称号を獲得すると、別の君主が称号を失うことになります。
称号の喪失は、収入と軍事力の変化を意味します。弱体化した君主は、派閥や他の敵からより危険にさらされます。これらのステップのいずれにおいても、追加のイベントが発生する可能性があります。
ゲームの結果を理解し予測するには、厳密に決定されたアクションの順序と連鎖的な副作用が必要です。これを念頭に置き、私たちは可能な限り多くの作業を並行して進めたいと考えています。
私たちは、機能の一部を順次ステップと並行ステップに分割できる内部フレームワークを持っています。
これは、かなり前にプレリリース開発日記36で取り上げました。並行部分が最初に行われ、「事前アップデート」と呼ばれます。
事前アップデート中は、ゲームに目に見える変化はありません。すべての並列スレッドは、まるで凍結されているかのように、同じゲーム状態を認識します。
並行して、さまざまなシステムがそれぞれ独立して、次のシーケンシャルステップで何を変更すべきかを計算するという重い処理を実行できます。
各システムが独立して新しい収入を計算し、すべてのAIアクターが独立して決定を下し、高度なロジック、トリガー、および事前に実行可能な計算が事前に計算されます。
これらはすべて、最終的なシーケンシャル変更の量を最小限に抑えるために行われます。計算を行う代わりに既知の値を適用し、意思決定プロセス全体を行う代わりに最終決定を実行するなどです。
シーケンシャルアップデート中でも、複数のスレッドを使用する必要があります。そして、この部分は特にリスクが高く複雑です。
特定の変更が操作の順序に関係なく実行できることを証明できる場合、または一部のアクションが限られた副作用しか持たないことが保証されている場合、そこで並列作業を実行しても安全です。
ここでもう1つ重要な点があります。毎日更新されるのはゲームのごく一部だけです。
アップデートは日次、月次、年次の3つのアップデートに分かれています。毎日更新が必要な重要なシステムはごくわずかです。
ユニットのAIによる移動もその一例です。ほとんどのシステムは月に1回しか更新されません。これは頻度と効率性のバランスを取った結果です。
毎日、全プレイヤー、全建設物、全伝染病などの30分の1だけがアップデートされます。これにより、高額なアップデートがゲーム全体を通して均等に分散されます。これは以前のゲームと比べて明らかな欠点です。
プレイヤーであるあなたにいつアップデートが行われるかは分かりません。毎月の収入がいつになるかは分かりませんが、それは30日ごとに発生します。
特定のシステムへの最適化の適用
ゲームのより具体的な変更点、つまり個々のゲームシステムにどのように適用されるかについてお話ししましょう。
先ほど述べた例の一つは、高額な継承アップデートでした。All Under Heavenでは、独自の継承システムを持つ巨大な国、中国が登場します。
多くの人々が郡、公国、王国をめぐって争い、候補者は、個人、家族、そして人生における出来事といった様々な特性に基づいた功績ランクとスコアに基づいて任命されます。
ある時点では、他のシステムと比較して継承アップデートが非常にまれであるにもかかわらず、毎日のアップデート費用が最も高額なシステムでした。それほど高額だったのです。
個々に合理的な判断を数多く下した結果、このような結果になりました。中国には任命すべき称号が多数あり、ほぼ全員が中国全土のどの郡でも候補者になれるように設計されていたのです。
これはすぐに制御不能に陥る二次関数的な問題です。広大なビザンツ帝国ではある程度うまく機能していたものの、既に遅すぎたため、中国ではもはや適さなくなってしまいました。
最初の試みは、演算順序の変更でした。不適格な候補者をできるだけ早く排除し、スコアリングに高価なロジックと計算を使わずに済むようにすることはできないでしょうか?
4,000人の中から最適な候補者を見つけるよりも、100人の中から最適な候補者を見つける方がはるかに安価です。
もう一つの明らかな欠陥は、逐次的なスコアリングでした。任命スコアの計算は不変の操作であり、一人ずつではなく、全員に対して同時に安全に並列処理できます。これだけでも速度は3倍になりましたが、それでも十分ではありませんでした。結局、3番目に高価なシステムのままでした。
3つ目のステップは、設計者の意図について話し合うことでした。中国のすべての廷臣がどの郡でも有効な候補者になれるかどうか、本当に問題なのでしょうか?
きっと妥協点が見つかるでしょう。
土地を持たない君主にもこの機会が与えられることは重要です。正当な貴族の家族全員に、国に仕える機会が与えられるべきです。しかし、賤民はどうでしょうか?
彼らの参加を、彼らが個人的に住んでいる場所の称号のみに制限することはできるでしょうか?
この設計ステップにより、候補数が半減し、プレイヤー体験はそのままにパフォーマンスが大幅に向上します。
プレイヤーは、王国中の重要人物が任務に就く様子を引き続き見ることができ、システムは競争的で活気に満ちているように感じられ、さらに時間も短縮されます。ゲームシステムにおいて、私たちが何を達成したいのか、つまりプレイヤーにとって何が重要で、デザイナーがどのような目標を持っているのかを常に念頭に置くことが重要です。
4 番目のステップは影響度が低かったものの、それでも価値がありました。前の 3 つのステップの後、継承の更新のほとんどはスクリプト化されたスコア計算に費やされていました。
そこで疑問を持ち、さらに詳しく調べることができたのは幸運でした。それは、保有収入を計算する 1 行のスクリプトだけでした。非常に古いトリガーの 1 つは常に十分な速さでしたが、管理領域に知事効率が導入されたことで、保有収入が知事効率の影響を受けるようになり、キャッシュの誤りが原因で速度が低下しました。
事前計算済みの値を単純に返すだけのはずが、オンデマンドで非常にコストのかかる計算のシーケンス全体に変わってしまいました。キャッシュされた収入が常に有効で利用可能になる変更により、継承が非常に高速になったため、グラフから消えて「その他」に含まれるようになりました。
また、保有収入を使用していたその他のスクリプトも全体的に高速になりました。
システムレベルの最適化から学ぶこと
ゲームを高速化するもう 1 つの方法は、処理の頻度を減らすか、まったく処理を行わないことです。男爵の AI は、ほとんど意味のないことを数多く実行しようとしていたことが判明しました。
男爵には評議会や宮廷の役職はありませんが、それでもすべてを評価していました。現在はもうそうしません。
領地が 1 つしかない場合に建物の建設に約 3 年かかる場合、新しい建設を 3 年に 1 回だけ試みるべきです。
男爵は現在、建設を開始しようと試みるのは 3 年に 1 回だけです。
ゲーム上の多くの決定ややり取りは、さまざまなトリガーが失敗するため、男爵では実行できませんが、そもそもそれらのトリガーを実行する必要がなかったらどうなるでしょうか。
これらのメカニズムについて、統治者層ごとに可用性を徹底的に見直したことで、さらに多くの時間を節約できましたが、最終的な結果はそれほど印象的ではありませんでした。
全体として、1 日のティック時間は合計 0.5~1 ミリ秒で、ハードウェアのランダムな変動や現在のゲーム状態によって影が薄くなる可能性があります。
主な原因は、適切な統治者が高負荷のタスクに多くの時間を費やしているため、男爵の最適化にほとんど意味がなかったことです。
AIの意思決定も既にすべて並列処理されているため、パフォーマンスの向上は複数のコアに分散され、結果として目立たなくなっています。
私からのコメントは以上です。パフォーマンスに関する私の考えを共有させていただき、ありがとうございました。
それでは、AUHの開発中にパフォーマンスに関する多くの作業を行ってくれたCarl-Henrikにバトンタッチします。
全体像
Daily Tick ファンの皆様へ: 私はチームの主席プログラマーの Carl-Henrik です。Crusader Kings III のパフォーマンスとメモリ使用量の改善に主に取り組んでいます。
個人的な趣味として、アセンブラで 8 ビットおよび 16 ビット CPU のサイズコーディングを行っているため、パフォーマンスへの取り組みは私にとって身近なものです。サイズ/パフォーマンスのコーディングコンテストで 2 回優勝したこともあります。また
、私はここでまだ新人なので、Daan、Joel、Jimmy、Anton といった周りの人々、そして設計チームやチームパートナーに大きく依存しています。(ただし、Paradox に在籍する前の Johan Andersson のメンターとして、私は長い影を落としています。)
一般的に、私が最適化したコードはうまく動作することがわかりましたが、コードの作成後、多数のタイトルとキャラクターを導入しました。
つまり、当時の想定はもはや通用せず、これはパフォーマンスを改善する機会です。コードが何らかの点で劣っていたために変更されたわけではありません。
読み込み中
当初はゲームのロード画面に焦点を当てていました。自宅のパソコンはCK3の最低スペックを満たしていないため、この点を改善すればゲームをプレイする能力が大幅に向上するはずでした。
しかし、膨大なアクティビティ量には全く対応しておらず、パフォーマンス測定に使えるのはデバッグログのみで、これはゲームフェーズ中に発生する他のすべてのプロセスと複雑に絡み合っていました。
ロード時間をより詳細に分析するために、下図に示すように、シーケンス全体を追跡できる新しいパフォーマンス追跡ツールを作成しました。
グラフは各CPUがロードまたはセットアップにビジー状態にあることを示しています。黒い部分は必ずしもCPUがアイドル状態であることを示すわけではなく、表示が速すぎたか、ロード機能に含まれていなかった可能性があります。
改善すべき点は数多く特定されましたが、これらの変更を実装するには当時は規模が大きすぎることが判明しました。近いうちにこれらのシステムの強化に着手する予定です。
メモリ
Khans of the Steppeのリリースとほぼ同時に、RAM を節約する必要がありました。最低スペックのコンピュータではゲームの実行に問題があり、これは以前からコンソール移植チームが調査していたことでした。
Crusader Kings III 全体を最新プラットフォームに移植することは素晴らしい偉業であり、彼らの成果にアクセスできたことで、これらのメモリ改善を迅速に行うことができました。
特に改善された点の一つは、ゲーム内のツールチップのメモリ使用量で、これは驚くべきものでした。この変更を PC 版に取り入れるだけで、数ギガバイトも節約できました!
Clausewitz の GUI コードの更新によるメモリ節約も検討しましたが、All Under Heaven で使用するにはあまりにも大きく異なっていることが判明しました。
パフォーマンス
メモリの改善が進んだ後、コードパフォーマンスにも注目する必要がありました。初期の測定では、以前のDLC「Khans of the Steppe」と比べてパフォーマンスが最大1.5倍も低下していることが分かりました。
私はコードの改善を最優先に考え、デザインを専門とする他のチームメンバーは、スクリプトやゲーム内のキャラクターのアクション頻度といった要素に焦点を当てています。
最適化の進捗状況を監視し、開発関連の問題を特定するために、パフォーマンスアナライザーを100年間毎日実行しています。これは、高スペックのコンピュータ(32プロセッサ、64GB RAM、Windows 11)と低スペックのコンピュータ(8コア、16GB RAM)の両方で実行されています。
システムを特定し、関連する(遅い)コードを追跡したら、改善を開始できます。多くの場合、更新が必要なものが、必ずしもすべて更新する必要がないものも更新していることに気づきます。
たとえば、更新前のパフォーマンスグラフでは、スクリプト変数が最も多くのユーザーを占めていました。
保存済み変数/スクリプト変数では、タイムアウトする可能性があるため、更新が必要な変数のチェック範囲を絞り込みました。
そのため、約50万個の変数をチェックする代わりに、ティックごとに数百個の変数をテストするだけで済みます。この変更により、このカテゴリはグラフから完全に消えました。
レンダリング
グラフィックはゲームのパフォーマンスにおいて最も大きな影響を与えるカテゴリの一つですが、最新のマップアップデートを適用しても、Crusader Kings III ではこの点で大きな影響はありません。
グラフィックパフォーマンスを向上させるため、アダプティブフレームレートを導入しました。
この新しい設定は、画面アクティビティが少ない場合にレンダリングフレームレートをわずかに下げることで、10基未満のプロセッサを搭載したコンピューターにメリットをもたらします。低品質グラフィックプリセットで有効にする必要があります。
アダプティブフレームレートは、最大FPS設定と連携して動作します。最大FPS設定には、「ディスプレイのリフレッシュレート」という新しいデフォルトオプションが追加されています。
以前は、VSyncを無効にして最大FPSをオフにしたままにすると、フレームレートがデフォルトで上限なしになったため、パフォーマンスが大幅に低下していました。
レンダリングへの影響を最小限に抑えながら最適なパフォーマンスを得るには、アダプティブフレームレートを有効にし、最大FPSを30に設定してください。
マップのアップデートやその他の改善により、GPUが処理する詳細度が増加しました。これにより、フレーム処理時間が長くなり、画面更新レートが低下し、インターフェースの応答性が低下する可能性があります。
CPUとGPUは並行して動作するため、これは1日のティックレートには影響しませんが、このワークロードのバランスをより適切に取る方法を調査する予定です。この最適化は、他の計画されている改善と並んでまだ優先順位が付けられていません。
複雑さ
多くの最適化は単純で理解しやすいものですが、時には小さくて無害に見えるものが、あなたを深いところに引きずり込むような落とし穴に陥ることがあります。
いくつかのチェックを行い、(チェックが問題なければ)スクリプトを実行する関数があります。これは約30行のコードですが、パフォーマンス上の問題が多々あったため、改善には2週間以上かかりました。
最も遅いコード行は、スクリプトのスコープ(パラメータ)がトリガーまたはエフェクトの期待値と一致するかどうかを確認するIsScopeOKの呼び出しでした。些細な改善をいくつか行っても結果は出ませんでした。これは、コンパイラが自動的にその作業を行ったことを示しています。
この関数自体はそれほど遅いわけではありませんが、1ティックあたりに実行されるスクリプトの膨大な量を考えると、ゲーム全体で最もリソースを消費する部分です。
この行の実際の問題は、スコープをチェックするために、各トリガーが128ビットのフラグフィールドを生成し、それをスコープから生成された128ビットのフラグフィールドと比較していたことにありました。
これをスコープの数を比較するだけの単純な方法に置き換えることで、パフォーマンスが大幅に向上しました。
残念ながら、トリガーとエフェクトは複数の種類のスコープを受け入れることができるため、最終的には最初のチェックが失敗した場合、フォールバックとしてビットフラグテストが必要になりますが、それでも大きな改善です。
ほとんどの場合、トリガーとエフェクトはスコープ番号を返しますが、スコープビットフラグのみを返すリンクという概念もあります。私は最終的に2日間かけて、これらのクラスをすべて調べ、不足している関数を追加しました。
トリガー/エフェクトもキャッシュミスの影響を受けていたようで、キャッシュプリフェッチを追加すると少し改善されます。
しかし、これは関数の1行に過ぎません。私たちは、スクリプトのパフォーマンス向上のために、ゲーム内スクリプトプロファイラーを頻繁に使用しています。これは使用時にのみパフォーマンスコストが発生するはずなので、さらに調査が必要でした。
スクリプトのファイル名と行番号を記憶する必要があり、この特定のデータ型はその情報を格納するための実装が非効率的だったため、かなり役立ちました。しかし、この効率性は、使用されていないときには依然として冗長です。そのため、デフォルトコンストラクタの代わりにインプレースnew
を使用することで、この行は許容範囲となりました。時間はかかりましたが、実験、テスト、プロファイリングの各ステップは、大幅なパフォーマンス向上を実現したことで非常に価値のあるものでした。
シンプルさ
時には物事をシンプルにすると、処理が速くなることもあります。すべてのキャラクターは他のキャラクターについて多くの意見を持っており、各評価の強さは時間の経過とともに変化する可能性があります。
評価を追跡するシステムは、現在の評価値を巧みにキャッシュし、この評価が 1 ポイント変化した将来の日付を計算しました。
これで、キャラクター間の現在の評価値を取得するには、それをキャッシュ内で見つけて、毎日すべての意見をチェックして、ステップを踏むタイミングかどうかを判断すればよくなりました。
このチェックはソートされたリストで行われるため、変化しようとしている評価だけを考慮する必要があります。残念ながら、各ステップの後に新しい日付をリストに再挿入する必要があるため、この改善は、キャラクターを追加するにつれてコストが高くなっていきました。
計算は単純に変化率定数を使った乗算と加算であることが判明したため、キャッシュを削除して意見をオンザフライで計算する方が、ソートされた配列を維持するよりもはるかに高速になりました。
敗北
関数のパフォーマンス向上を試みたものの、結局は変更に見合う価値がないと気づくことが時々あります。
補正値の参照は毎日の処理の中で頻繁に行われるため、このコードを高速化すれば大きなメリットが得られるはずです。
そこで、関数の実行時間を大幅に短縮できる2つの方法を試しました。しかし、これらの改善は他のシステムでの同等の速度低下によって相殺され、全体的なメリットは得られませんでした。
これらのコード変更にかなりの時間を費やしたにもかかわらず、最終的には破棄されました。
脚本とデザインの観点から見たパフォーマンス
皆さん、こんにちは。CK3のシニアプログラマーであり、All Under Heavenのコード分野のコーディネーターを務めるDaanです。また、中国版のデザイン「機能管理」も担当しています。
そのため、デザインと「スクリプト」の観点から、パフォーマンス改善にどのように取り組んだかについてお話しするのに最適な立場にあります。
スクリプトの調整の大部分は私が行ったわけではありませんが、調整のためのツールや提案を提供しました。
スクリプトの最適化
ゲーム内でテキストファイルとして表示されるスクリプトファイルは、ゲームメカニクスの大部分を担っており、デザイナーやモッダーに大きな力を与えています。
しかし、その大きな力には…ある程度の責任が伴います。そして、大きな欠点も存在します。Paradoxスクリプトは同等のC++コードほど高速ではないのです!
そのため、必要以上に遅くしてしまうことも少なくありません。
この問題に対処するため、最近のリリースでは、遅いスクリプトを検出できるツールを追加しました。例えば、スクリプトプロファイラーは開発モードであればゲーム内で既に利用可能です。
これらのツールを用いて、ゲーム内のほぼすべてのスクリプトファイルを調べ、パフォーマンス向上の可能性を探りました。
トリガーの順序を入れ替え、構造を簡素化し、「同等だがわずかに高速」なマイクロ最適化を行うなど、様々な調整を行いました。
スクリプトをほとんど変更せずにそのままにしておくことを望むモッダーの方々にはお詫び申し上げます。最適化を進める中で、多くの調整を行いました。
また、最も使用頻度の高いトリガーとエフェクトにも着目し、それらのC++側も最適化しました。場合によっては、新しいトリガーを作成することもありました。良い例としては、適切ではあるものの扱いにくい名前の `is_available_quick` トリガーがあります。
この新しいトリガーにより、既存の複数の空室状況「ステータス」チェックを一度に確認できるようになりました。これは、これらのチェックを頻繁に同時に行っていることがわかったためです。
これらのチェックは、それぞれを個別に呼び出す場合と機能的には同等のロジックになりますが、内部的にはC++で記述された単一の「スマート」で高速なブロックで実現されています。
同様に、よく使用されるフィルター用のスクリプトリストジェネレーターに「ショートカット」オプションを追加し、テキストの微調整のみでスクリプトを高速化しました。
スクリプト順序最適化ツール
また、スクリプト実行順序を重み付けに応じてシャッフルする自動スクリプト順序最適化ツールも調査して実装しました。これは、実行を早期に停止することでパフォーマンスを向上させるものです。
しかし、このスクリプト最適化ツールによって常に十分な一貫性のあるパフォーマンス向上が見られたわけではないため、今のところは無効のままにしておくことにしました。
これは、パフォーマンスの向上に取り組む際に遭遇する可能性のある良い例です。
つまり、1 つのパフォーマンス向上が別のパフォーマンス向上の効果を食いつぶしてしまうことがあるのです。
そしてこのケースでは、他のスクリプト最適化によって、このツールによる最大の利点が削ぎ落とされてしまいました。
統計的に有用であると判断された場合は、これを有効にする可能性があります。
いずれにせよ、これはスクリプト言語の将来のメタプログラミングの改善に使用します。
興味のある方は、コマンドラインオプション '-script_optimizer` を使用してゲームを実行することで、基本バージョンを有効にできます。このツールとそのオプションについて別の開発日記を書くこともできますが、それはまた別の機会に残しておきます。
柔軟な頻度
時には、重要ではないものや、特定のキャラクタータイプではそれほど頻繁には変化しないものなど、すべきでないものをシミュレートすることがあります。
スクリプト ロジックの多くには、AI の頻度をスマートに適応させるための簡単なツールがありませんでした。たとえば、ほとんどのキャラクターのインタラクションは、アイスランドの男爵によっても中国の皇帝によっても同じ頻度で考慮されていました。
そのため、AI を考慮するための階層ベースの頻度を構成できるように、いくつかのシステム (インタラクション、決定など) を適応させました。
これにより、特定の階層の統治者が何かを考慮すべき頻度を静的に定義する方法も提供されますが、非常に重要なこととして、考慮すべきでない場合(頻度が 0) も提供されます。
たとえば、遊牧民、マンダラ、ワヌアの統治者には 'release_as_tributary_interaction’ があります。男爵や伯爵はこれを考慮すべきでしょうか? いいえ、決してありません。
メカニズム的に考慮することはできません。公爵は? おそらく… 彼らにとってはあまり重要ではありません。
しかし、古いインタラクション定義ロジックでは、チェックを行う必要があり、それは毎年、すべての統治者によってチェックされていました。比較的「高価な」スクリプト チェックを使用します。
Tierベースの AI 頻度定義を使用すると、このチェックをなくし、このインタラクションを伯爵または男爵によってチェックされません。
重要なボーナス ポイントとして、これにより、Tier の低いキャラクターによってパフォーマンスを失うことなく、Tier の高い AI 統治者についてより頻繁に物事を考慮することも可能になり、重要なときにゲーム AI の応答性が向上します。
このスタイルの Tier ベースの頻度構成を、ほぼすべてのキャラクターのインタラクション、決定、アクティビティ、および優れたプロジェクトに追加しました。統治者 AI が実行を検討している合計約 550 種類の「こと」です。
迷える無名人を排除する
ゲームを高速化するためによく使われるMODの一つに「人口制御」MODがあります。これは、世界の人口が一定数に達すると人口を減少させるものです。
私たちはこれと全く同じことをしたわけではありませんが、根本的な問題に対処しようと試みてきました。それは、あまり役に立たないキャラクターが蓄積され、数百年後には数万人規模にまで膨れ上がってしまうことです。
そこで、退屈で役に立たない、ランダム、あるいは目に見えないキャラクターを生成するキャラクターの「発生源」を削減または削除しました。
これらのキャラクターはゲストプールや男爵や伯爵の宮廷の脇に留まり、スペースとコストを浪費します。
同時に、ある程度の歴史を持つ興味深いキャラクターは長くプレイさせ、イベントに積極的に参加させ、ランダムに生成された無名のキャラクターと入れ替えるなどしています。
これにより、長期ゲームで蓄積される無駄な要素を削減し、比較的軽い非統治者キャラクターでさえシミュレーションシステムに負担をかけないようにすることができました。
結論
それでは、リリースまで数週間となった現在の状況を見てみましょう。当初、1日あたりのティックレートが約1.5倍低下したことを測定し、前回リリース時よりもわずかに遅くならないことを目標としていました。
マップエリアを大幅に拡張し、それに伴いキャラクターも大幅に増加しました。
リリースまであと数週間となった今、開発は最終段階に入り、日々の変更を実装しています。パフォーマンスは大幅に改善され、ゲームは以前のティックレート目標よりも、現在のリリースに大幅に近づくようになりました。これは、テストに使用したハイスペックPCと低スペックPCの両方で確認されています。
最終調整が終われば、もう少し速くできると期待しています。
さあ、プレイしましょう!
最小ハードウェア仕様の更新
最後に、Studio Blackのテクニカルディレクター、Jimmyより、最小ハードウェア要件について一言ご挨拶させていただきます。
まず第一に、この非常に技術的に要求の厳しい拡張パック「All Under Heaven」のパフォーマンス向上に向けて、チームが尽力してくれたことに驚嘆しています。「All Under Heaven」のパフォーマンスレビューの一環として、最小ハードウェア要件、特にCPUベースラインを見直しました。
この開発日記でも触れましたが、数ヶ月に及ぶ最適化作業を経て、開発初期に見られた速度低下はほぼすべて回復しました。
開発開始当初は1日のティック間隔が大幅に増加していましたが、現在ではマップの拡張やシミュレーションの負荷増加にもかかわらず、現在のライブバージョンとほぼ同等の動作を実現しています(序盤と終盤では多少の変動はありますが)。
これらのテストは、ハイエンドとローエンドの両方のハードウェアで実施しました。つまり、今回のハードウェア仕様のアップデートは、最適化されていないコードの問題ではなく、すべてのユーザーがゲームを安定してプレイできるようにするためのものです。
ハードウェアラボで実施した徹底的なテストの結果、2011年に発売されたIntel i3-2120のような古いCPUは、特に6GBのRAMしか搭載していないシステムでは、高負荷時に予測通りの動作が困難であることが判明しました。
これは2020年の当初の最低スペックであり、15年近く前のハードウェアとしては驚異的な長寿命です。
しかし、一貫した体験を保証するため、CPUの最低推奨スペックをIntel i5-750またはAMD FX-4300に更新し、8GBのRAMを搭載する構成としました。これらの構成は今日の基準からするとまだ控えめですが、発売から5年を経た現在のCrusader Kingsに必要な、安定した予測可能なパフォーマンスを提供します。