UE5 CPU負荷軽減についてのメモ

# 概要
UnrealEngine5 にてCPU負荷軽減における注意事項をまとめたメモです。UE5独自ではなくUE4でも使えるものもあります。
基本的な「余計なTick処理を省く」「LOD設定をやる」「複雑なコリジョンを使用しない」などの対応を行ったうえで検討してみるといいと思います。


## 更新履歴
| 日付 | 内容 |
|:-----|:----|
|2023/05/17| 初版 |
|2023/06/07| PoolingMethod, MaxSimulationIterations等を追記 |
|2023/07/07| AIPerceptionについて追記 |

## 参考
以下の記事を参考にいたしました、ありがとうございます。
[UE公式：CPUプロファイリング](https://docs.unrealengine.com/4.27/ja/TestingAndOptimization/PerformanceAndProfiling/CPU/)
[UE公式：Task System](https://docs.unrealengine.com/5.1/ja/tasks-systems-in-unreal-engine/)
[UE公式：Blendノード](https://docs.unrealengine.com/5.1/ja/animation-blueprint-blend-nodes-in-unreal-engine/)
[UE公式：エフェクト タイプを使用したパフォーマンス バジェット管理](https://docs.unrealengine.com/5.1/ja/performance-budgeting-using-effect-types-in-niagara-for-unreal-engine/)
[UE公式：UMGの最適化に関するガイドライン](https://docs.unrealengine.com/5.0/ja/optimization-guidelines-for-umg-in-unreal-engine/)
[Unreal Engine - プロファイリング＆最適化の始め方](https://www.docswell.com/s/EpicGamesJapan/ZJDQ1Z-UE_profiling_and_optimization)
[[UE5] UnrealInsights を使ってみよう](https://historia.co.jp/archives/30687/)
[UE4で多数のキャラクターを生かすためのテクニック【CEDEC 2018】 ](https://www.docswell.com/s/EpicGamesJapan/ZVPELZ-cedec2018-ue4-CharacterPerformance)
[UnrealEngineにおけるマルチスレッディング入門 タスクグラフ編【CEDEC+KYUSHU 2021 ONLINE】 ](https://www.docswell.com/s/EpicGamesJapan/5QMWWK-UE4_CEDECKYUSHU2021_MultiThread#p1)
[『FINAL FANTASY VII REMAKE』におけるプロファイリングと最適化事例 【UNREAL FEST EXTREME 2021 SUMMER】 ](https://www.docswell.com/s/EpicGamesJapan/ZEEQEZ-UE4_UFE21S_SQEX_FF7R_Profiling)
[【UE4】URO(UpdateRateOptimizations)でアニメーションの最適化](https://logicalbeat.jp/blog/6222/)


## 関連する過去記事
[UE4 CPU負荷軽減についてのメモ](https://qiita.com/unknown_ds/items/f9eb5f024dbd29cf42f4)
[UE4 Tickの処理順序についてのメモ](https://qiita.com/unknown_ds/items/e9efab0fb9cc647f4ef9)
[UE4 アニメ―ションノードの作成（ボーンのトランスフォーム）を試してみる](https://qiita.com/unknown_ds/items/b343a2ab56d77f1b97c5)
[UE5 アニメーションのマルチスレッド処理についてのメモ](https://qiita.com/unknown_ds/items/9232c9e43b1073e3d8c4)

## 環境
Windows10
Visual Studio 2022
UnrealEngine 5.1.1

## 関連ソース
"Engine\Plugins\Experimental\ChaosCaching\Source\ChaosCaching\Public\Chaos\CacheManagerActor.h"
"Engine\Source\Runtime\Engine\Classes\GameFramework\CharacterMovementComponent.h"
"Engine\Source\Runtime\Engine\Private\Components\CharacterMovementComponent.cpp"
"Engine\Source\Runtime\Core\Public\Tasks\Task.h"


# Niagara
UE5からはCascadeが非推奨になったのでNiagaraがエフェクトシステムとしてはデフォルトとなります。

## SimTarget
エミッタのプロパティ [Sim Target]にて`CPUSim`ではなく`GPUCompute Sim`に変更すると計算がCPUからGPUへ変更されるためCPU負荷が減ります（GPU負荷は増えます）。
`CPUSim`だと負荷が増えやすいGameThreadでの計算となるため特別な事情がない限りはやっといた方がいいかと思われます。

![niagara01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/b0d5e8d9-5269-6167-d43f-7c6e63508007.png)


## パーティクルコリジョン
パーティクルのコリジョンが有効な場合、これもGPU計算にできます。`GPUCompute Sim`にすると自動で設定されるようです。

![niagara02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/8fbeef48-a638-3662-1700-9927563ce3d6.png)


## CPUSim と GPUCompute Sim の負荷検証
テスト作成したパーティクルにて `stat unit`にて確認をすると以下の様に `Game`と`Draw` にて値の違いが確認できました。

▼CPUSim
![CPU.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/a3dcc0b0-0e67-14df-c2d1-93c7a720f001.png)

▼GPUCompute Sim
![GPU.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/af568431-1eef-3c7d-7eff-a19159ceb103.png)

パーティクル数700ほどを発生させていますが、CPU負荷が下がっています(GPU負荷は上がっています)。

:::note warn
`GPUCompute Sim`では`Calculate Bounds Mode`で`Dynamic`を選択すると警告がでてしまうことに注意が必要です。
:::


## Scalability
エミッタのステートに`Scalability`の設定があるのでこれを適宜調整することで負荷軽減になります。
ScalabilityMode を `system` から `self` に切り替えると各種設定が可能になります。

![scalability.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/f21ff046-c3e9-f414-b908-0296d7c83530.png)

詳細設定は以下のようになります。
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/2124d1ca-f5dd-1172-ec2d-ae8f49caa42a.png">

**▼Enable Distance Culling**
距離による設定で、`Min Distance Response`が設定距離以下になった場合の処理設定。 `Max Distance Response`が設定距離以上になった場合の処理設定となります。

**▼Scale Spawn Count**
パーティクル量の調整項目で、`Spawn Count Scale`がパーティクル量の倍率、距離によって変化を付ける場合は`Spawn Count Scale By Distance`にチェックを入れます。

**▼Enable Visibility Culling**
カメラ表示によるカリング設定項目で、`Visibility Culling Response`設定で処理されます。
`SLEEP AND LET PARTICLES FINISH` か `SLEEP AND CLEAR PARTICLES` を設定することになると思います。


## Effect Type
エフェクトタイプアセットを作成してエミッタシステムに割り当てることで、同一タイプによるバジェット管理ができるようです。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/9c442f69-780d-551d-61b0-c659c15231dc.png">

これにより、割り当てられた使用量を超える同一エフェクトタイプではカメラ距離や優先に基づいてカリング処理が行われるようです。


## PoolingMethod
エフェクトをスポーンする際に `Pooling Method` を `Auto Release`にするとエフェクトをプールして再度同じエフェクトをスポーンする際に使いまわしをしてくれるようです。これによりスポーン時の処理負荷が軽減されます。使用頻度が高い単発エフェクト（攻撃ヒットエフェクトや移動時の砂煙エフェクトなど）に使うと効果があると思われます。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/94c22803-e0c5-a3fa-809e-961c83adc1b2.png">

`Manual Release`はエフェクト終了時に明示的に `UParticleSystemComponent::ReleaseToPool()` を呼ぶ必要があるようです。単発ではないエフェクトはこちらを使う必要があるようです。



# Chaos Destruction
UE5のデフォルト物理エンジンChaosの破砕シミュレーションです。

## ジオメトリコレクションの表示
ジオメトリコレクションは破砕された状態での表示扱いとなり、LODなどはないため破砕しない場合や遠方などでの適用は何らかの対策すべきです。（ただDrawCall数は変らないぽい？）
考えられる対策としては破砕前と同等のスタティックメッシュを用意して差し替えなどがあると思います（スムーズに差し替えるにはそれなりに難しいとは思いますが）。


## Choas Cache Manager
キャッシュを利用することで負荷が軽減できます。ただし予め記録したキャッシュデータのとおりに破壊されることになります（インタラクティブではない）。

キャッシュマネージャは[アクタ]->[Chaos]->[キャッシュマネージャを作成]から作成します。
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/4df4a87f-2504-fce4-1076-d1c8f5816541.png">

キャッシュマネージャの設定は以下のようになります。
1. `Cache Collection` にChaosキャッシュコレクションを設定。
2. `Cache Name` に任意のキャッシュ名を設定。
3. `Referenced Actor` に破砕対象のジオメトリコレクション。
4. `Component Name` にジオメトリコレクションコンポーネント名を設定。 

:::note info
Chaosキャッシュコレクションはコンテンツブラウザから右クリック->[フィジックス]->[Chaosキャッシュコレクション]でアセットを作成します。 
:::
![ChaosCacheManager.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/5dd72174-5936-34f6-8c58-3637f759427b.png)

キャッシュマネージャの `Cache Mode` を `記録` にし、PIEを起動することで記録を開始します。
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/c663b3f7-b8b6-7169-6010-18d97f4afd1d.png">

:::note warn
記録時に
**LogChaosCache: Warning: ChaosCacheManager_UAID_????? has invalid observed component reference.**
というログが出た場合は `Component Name` 設定のスペルを確認してください。
コンポーネント固有名ではなくコンポーネント名になります。
![example01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/62b6cda4-4252-cb4d-8696-2a22f5ad4b9a.png)
破砕対象が上記の場合、`GeometryCollectionComponent0` ではなく `GeometryCollectionComponent` となります。
:::

記録に成功すると以下のようにChaosキャッシュコレクションに設定したキャッシュ名で保存されます。
![CacheCollection.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/32ef9076-e055-728c-fb5a-fb1c397ae5bd.png)

`Cache Mode` を `プレイ`にすることで再生されます。任意のタイミングが必要な場合は `Start Mode` を `Timed` から `Triggered` に変更し、ChaosChacheManager から `Trigger All`等を実行することで再生ができます。
![TriggerBP.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/253c2f54-d70e-c2be-8123-bee13bd71a4c.png)


### 負荷検証
キャッシュあり/なしでGame負荷が落ちているのが確認できます。

▼キャッシュなし時（記録時）
![example01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/0ab4fdec-be50-d65d-012f-d94d2a5624e5.png)

▼キャッシュ再生時
![example02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/651b5979-c603-8222-f869-3a364cdd2025.png)



# CharacterMovementComponent
エンジン付随のアクター移動コンポーネント。単純な移動ならこれを使わずに作成したほうが軽くなります。

## MovementMode
NavMesh使用している条件下なら移動中のコリジョンチェックをなくすことができます。

:::note warn
注意
NavMeshが条件になるため、NavMeshがない場所に移動すると`Walking`モード（コリジョンチェックあり）に戻ります。また段差等の高さの差がある個所の移動が怪しくなります。
:::

`CharacterMovementComponent` の詳細設定の `DefaultLandMovementMode` を `Navmesh Walking`に、`Sweep While Nav Walking` のチェックも外します。

<img width="270" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/de3bbf7b-c493-78d5-a895-282565c54afc.png">

対象のアクターがレベルに配置済の場合、上記デフォルト設定で DefaultLandMovementMode を変えてもNavMeshが見つけられず、すぐ `Walking`モードに戻ってしまう場合があるので、
スポーン時に`NavMesh Walking` に設定してしまう方法がよいかもしれません。以下コード例。

▼BPでのMovementMode設定
![SetMovementModeBP.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/5d6edc10-c38e-d0f7-47be-19c1e282a9d0.png)

▼C++でのMovementMode設定
```cpp:ATestEnemyCharacter.cpp
#include "GameFramework/CharacterMovementComponent.h"

void ATestEnemyCharacter::BeginPlay()
{
	Super::BeginPlay();
	
	UCharacterMovementComponent* _CharaMC = GetCharacterMovement();
	if( _CharaMC ){
		// 移動モード設定
		_CharaMC->SetMovementMode(EMovementMode::MOVE_NavWalking);
		// NavWalk中はSweepしない
		_CharaMV->bSweepWhileNavWalking = false;
	}
}
```

### コリジョンアナライザーでの検証
[ツール]->[デバッグ]->[コリジョンアナライザー]にて各MovementModeでの移動中のコリジョンを確認すると以下のようになりました。
移動時の床チェック`ComputeFloorDist`がなくなっており、Sweep処理もなく移動中のOverlapチェックのみになっているのが確認できます。

▼通常時(Walking)の移動中
![CollisionTest1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/198e0978-e6a8-2187-45dc-b2a18ea9c7ad.png)

▼NavWalkingモードの移動中
![CollisionTest2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/cb7b7ed7-d7c1-94f8-1e0a-2054ca033c0e.png)

この`Overlap`もコリジョンの `Generate Overlap Events`のチェックを切ると消えます。（ただしOverlapイベント受け取りができません）

通常操作の(PlayerControllerの)ポーンでも `NavWalking`モードでの移動は可能ですが、NavMeshがない場所（途切れているような場所）へ移動すると通常モードに戻るためあまり意味がないかと思います。
なので使用する対象としては、動く範囲がNavMesh内に決まっているアクターにとどめておく方がいいと思われます。


## MaxSimulationIterations
`UCharacterMovementComponent::PhysWalking()` にて行われている処理で、Walkingモード時の移動位置シミュレート最大回数の値です、コリジョンチェック等を複数回繰り返しているようです。複雑なコリジョンや高速移動するようなオブジェクト以外は減らすことで負荷軽減になるようです。
![MaxSimulationIter.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/216ff87b-f76b-a92c-aaac-66ecda21c0cf.png)

### CharPhysWalkingの検証
UnrealInsights を使って検証してみましたが、差があまりでませんでした。フラットな地形ではなく複雑な地形でないと差がでにくいと思われます。
![UnrealInsight.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/28f5eb37-c142-cda0-fd13-17a8177893a7.png)


## Update Only if Rendered
`Update Only if Rendered`にチェックを入れると非表示時の移動処理をスキップします。非表示時に移動の必要がないキャラクタの場合に有効だと思われます。
![UpdateOnlyIfRendered.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/0a98cb99-7524-1416-ccbc-b1bab74fae21.png)


# AI Perception
AIに対する感覚システムで、視覚、聴覚、触覚などがあります。

## 感覚システムのOn/Off制御
感覚システムを稼働する側（敵キャラの視界など）を適宜On/Offすることで処理負荷軽減が期待できます。
登録（知覚システムの開始）は`UAIPerceptionSystem::UpdateListener` で、解除（知覚システムの停止）は `UAIPerceptionSystem::UnregisterSource` で行います。BPには対応していないようです。

```cpp:.cpp
#include "Perception/AIPerceptionComponent.h"
#include "Perception/AIPerceptionSystem.h"

auto PerceptionSys = UAIPerceptionSystem::GetCurrent(GetWorld());
if (PerceptionSys) {
	if(_IsRegist){
		// 登録
		PerceptionSys->UpdateListener(*AIPerceptionComponent);
	}else{
		// 解除
		PerceptionSys->UnregisterListener(*AIPerceptionComponent);
	}
}	
```


## 視界対象ポーンの自動登録の停止
AIPerception_Sight は基本ポーンを対象とした仕組みでシステム開始時にレベル上のポーンが自動登録されます。検索する対象が多いと当然処理負荷が高くなるため、必要な対象ポーンだけ登録することが可能ならば負荷軽減につながると思います。
各AISenseクラスの `bAutoRegisterAllPawnsAsSources` を `false` にすると対象の自動登録をなくすことができ、`DefaultGame.ini`に以下のような設定をすることで実現ができます。
以下、AISense_Sightの場合のコード例。

```ini:DefaultGame.ini
[/Script/AIModule.AISense_Sight]
bAutoRegisterAllPawnsAsSources=false
```

代わりに手動で対象ポーンを登録する必要があります。`UAIPerceptionSystem::RegisterSource` で行います。
以下コード例。

```cpp:.cpp
#include "EngineUtils.h"

UWorld* World = GetWorld();
for (TActorIterator<APawn> PawnIt(World); PawnIt; ++PawnIt)
{
	if( PawnIt->ActorHasTag(FName("MyChara"))){	// タグで判別する
		// 対象登録
		RegisterSource(SenseID, **PawnIt);
	}
}
```


## AIPerceptionSystemの差し替え
`UAIPerceptionSystem`クラスを差し替えて知覚システムの対象ポーンを制御することなどができます。
プロジェクト設定の[エンジン]->[AIシステム]->[認識システムクラス]で差し替えられます。
![MyAiPerceptionSystem.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/d5271288-55eb-8628-d0b4-c712f17e3357.png)

`UAIPerceptionSystem`クラスの差し替えコード例。

```cpp:MyAIPerceptionSystem.h
#include "CoreMinimal.h"
#include "Perception/AIPerceptionSystem.h"
#include "MyAIPerceptionSystem.generated.h"


UCLASS()
class MYPROJECT_API UMyAIPerceptionSystem : public UAIPerceptionSystem
{
friend class UAISystem;

	GENERATED_BODY()
	
protected:
	// 全ポーンを全センスへ登録
	virtual void RegisterAllPawnsAsSourcesForSense(FAISenseID SenseID) override;

	// ポーンが新規作成された際に各センスへの通知と登録を行う
	virtual void OnNewPawn(APawn& Pawn) override;

};
```


```cpp:MyAIPerceptionSystem.cpp
#include "MyAIPerceptionSystem.h"
#include "Perception/AISense.h"
#include "EngineUtils.h"
#include "AIController.h"


void UMyAIPerceptionSystem::RegisterAllPawnsAsSourcesForSense(FAISenseID SenseID)
{
// bAutoRegisterAllPawnsAsSources が true の場合呼ばれる。
// 全ポーンを勝手に登録しないように判定する場合はここでコードを書く

//	Super::RegisterAllPawnsAsSourcesForSense(SenseID);

}

void UMyAIPerceptionSystem::OnNewPawn(APawn& Pawn)
{
// 新たなポーンが生成されるときに呼ばれる。
// そのポーンを登録するかしないかを判定する必要がある場合はここにコードを書く

//	Super::OnNewPawn(Pawn);
}
```



# Animation
アニメーションはFastPathの利用、UpdateRateOptimization(URO)の利用等でCPU処理負荷軽減を図ることができます。またUE5からは `PropertyAccess`をつかってAnimBPでのマルチスレッド処理を書くことが可能です。

## 慣性ブレンド(Inertialization)
UE4.24から追加されたブレンド方法です。
通常のブレンドはAからBへ遷移する場合、AとB両方のアニメーションを計算して徐々にウエイトを移していきますが、慣性ブレンドの場合は即時にBに切り替え、遷移前のAは切り替え時の慣性を使用してブレンドを行います。それによりアニメーション評価関数の呼び出しが減るためCPU処理負荷が減るようです。

トランジションのブレンドロジックを`Inertialization`に変更し、そのロジック後に`慣性化`ノードをつなぐことで使用できます。（Outputポーズノード手前でOK）
![Trangition.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/fd5192af-da68-1e8f-a225-72f1c20a0e52.png)
![Inerti.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/87c777cc-b7c3-b1d7-b681-a58eec70650f.png)

トランジション以外にも各ブレンドノードのブレンドロジックにて設定が可能なようです。

:::note warn
注意
慣性ブレンドは遷移元のアニメーション評価も止まるため、ブレンドタイミング後でアニメーションシーケンスに設定した通知もされないことに注意が必要です。
:::


## Animation停止
不要なタイミングでアニメーションを停止することで負荷軽減になります。`SkeletalMeshComponent`に各種アニメーションを停止するメソッド/フラグがあります。

### SetRender Static
動的変更が可能で、アニメーション更新を停止します。停止後はデフォルトポーズになります。
<img width="270" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/ac1a24a2-2379-435c-22bb-8551511fac2c.png">

### Pause Anims
動的変更が可能で、アニメーション更新を停止します。停止後は停止時のアニメーションポーズのままになります。
<img width="270" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/e99b7800-ca0b-afbb-19a9-3f7d818c0654.png">

### No Skeleton Update
動的変更が可能で、スケルトンの更新を停止します。停止後は停止時のアニメーションポーズのままになります。
<img width="270" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/41aba9c9-dfb9-ca9b-aad4-01d0d9c41b1a.png">


# マルチスレッド処理
UEは`TaskGraph`や`ParallelFor`といった手段でマルチスレッド処理が実装できます。

## bRunOnAnyThread
アクター/コンポーネントクラスの `bRunOnAnyThread` を `true` にすることで、アクター/コンポーネント処理がTaskGraphにより別スレッドにて処理を行ってくれます。ただしスレッドセーフにするには自前で行う必要があります。Tick処理の依存関係は `AddTickPrerequisiteActor`, `AddTickPrerequisiteComponent` で設定ができるので独自処理を切り出して既存処理の後に行うといったことが可能かと思います。
既存コンポーネントにはゲームスレッド前提で動く想定になっているものが多く、`check(IsInGameThread())` というチェックコードが入っているようです。

## Task System
UE5から実装された非同期処理システムです。
基本的な使い方は以下のように。
他にもプライオリティ付けや複数タスクを連続して処理するパイプ機能などがあるようです。

```cpp:基本的なタスク作成
#include "Tasks/Task.h"

using namespace UE::Tasks;

FTask MyTask = Launch(UE_SOURCE_LOCATION, [] {
		FPlatformProcess::Sleep(1.0f);
		UE_LOG(LogTemp, Log, TEXT("Task End.."));
		});

```

```cpp:タスクを作成して結果受け取り
// ヘッダファイルメンバ定義
// TTask<int32> MyTaskInt;

void UMyTestComponent::BeginPlay()
{
	// タスクを起動
	MyTaskInt = Launch(UE_SOURCE_LOCATION, [] {
		FPlatformProcess::Sleep(0.5f);
		return(1);	// 結果を返す
		});
}

void UMyTestComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	// タスク終了の確認
	if (MyTaskInt.IsValid()) {
		if (MyTaskInt.IsCompleted()) {
			// 結果受け取り
			auto _Result = MyTaskInt.GetResult();
			UE_LOG(LogTemp, Log, TEXT("%d"), _Result);
			MyTaskInt = {};		// タスク始末
		}
		else {
			// まだタスクが終わっていない
		}
	}
}
```


# コンパイラ最適化
コンパイラの最適化でCPUコスト削減ができます。ですが最適化により特定の処理に不具合が起こる場合もあり得るので注意が必要です。例えばBPの変数やカスタムイベントなどで日本語が使われていたりする場合などは要注意です。

## リンク時最適化(Link Time Optimaization : LTO)
異なるオブジェクトファイル間での最適化。
ビルドコンフィギュレーションの `bAllowLTCG` を有効にすることで可能になります。ただしビルド時間が大幅に延びることに注意が必要です。

```cs:MyProject.Target.cs
public class MyProjectTarget : TargetRules
{
	public MyProjectTarget(TargetInfo Target) : base(Target)
	{
		Type = TargetType.Game;
		DefaultBuildSettings = BuildSettingsVersion.V2;
		bAllowLTCG = true;		// リンク時最適化
		
		//...省略..
	}
}
```

また `bPreferThinLTO` を有効にすることでLTOの軽量バージョンが利用できるようです。（リンク時間が大幅にかかるのを軽減する？）


## プロファイル誘導最適化(Profile Guided Optimization : PGO)
プロファイルデータに基づいた最適化。プロファイルデータの測定のための専用ビルドとトレーニングランが必要です。

1. `bPGOProfile` を有効にして計測用パッケージを作成する。
2. 作ったパッケージでプロファイルデータを取得するためのトレーニングランを行い、*.pgcファイルを作成する。
3. /Platforms/{プラットフォーム名}/Build/PGO/ フォルダに計測用パッケージ作成時に生成された *.pgd と トレーニングランで作成された *.pgc ファイルを置く。
4. `bPGOOptimize` を有効にしてパッケージを再作成する。成功していた場合はビルドログに **[??? of ??? instructions (100.0%) were optimized using profile data]** といった最適化を行った旨のログがあります。

```cs:MyProject.Target.cs
public class MyProjectTarget : TargetRules
{
	public MyProjectTarget(TargetInfo Target) : base(Target)
	{
		//...省略...
		
		if( Target.Configuration == UnrealTargetConfiguration.Shipping
			|| Target.Configuration == UnrealTargetConfiguration.Test )
		{
			bPGOProfile = true;       // 計測パッケージ作成時
			//bPGOOptimize= true;　　　// PGO適用時
		}
		//...省略...
	}
}
```

:::note warn
注意
・計測/最適化パッケージのビルドコマンドで指定する場合は `-PGOProfile` 、`-PGOOptimize` となります。
・`bPGOProfile` `bPGOOptimize` は `BuildConfiguration.xml` で指定も可能です。
・Test版かShipping版でないと最適化されたパッケージが作成できないようです。
:::



# まとめ
CPU処理負荷が高い場所は アクター（Animation, Tick処理、物理、LOD）、UI処理、AI処理辺りが多いと思います。最近のプロジェクトではオブジェクトが多いので調べるのも大変です。
プログラマーが関わらない背景アーティストのオブジェクトなどはカメラ外で普通にTick処理が走っていたりLOD設定されていなかったりするので注意が必要です。