第8章 性能分析与诊断

第8章 性能分析与诊断

Addressables Profiler使用

Addressables Profiler基础

Unity Addressables Profiler是一个强大的工具,用于监控和分析Addressables系统的性能。它提供了详细的资源加载、缓存、依赖关系等信息,帮助开发者优化资源管理。

Addressables Profiler窗口介绍

Addressables Profiler窗口包含以下几个主要部分:

  1. Event Viewer - 显示资源加载事件的时间线
  2. Cache Profiler - 显示缓存使用情况
  3. Bundle Profiler - 显示AssetBundle加载和使用情况
  4. Catalog Profiler - 显示内容目录信息

启用Addressables Profiler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
using UnityEngine;
using UnityEditor;
using UnityEditor.AddressableAssets;

public class AddressablesProfilerSetup : EditorWindow
{
[MenuItem("Tools/Addressables/Profiler Setup")]
static void Init()
{
AddressablesProfilerSetup window = (AddressablesProfilerSetup)EditorWindow.GetWindow(typeof(AddressablesProfilerSetup));
window.titleContent = new GUIContent("Addressables Profiler Setup");
window.Show();
}

bool enableDetailedLogging = false;
bool enableMemoryLogging = false;
bool enableCacheLogging = false;

void OnGUI()
{
GUILayout.Label("Addressables Profiler配置", EditorStyles.boldLabel);

enableDetailedLogging = EditorGUILayout.Toggle("启用详细日志", enableDetailedLogging);
enableMemoryLogging = EditorGUILayout.Toggle("启用内存日志", enableMemoryLogging);
enableCacheLogging = EditorGUILayout.Toggle("启用缓存日志", enableCacheLogging);

if (GUILayout.Button("应用配置"))
{
ApplyProfilerConfig();
}

if (GUILayout.Button("打开Profiler窗口"))
{
OpenProfilerWindow();
}

if (GUILayout.Button("清除Profiler数据"))
{
ClearProfilerData();
}

GUILayout.Space(20);

GUILayout.Label("使用说明:", EditorStyles.boldLabel);
GUILayout.Label("1. 在运行时窗口中选择Addressables进行监控");
GUILayout.Label("2. 可以实时查看资源加载和缓存情况");
GUILayout.Label("3. 使用Event Viewer分析加载时间线");
}

void ApplyProfilerConfig()
{
// 在编辑器中配置Addressables设置
var settings = AddressableAssetSettingsDefaultObject.Settings;
if (settings != null)
{
// 配置日志级别
if (enableDetailedLogging)
{
Debug.unityLogger.logEnabled = true;
}

Debug.Log("Addressables Profiler配置已应用");
}
}

void OpenProfilerWindow()
{
// 打开Unity Profiler窗口
EditorApplication.ExecuteMenuItem("Window/Analysis/Profiler");

// 提示用户选择Addressables
Debug.Log("请在Profiler中选择Addressables进行监控");
}

void ClearProfilerData()
{
// 清除Profiler数据
UnityEditor.Profiling.ProfilerDriver.ClearAllFrames();
Debug.Log("Profiler数据已清除");
}
}

Profiler数据收集与分析

性能数据收集器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

public class PerformanceDataCollector : MonoBehaviour
{
[System.Serializable]
public class LoadEvent
{
public string assetAddress;
public string assetType;
public System.DateTime startTime;
public System.DateTime endTime;
public float loadTime;
public long size;
public bool success;
public string error;
public string group;
}

[System.Serializable]
public class CacheEvent
{
public string assetAddress;
public System.DateTime timestamp;
public bool isHit;
public long size;
public string cacheType; // "Memory", "Disk", "Download"
}

[Header("性能监控设置")]
public bool collectLoadEvents = true;
public bool collectCacheEvents = true;
public int maxEventsToStore = 1000;
public float reportInterval = 5f;

private List<LoadEvent> loadEvents = new List<LoadEvent>();
private List<CacheEvent> cacheEvents = new List<CacheEvent>();
private Dictionary<string, System.DateTime> loadStartTimes = new Dictionary<string, System.DateTime>();

void Start()
{
if (reportInterval > 0)
{
InvokeRepeating("ReportPerformanceData", 1f, reportInterval);
}
}

/// <summary>
/// 记录加载开始
/// </summary>
public void RecordLoadStart(string assetAddress, string assetType = "Unknown", string group = "Default")
{
if (!collectLoadEvents) return;

loadStartTimes[assetAddress] = System.DateTime.Now;

Debug.Log($"开始加载: {assetAddress} ({assetType})");
}

/// <summary>
/// 记录加载完成
/// </summary>
public void RecordLoadComplete(string assetAddress, bool success = true, string error = null)
{
if (!collectLoadEvents) return;

if (loadStartTimes.ContainsKey(assetAddress))
{
var startTime = loadStartTimes[assetAddress];
var endTime = System.DateTime.Now;
var loadTime = (float)(endTime - startTime).TotalSeconds;

var loadEvent = new LoadEvent
{
assetAddress = assetAddress,
startTime = startTime,
endTime = endTime,
loadTime = loadTime,
success = success,
error = error
};

loadEvents.Add(loadEvent);

// 限制事件数量
if (loadEvents.Count > maxEventsToStore)
{
loadEvents.RemoveAt(0);
}

loadStartTimes.Remove(assetAddress);

if (success)
{
Debug.Log($"加载完成: {assetAddress}, 耗时: {loadTime:F3}s");
}
else
{
Debug.LogError($"加载失败: {assetAddress}, 错误: {error}");
}
}
}

/// <summary>
/// 记录缓存事件
/// </summary>
public void RecordCacheEvent(string assetAddress, bool isHit, long size = 0, string cacheType = "Memory")
{
if (!collectCacheEvents) return;

var cacheEvent = new CacheEvent
{
assetAddress = assetAddress,
timestamp = System.DateTime.Now,
isHit = isHit,
size = size,
cacheType = cacheType
};

cacheEvents.Add(cacheEvent);

// 限制事件数量
if (cacheEvents.Count > maxEventsToStore)
{
cacheEvents.RemoveAt(0);
}

Debug.Log($"缓存事件: {assetAddress}, 类型: {cacheType}, 命中: {isHit}");
}

/// <summary>
/// 获取加载性能统计
/// </summary>
public string GetLoadPerformanceStats()
{
if (loadEvents.Count == 0) return "无加载数据";

float totalTime = 0;
int successCount = 0;
int errorCount = 0;
float maxLoadTime = 0;
float minLoadTime = float.MaxValue;

foreach (var eventItem in loadEvents)
{
totalTime += eventItem.loadTime;

if (eventItem.success)
{
successCount++;
maxLoadTime = Mathf.Max(maxLoadTime, eventItem.loadTime);
minLoadTime = Mathf.Min(minLoadTime, eventItem.loadTime);
}
else
{
errorCount++;
}
}

float avgLoadTime = totalTime / loadEvents.Count;
float successRate = (float)successCount / loadEvents.Count * 100;

return $"总加载数: {loadEvents.Count}, 成功率: {successRate:F1}%, " +
$"平均耗时: {avgLoadTime:F3}s, 最大耗时: {maxLoadTime:F3}s, 最小耗时: {minLoadTime:F3}s";
}

/// <summary>
/// 获取缓存性能统计
/// </summary>
public string GetCachePerformanceStats()
{
if (cacheEvents.Count == 0) return "无缓存数据";

int hitCount = 0;
int missCount = 0;
long totalSize = 0;

foreach (var eventItem in cacheEvents)
{
if (eventItem.isHit)
{
hitCount++;
}
else
{
missCount++;
}
totalSize += eventItem.size;
}

float hitRate = (float)hitCount / cacheEvents.Count * 100;

return $"总缓存事件: {cacheEvents.Count}, 命中率: {hitRate:F1}%, " +
$"总大小: {FormatFileSize(totalSize)}, 命中: {hitCount}, 未命中: {missCount}";
}

/// <summary>
/// 报告性能数据
/// </summary>
private void ReportPerformanceData()
{
if (collectLoadEvents)
{
Debug.Log($"[性能报告] 加载统计: {GetLoadPerformanceStats()}");
}

if (collectCacheEvents)
{
Debug.Log($"[性能报告] 缓存统计: {GetCachePerformanceStats()}");
}
}

/// <summary>
/// 清除性能数据
/// </summary>
public void ClearPerformanceData()
{
loadEvents.Clear();
cacheEvents.Clear();
loadStartTimes.Clear();

Debug.Log("性能数据已清除");
}

/// <summary>
/// 格式化文件大小
/// </summary>
private string FormatFileSize(long bytes)
{
string[] sizes = { "B", "KB", "MB", "GB" };
double len = bytes;
int order = 0;
while (len >= 1024 && order < sizes.Length - 1)
{
order++;
len = len / 1024;
}
return $"{len:F2}{sizes[order]}";
}

void OnDestroy()
{
CancelInvoke();
}
}

实时性能监控

实时监控面板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class RealTimePerformanceMonitor : MonoBehaviour
{
[Header("UI引用")]
public Text loadStatsText;
public Text cacheStatsText;
public Text memoryStatsText;
public Slider loadProgressSlider;
public Text statusText;

[Header("监控设置")]
public float updateInterval = 1f;
public bool showDetailedStats = true;

private PerformanceDataCollector dataCollector;
private float lastUpdateTime = 0;

void Start()
{
dataCollector = FindObjectOfType<PerformanceDataCollector>();
if (dataCollector == null)
{
dataCollector = gameObject.AddComponent<PerformanceDataCollector>();
}

UpdateUI();
}

void Update()
{
if (Time.time - lastUpdateTime >= updateInterval)
{
UpdateUI();
lastUpdateTime = Time.time;
}
}

private void UpdateUI()
{
if (loadStatsText != null)
{
loadStatsText.text = dataCollector.GetLoadPerformanceStats();
}

if (cacheStatsText != null)
{
cacheStatsText.text = dataCollector.GetCachePerformanceStats();
}

if (memoryStatsText != null)
{
memoryStatsText.text = GetMemoryStats();
}

if (statusText != null)
{
statusText.text = $"运行时间: {Time.realtimeSinceStartup:F1}s\n" +
$"对象数量: {FindObjectsOfType<UnityEngine.Object>().Length}";
}
}

private string GetMemoryStats()
{
long usedMemory = UnityEngine.Profiling.Profiler.usedHeapSizeLong;
long totalMemory = SystemInfo.systemMemorySize * 1024L * 1024L;
float usagePercent = (float)usedMemory / totalMemory * 100;

return $"内存使用: {FormatFileSize(usedMemory)} / {FormatFileSize(totalMemory)} ({usagePercent:F1}%)";
}

private string FormatFileSize(long bytes)
{
string[] sizes = { "B", "KB", "MB", "GB" };
double len = bytes;
int order = 0;
while (len >= 1024 && order < sizes.Length - 1)
{
order++;
len = len / 1024;
}
return $"{len:F2}{sizes[order]}";
}

public void ClearData()
{
dataCollector.ClearPerformanceData();
}
}

Event Viewer分析加载链路

Event Viewer基础

Unity Addressables Event Viewer是分析资源加载时间线的强大工具。它显示了资源请求、依赖解析、下载、解压、实例化等各个阶段的时间消耗。

Event Viewer数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
using System;
using System.Collections.Generic;
using UnityEngine;

public class EventViewerAnalyzer
{
[System.Serializable]
public class LoadTimelineEvent
{
public string assetAddress;
public string eventType; // "Request", "Download", "CacheCheck", "Instantiate", "Complete"
public System.DateTime timestamp;
public float duration; // 持续时间(秒)
public string status; // "Start", "Progress", "Complete", "Error"
public string details; // 详细信息
public int depth; // 嵌套深度,用于显示依赖关系
}

[System.Serializable]
public class LoadChain
{
public string rootAsset;
public List<LoadTimelineEvent> events;
public float totalDuration;
public bool success;
public string error;
}

private List<LoadChain> loadChains = new List<LoadChain>();
private Dictionary<string, LoadChain> activeChains = new Dictionary<string, LoadChain>();

/// <summary>
/// 开始记录加载链
/// </summary>
public void StartLoadChain(string assetAddress)
{
var chain = new LoadChain
{
rootAsset = assetAddress,
events = new List<LoadTimelineEvent>(),
success = true
};

activeChains[assetAddress] = chain;

Debug.Log($"开始加载链: {assetAddress}");
}

/// <summary>
/// 记录加载事件
/// </summary>
public void RecordLoadEvent(string assetAddress, string eventType, string status, string details = "", float duration = 0)
{
if (!activeChains.ContainsKey(assetAddress))
{
StartLoadChain(assetAddress);
}

var chain = activeChains[assetAddress];
var eventItem = new LoadTimelineEvent
{
assetAddress = assetAddress,
eventType = eventType,
timestamp = DateTime.Now,
duration = duration,
status = status,
details = details,
depth = 0 // 简化实现,实际可能需要根据依赖关系设置深度
};

chain.events.Add(eventItem);

Debug.Log($"记录事件: {eventType} - {status} - {details}");
}

/// <summary>
/// 完成加载链
/// </summary>
public void CompleteLoadChain(string assetAddress, bool success = true, string error = null)
{
if (activeChains.ContainsKey(assetAddress))
{
var chain = activeChains[assetAddress];
chain.success = success;
chain.error = error;

// 计算总持续时间
if (chain.events.Count > 0)
{
var firstEvent = chain.events[0];
var lastEvent = chain.events[chain.events.Count - 1];
chain.totalDuration = (float)(lastEvent.timestamp - firstEvent.timestamp).TotalSeconds;
}

loadChains.Add(chain);
activeChains.Remove(assetAddress);

Debug.Log($"加载链完成: {assetAddress}, 耗时: {chain.totalDuration:F3}s, 成功: {success}");
}
}

/// <summary>
/// 分析加载链性能
/// </summary>
public string AnalyzeLoadChainPerformance(string assetAddress)
{
var chain = GetLoadChain(assetAddress);
if (chain == null) return "未找到加载链";

var analysis = new System.Text.StringBuilder();
analysis.AppendLine($"=== 加载链分析: {assetAddress} ===");
analysis.AppendLine($"总耗时: {chain.totalDuration:F3}s");
analysis.AppendLine($"成功: {chain.success}");

if (!chain.success)
{
analysis.AppendLine($"错误: {chain.error}");
}

analysis.AppendLine("事件详情:");
foreach (var eventItem in chain.events)
{
analysis.AppendLine($" {eventItem.eventType} - {eventItem.status} - {eventItem.duration:F3}s - {eventItem.details}");
}

return analysis.ToString();
}

/// <summary>
/// 获取加载链
/// </summary>
private LoadChain GetLoadChain(string assetAddress)
{
// 首先检查活跃链
if (activeChains.ContainsKey(assetAddress))
{
return activeChains[assetAddress];
}

// 然后检查已完成的链
return loadChains.Find(c => c.rootAsset == assetAddress);
}

/// <summary>
/// 获取性能瓶颈分析
/// </summary>
public string GetPerformanceBottleneckAnalysis()
{
var analysis = new System.Text.StringBuilder();
analysis.AppendLine("=== 性能瓶颈分析 ===");

// 找出耗时最长的加载链
LoadChain longestChain = null;
float maxDuration = 0;

foreach (var chain in loadChains)
{
if (chain.totalDuration > maxDuration)
{
maxDuration = chain.totalDuration;
longestChain = chain;
}
}

if (longestChain != null)
{
analysis.AppendLine($"最长加载链: {longestChain.rootAsset}, 耗时: {maxDuration:F3}s");

// 找出该链中耗时最长的事件
LoadTimelineEvent longestEvent = null;
float maxEventDuration = 0;

foreach (var eventItem in longestChain.events)
{
if (eventItem.duration > maxEventDuration)
{
maxEventDuration = eventItem.duration;
longestEvent = eventItem;
}
}

if (longestEvent != null)
{
analysis.AppendLine($"瓶颈事件: {longestEvent.eventType}, 耗时: {maxEventDuration:F3}s");
analysis.AppendLine($"事件详情: {longestEvent.details}");
}
}

// 统计失败的加载
int failedCount = 0;
foreach (var chain in loadChains)
{
if (!chain.success) failedCount++;
}

analysis.AppendLine($"失败加载数: {failedCount}/{loadChains.Count}");

return analysis.ToString();
}

/// <summary>
/// 清除分析数据
/// </summary>
public void ClearAnalysisData()
{
loadChains.Clear();
activeChains.Clear();

Debug.Log("Event Viewer分析数据已清除");
}

/// <summary>
/// 导出分析报告
/// </summary>
public string ExportAnalysisReport()
{
var report = new System.Text.StringBuilder();
report.AppendLine("Addressables Event Viewer 分析报告");
report.AppendLine($"生成时间: {DateTime.Now}");
report.AppendLine($"总加载链数: {loadChains.Count}");
report.AppendLine();

report.AppendLine(GetPerformanceBottleneckAnalysis());
report.AppendLine();

// 详细加载链信息
foreach (var chain in loadChains)
{
report.AppendLine(AnalyzeLoadChainPerformance(chain.rootAsset));
report.AppendLine();
}

return report.ToString();
}
}

加载链可视化

加载链可视化工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class LoadChainVisualizer : MonoBehaviour
{
[Header("可视化设置")]
public GameObject eventItemPrefab;
public Transform contentParent;
public ScrollRect scrollRect;

[Header("颜色配置")]
public Color requestColor = Color.blue;
public Color downloadColor = Color.green;
public Color cacheColor = Color.yellow;
public Color instantiateColor = Color.cyan;
public Color errorColor = Color.red;

private EventViewerAnalyzer analyzer;
private List<GameObject> eventItems = new List<GameObject>();

void Start()
{
analyzer = FindObjectOfType<EventViewerAnalyzer>();
if (analyzer == null)
{
analyzer = new GameObject("EventViewerAnalyzer").AddComponent<EventViewerAnalyzer>();
}
}

/// <summary>
/// 可视化特定加载链
/// </summary>
public void VisualizeLoadChain(string assetAddress)
{
ClearVisualization();

var chain = GetLoadChain(assetAddress);
if (chain == null) return;

foreach (var eventItem in chain.events)
{
CreateEventVisual(eventItem);
}
}

private EventViewerAnalyzer.LoadChain GetLoadChain(string assetAddress)
{
// 这里需要访问EventViewerAnalyzer的私有成员
// 在实际实现中,可能需要将相关方法设为public
return null; // 简化实现
}

private void CreateEventVisual(EventViewerAnalyzer.LoadTimelineEvent eventData)
{
if (eventItemPrefab == null || contentParent == null) return;

GameObject itemObj = Instantiate(eventItemPrefab, contentParent);
var itemUI = itemObj.GetComponent<LoadEventUI>();

if (itemUI != null)
{
itemUI.Initialize(eventData, GetEventColor(eventData.eventType));
}

eventItems.Add(itemObj);
}

private Color GetEventColor(string eventType)
{
switch (eventType.ToLower())
{
case "request":
return requestColor;
case "download":
return downloadColor;
case "cachecheck":
return cacheColor;
case "instantiate":
return instantiateColor;
case "error":
return errorColor;
default:
return Color.white;
}
}

private void ClearVisualization()
{
foreach (var item in eventItems)
{
if (item != null) Destroy(item);
}
eventItems.Clear();
}

/// <summary>
/// 可视化所有加载链
/// </summary>
public void VisualizeAllChains()
{
ClearVisualization();

// 这里需要访问所有加载链数据
// 在实际实现中,EventViewerAnalyzer需要提供公共方法
}
}

// 加载事件UI组件
public class LoadEventUI : MonoBehaviour
{
[Header("UI引用")]
public Text eventText;
public Image eventColor;
public Slider durationSlider;

public void Initialize(EventViewerAnalyzer.LoadTimelineEvent eventData, Color color)
{
if (eventText != null)
{
eventText.text = $"{eventData.eventType} - {eventData.status}\n{eventData.details}\n耗时: {eventData.duration:F3}s";
}

if (eventColor != null)
{
eventColor.color = color;
}

if (durationSlider != null)
{
durationSlider.value = eventData.duration;
}
}
}

Build Layout Report解读

构建布局报告生成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEditor.AddressableAssets.Settings;
using UnityEditor.AddressableAssets.Settings.GroupSchemas;

public class BuildLayoutReportGenerator
{
[System.Serializable]
public class BundleInfo
{
public string bundleName;
public string groupName;
public long size;
public int assetCount;
public string[] assets;
public string[] dependencies;
public string compression;
public string buildPath;
public string loadPath;
}

[System.Serializable]
public class BuildReport
{
public string buildTime;
public long totalSize;
public int bundleCount;
public int assetCount;
public List<BundleInfo> bundles;
public string[] warnings;
public string[] errors;
}

/// <summary>
/// 生成构建布局报告
/// </summary>
public static BuildReport GenerateBuildLayoutReport(AddressableAssetSettings settings)
{
var report = new BuildReport
{
buildTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
bundles = new List<BundleInfo>(),
warnings = new string[0],
errors = new string[0]
};

long totalSize = 0;
int totalAssets = 0;

foreach (var group in settings.groups)
{
if (group.HasSchema<BundledAssetGroupSchema>())
{
var bundleInfo = GenerateBundleInfo(group);
report.bundles.Add(bundleInfo);

totalSize += bundleInfo.size;
totalAssets += bundleInfo.assetCount;
}
}

report.totalSize = totalSize;
report.bundleCount = report.bundles.Count;
report.assetCount = totalAssets;

return report;
}

private static BundleInfo GenerateBundleInfo(AddressableAssetGroup group)
{
var bundleInfo = new BundleInfo
{
groupName = group.Name,
assetCount = group.entries.Count,
assets = new string[group.entries.Count]
};

for (int i = 0; i < group.entries.Count; i++)
{
bundleInfo.assets[i] = group.entries[i].address;
}

// 获取构建和加载路径
var bundleSchema = group.GetSchema<BundledAssetGroupSchema>();
if (bundleSchema != null)
{
bundleInfo.buildPath = bundleSchema.BuildPath.GetValue(settings);
bundleInfo.loadPath = bundleSchema.LoadPath.GetValue(settings);

// 获取压缩方式
switch (bundleSchema.Compression)
{
case BundledAssetGroupSchema.BundleCompressionMode.Uncompressed:
bundleInfo.compression = "Uncompressed";
break;
case BundledAssetGroupSchema.BundleCompressionMode.LZ4:
bundleInfo.compression = "LZ4";
break;
case BundledAssetGroupSchema.BundleCompressionMode.LZMA:
bundleInfo.compression = "LZMA";
break;
}
}

// 估算大小(实际构建后才能知道准确大小)
bundleInfo.size = EstimateBundleSize(group);

return bundleInfo;
}

private static long EstimateBundleSize(AddressableAssetGroup group)
{
long estimatedSize = 0;

foreach (var entry in group.entries)
{
string assetPath = UnityEditor.AssetDatabase.GUIDToAssetPath(entry.guid);
if (!string.IsNullOrEmpty(assetPath))
{
var importer = UnityEditor.AssetImporter.GetAtPath(assetPath);
if (importer != null)
{
// 根据资源类型估算大小
if (assetPath.EndsWith(".png") || assetPath.EndsWith(".jpg"))
{
estimatedSize += 1024 * 1024; // 假设1MB
}
else if (assetPath.EndsWith(".fbx"))
{
estimatedSize += 512 * 1024; // 假设512KB
}
else
{
estimatedSize += 64 * 1024; // 假设64KB
}
}
}
}

return estimatedSize;
}

/// <summary>
/// 导出构建报告为JSON
/// </summary>
public static void ExportBuildReport(BuildReport report, string filePath)
{
string json = JsonUtility.ToJson(report, true);
File.WriteAllText(filePath, json);

Debug.Log($"构建报告已导出到: {filePath}");
}

/// <summary>
/// 解析构建报告
/// </summary>
public static BuildReport ParseBuildReport(string filePath)
{
if (!File.Exists(filePath))
{
Debug.LogError($"构建报告文件不存在: {filePath}");
return null;
}

string json = File.ReadAllText(filePath);
var report = JsonUtility.FromJson<BuildReport>(json);

Debug.Log($"构建报告已解析: {filePath}");
return report;
}

/// <summary>
/// 生成构建报告摘要
/// </summary>
public static string GenerateBuildReportSummary(BuildReport report)
{
var summary = new System.Text.StringBuilder();
summary.AppendLine("=== 构建布局报告摘要 ===");
summary.AppendLine($"构建时间: {report.buildTime}");
summary.AppendLine($"总大小: {FormatFileSize(report.totalSize)}");
summary.AppendLine($"Bundle数量: {report.bundleCount}");
summary.AppendLine($"资源数量: {report.assetCount}");
summary.AppendLine();

summary.AppendLine("Bundle详情:");
foreach (var bundle in report.bundles)
{
summary.AppendLine($" {bundle.bundleName ?? bundle.groupName}: " +
$"{FormatFileSize(bundle.size)}, {bundle.assetCount}个资源, " +
$"压缩: {bundle.compression}");
}

if (report.warnings.Length > 0)
{
summary.AppendLine();
summary.AppendLine("警告:");
foreach (string warning in report.warnings)
{
summary.AppendLine($" {warning}");
}
}

if (report.errors.Length > 0)
{
summary.AppendLine();
summary.AppendLine("错误:");
foreach (string error in report.errors)
{
summary.AppendLine($" {error}");
}
}

return summary.ToString();
}

private static string FormatFileSize(long bytes)
{
string[] sizes = { "B", "KB", "MB", "GB" };
double len = bytes;
int order = 0;
while (len >= 1024 && order < sizes.Length - 1)
{
order++;
len = len / 1024;
}
return $"{len:F2}{sizes[order]}";
}
}

构建报告分析工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
using System.Collections.Generic;
using UnityEngine;

public class BuildReportAnalyzer
{
/// <summary>
/// 分析构建报告中的问题
/// </summary>
public static List<string> AnalyzeBuildReportIssues(BuildLayoutReportGenerator.BuildReport report)
{
var issues = new List<string>();

// 检查大的bundles
foreach (var bundle in report.bundles)
{
if (bundle.size > 50 * 1024 * 1024) // 50MB
{
issues.Add($"Bundle过大: {bundle.groupName} - {BuildLayoutReportGenerator.FormatFileSize(bundle.size)}, " +
$"包含 {bundle.assetCount} 个资源");
}
}

// 检查单一资源bundles
foreach (var bundle in report.bundles)
{
if (bundle.assetCount == 1 && bundle.size < 1024) // 小于1KB的单资源bundle
{
issues.Add($"单资源Bundle过小: {bundle.groupName} - {bundle.assets[0]}");
}
}

// 检查重复资源
var assetCountMap = new Dictionary<string, int>();
foreach (var bundle in report.bundles)
{
foreach (string asset in bundle.assets)
{
if (assetCountMap.ContainsKey(asset))
{
assetCountMap[asset]++;
}
else
{
assetCountMap[asset] = 1;
}
}
}

foreach (var pair in assetCountMap)
{
if (pair.Value > 1)
{
issues.Add($"资源重复打包: {pair.Key} 出现在 {pair.Value} 个bundles中");
}
}

return issues;
}

/// <summary>
/// 优化建议
/// </summary>
public static List<string> GenerateOptimizationSuggestions(BuildLayoutReportGenerator.BuildReport report)
{
var suggestions = new List<string>();

// Bundle大小优化建议
int largeBundles = 0;
long totalLargeSize = 0;

foreach (var bundle in report.bundles)
{
if (bundle.size > 20 * 1024 * 1024) // 20MB
{
largeBundles++;
totalLargeSize += bundle.size;
}
}

if (largeBundles > 0)
{
suggestions.Add($"发现 {largeBundles} 个大Bundle,总计 {BuildLayoutReportGenerator.FormatFileSize(totalLargeSize)}。" +
$"建议将大资源单独打包或按功能分组。");
}

// 压缩方式建议
int uncompressedBundles = 0;
foreach (var bundle in report.bundles)
{
if (bundle.compression == "Uncompressed")
{
uncompressedBundles++;
}
}

if (uncompressedBundles > 0)
{
suggestions.Add($"发现 {uncompressedBundles} 个未压缩的Bundle。" +
$"建议对非纹理/音频资源使用LZ4压缩。");
}

// 资源分组建议
var groupAssetCounts = new Dictionary<string, int>();
foreach (var bundle in report.bundles)
{
if (groupAssetCounts.ContainsKey(bundle.groupName))
{
groupAssetCounts[bundle.groupName] += bundle.assetCount;
}
else
{
groupAssetCounts[bundle.groupName] = bundle.assetCount;
}
}

foreach (var pair in groupAssetCounts)
{
if (pair.Value > 100) // 一个组超过100个资源
{
suggestions.Add($"组 '{pair.Key}' 包含 {pair.Value} 个资源,建议进一步细分。");
}
}

return suggestions;
}

/// <summary>
/// 性能影响评估
/// </summary>
public static string AssessPerformanceImpact(BuildLayoutReportGenerator.BuildReport report)
{
var assessment = new System.Text.StringBuilder();
assessment.AppendLine("=== 性能影响评估 ===");

// 下载时间估算
float estimatedDownloadTime = 0;
foreach (var bundle in report.bundles)
{
// 假设下载速度为1MB/s
float downloadTime = bundle.size / (1024f * 1024f); // 秒
estimatedDownloadTime += downloadTime;
}

assessment.AppendLine($"估算总下载时间: {estimatedDownloadTime:F2}秒 (1MB/s)");

// 并发下载影响
int maxConcurrentDownloads = 4; // 假设最大并发数
float parallelDownloadTime = estimatedDownloadTime / maxConcurrentDownloads;
assessment.AppendLine($"并行下载时间: {parallelDownloadTime:F2}秒 ({maxConcurrentDownloads}并发)");

// 内存影响
long peakMemoryUsage = 0;
foreach (var bundle in report.bundles)
{
// 估算解压时的内存使用(假设解压后大小是压缩大小的3倍)
long decompressedSize = bundle.size * 3;
peakMemoryUsage += decompressedSize;
}

assessment.AppendLine($"估算峰值内存使用: {BuildLayoutReportGenerator.FormatFileSize(peakMemoryUsage)}");

return assessment.ToString();
}
}

依赖关系可视化

依赖关系分析器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;

public class DependencyVisualizer
{
[System.Serializable]
public class DependencyNode
{
public string address;
public string type;
public List<string> dependencies;
public List<string> dependents;
public int depth;
public bool isRoot;
public long size;
}

[System.Serializable]
public class DependencyGraph
{
public List<DependencyNode> nodes;
public string[] rootNodes;
}

/// <summary>
/// 分析资源依赖关系
/// </summary>
public static DependencyGraph AnalyzeDependencies(string[] rootAddresses)
{
var graph = new DependencyGraph
{
nodes = new List<DependencyNode>(),
rootNodes = rootAddresses
};

var processed = new HashSet<string>();
var queue = new Queue<string>();

// 添加根节点
foreach (string address in rootAddresses)
{
queue.Enqueue(address);
}

while (queue.Count > 0)
{
string currentAddress = queue.Dequeue();

if (processed.Contains(currentAddress))
continue;

processed.Add(currentAddress);

var node = CreateDependencyNode(currentAddress);
graph.nodes.Add(node);

// 将依赖项加入队列
foreach (string dependency in node.dependencies)
{
if (!processed.Contains(dependency))
{
queue.Enqueue(dependency);
}
}
}

return graph;
}

private static DependencyNode CreateDependencyNode(string address)
{
var node = new DependencyNode
{
address = address,
dependencies = new List<string>(),
dependents = new List<string>(),
isRoot = true // 会在后续处理中更新
};

// 获取依赖信息(这需要Addressables的内部API)
// 在实际实现中,可能需要使用Reflection或Addressables的分析工具
node.dependencies = GetDependencies(address);

// 估算大小
node.size = EstimateAssetSize(address);

return node;
}

private static List<string> GetDependencies(string address)
{
// 这里需要使用Addressables的依赖分析功能
// 简化实现,返回空列表
return new List<string>();
}

private static long EstimateAssetSize(string address)
{
// 估算资源大小
// 简化实现,返回固定值
return 1024 * 1024; // 1MB
}

/// <summary>
/// 生成依赖关系图的文本表示
/// </summary>
public static string GenerateDependencyTree(DependencyGraph graph, string rootAddress)
{
var tree = new System.Text.StringBuilder();
tree.AppendLine($"=== 依赖关系树: {rootAddress} ===");

var nodeMap = new Dictionary<string, DependencyNode>();
foreach (var node in graph.nodes)
{
nodeMap[node.address] = node;
}

var visited = new HashSet<string>();
BuildDependencyTree(tree, rootAddress, nodeMap, visited, 0);

return tree.ToString();
}

private static void BuildDependencyTree(System.Text.StringBuilder tree, string address,
Dictionary<string, DependencyNode> nodeMap, HashSet<string> visited, int depth)
{
if (visited.Contains(address) || !nodeMap.ContainsKey(address))
return;

visited.Add(address);

var node = nodeMap[address];
string indent = new string(' ', depth * 2);

tree.AppendLine($"{indent}{node.address} ({BuildLayoutReportGenerator.FormatFileSize(node.size)})");

foreach (string dependency in node.dependencies)
{
BuildDependencyTree(tree, dependency, nodeMap, visited, depth + 1);
}
}

/// <summary>
/// 查找循环依赖
/// </summary>
public static List<string> FindCircularDependencies(DependencyGraph graph)
{
var circularDeps = new List<string>();
var allNodes = new Dictionary<string, DependencyNode>();

foreach (var node in graph.nodes)
{
allNodes[node.address] = node;
}

foreach (var node in graph.nodes)
{
var path = new List<string>();
if (HasCircularDependency(node.address, allNodes, path))
{
circularDeps.Add($"循环依赖: {string.Join(" -> ", path)} -> {node.address}");
}
}

return circularDeps;
}

private static bool HasCircularDependency(string current, Dictionary<string, DependencyNode> nodes,
List<string> path)
{
if (path.Contains(current))
{
path.Add(current);
return true;
}

if (!nodes.ContainsKey(current))
return false;

path.Add(current);

var node = nodes[current];
foreach (string dependency in node.dependencies)
{
if (HasCircularDependency(dependency, nodes, new List<string>(path)))
{
return true;
}
}

return false;
}

/// <summary>
/// 生成依赖关系统计
/// </summary>
public static string GenerateDependencyStats(DependencyGraph graph)
{
int totalNodes = graph.nodes.Count;
int totalDependencies = 0;
long totalSize = 0;
int maxDepth = 0;

foreach (var node in graph.nodes)
{
totalDependencies += node.dependencies.Count;
totalSize += node.size;

if (node.dependencies.Count > 0)
{
maxDepth = Math.Max(maxDepth, CalculateMaxDepth(node.address, graph));
}
}

float avgDependencies = totalNodes > 0 ? (float)totalDependencies / totalNodes : 0;

var stats = new System.Text.StringBuilder();
stats.AppendLine("=== 依赖关系统计 ===");
stats.AppendLine($"节点数量: {totalNodes}");
stats.AppendLine($"总依赖数: {totalDependencies}");
stats.AppendLine($"平均依赖数: {avgDependencies:F2}");
stats.AppendLine($"总大小: {BuildLayoutReportGenerator.FormatFileSize(totalSize)}");
stats.AppendLine($"最大依赖深度: {maxDepth}");

return stats.ToString();
}

private static int CalculateMaxDepth(string address, DependencyGraph graph)
{
var nodeMap = new Dictionary<string, DependencyNode>();
foreach (var node in graph.nodes)
{
nodeMap[node.address] = node;
}

return CalculateMaxDepthRecursive(address, nodeMap, new HashSet<string>(), 0);
}

private static int CalculateMaxDepthRecursive(string address, Dictionary<string, DependencyNode> nodeMap,
HashSet<string> visited, int currentDepth)
{
if (!nodeMap.ContainsKey(address) || visited.Contains(address))
return currentDepth;

visited.Add(address);
var node = nodeMap[address];

int maxDepth = currentDepth;
foreach (string dependency in node.dependencies)
{
int depth = CalculateMaxDepthRecursive(dependency, nodeMap,
new HashSet<string>(visited), currentDepth + 1);
maxDepth = Math.Max(maxDepth, depth);
}

return maxDepth;
}
}

常见性能问题识别

性能问题检测器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;

public class PerformanceIssueDetector
{
[System.Serializable]
public class PerformanceIssue
{
public string issueType;
public string description;
public string severity; // "Low", "Medium", "High", "Critical"
public string assetAddress;
public string recommendation;
public DateTime detectedTime;
}

[System.Serializable]
public class PerformanceReport
{
public List<PerformanceIssue> issues;
public string summary;
public DateTime reportTime;
public float analysisDuration;
}

/// <summary>
/// 检测性能问题
/// </summary>
public static PerformanceReport DetectPerformanceIssues()
{
var report = new PerformanceReport
{
issues = new List<PerformanceIssue>(),
reportTime = DateTime.Now
};

var startTime = DateTime.Now;

// 检测各种性能问题
report.issues.AddRange(DetectLargeBundleIssues());
report.issues.AddRange(DetectMemoryIssues());
report.issues.AddRange(DetectLoadingTimeIssues());
report.issues.AddRange(DetectCacheIssues());
report.issues.AddRange(DetectDependencyIssues());

report.analysisDuration = (float)(DateTime.Now - startTime).TotalSeconds;

// 生成摘要
var summary = new System.Text.StringBuilder();
summary.AppendLine("=== 性能问题检测报告 ===");
summary.AppendLine($"检测时间: {report.reportTime}");
summary.AppendLine($"分析耗时: {report.analysisDuration:F2}秒");
summary.AppendLine($"发现问题数: {report.issues.Count}");

int critical = 0, high = 0, medium = 0, low = 0;
foreach (var issue in report.issues)
{
switch (issue.severity.ToLower())
{
case "critical": critical++; break;
case "high": high++; break;
case "medium": medium++; break;
case "low": low++; break;
}
}

summary.AppendLine($"严重: {critical}, 高: {high}, 中: {medium}, 低: {low}");

report.summary = summary.ToString();

return report;
}

private static List<PerformanceIssue> DetectLargeBundleIssues()
{
var issues = new List<PerformanceIssue>();

// 这里需要访问Addressables的内部数据来检测大bundle
// 简化实现,返回示例数据
if (UnityEngine.Random.value > 0.9f) // 模拟检测到问题
{
issues.Add(new PerformanceIssue
{
issueType = "Large Bundle",
description = "发现过大的AssetBundle,可能影响加载性能",
severity = "High",
assetAddress = "LargeBundle",
recommendation = "考虑将大资源分离到独立的bundle中",
detectedTime = DateTime.Now
});
}

return issues;
}

private static List<PerformanceIssue> DetectMemoryIssues()
{
var issues = new List<PerformanceIssue>();

// 检测内存使用问题
long usedMemory = UnityEngine.Profiling.Profiler.usedHeapSizeLong;
long totalMemory = SystemInfo.systemMemorySize * 1024L * 1024L;
float memoryUsage = (float)usedMemory / totalMemory;

if (memoryUsage > 0.8f)
{
issues.Add(new PerformanceIssue
{
issueType = "High Memory Usage",
description = $"内存使用率过高: {(memoryUsage * 100):F1}%",
severity = "High",
assetAddress = "Memory",
recommendation = "检查资源引用,及时释放不需要的资源",
detectedTime = DateTime.Now
});
}

return issues;
}

private static List<PerformanceIssue> DetectLoadingTimeIssues()
{
var issues = new List<PerformanceIssue>();

// 这里需要监控加载时间
// 简化实现,返回示例数据
if (UnityEngine.Random.value > 0.95f) // 模拟检测到问题
{
issues.Add(new PerformanceIssue
{
issueType = "Slow Loading",
description = "资源加载时间过长",
severity = "Medium",
assetAddress = "SlowAsset",
recommendation = "优化资源大小或使用预加载",
detectedTime = DateTime.Now
});
}

return issues;
}

private static List<PerformanceIssue> DetectCacheIssues()
{
var issues = new List<PerformanceIssue>();

// 检测缓存问题
if (UnityEngine.Random.value > 0.9f) // 模拟检测到问题
{
issues.Add(new PerformanceIssue
{
issueType = "Cache Miss",
description = "缓存命中率低",
severity = "Medium",
assetAddress = "Cache",
recommendation = "优化缓存策略",
detectedTime = DateTime.Now
});
}

return issues;
}

private static List<PerformanceIssue> DetectDependencyIssues()
{
var issues = new List<PerformanceIssue>();

// 检测依赖问题
if (UnityEngine.Random.value > 0.95f) // 模拟检测到问题
{
issues.Add(new PerformanceIssue
{
issueType = "Deep Dependencies",
description = "依赖层级过深",
severity = "High",
assetAddress = "ComplexDependencies",
recommendation = "简化依赖关系",
detectedTime = DateTime.Now
});
}

return issues;
}

/// <summary>
/// 生成性能优化建议
/// </summary>
public static string GenerateOptimizationRecommendations(PerformanceReport report)
{
var recommendations = new System.Text.StringBuilder();
recommendations.AppendLine("=== 性能优化建议 ===");

var criticalIssues = new List<PerformanceIssue>();
var highIssues = new List<PerformanceIssue>();
var mediumIssues = new List<PerformanceIssue>();
var lowIssues = new List<PerformanceIssue>();

foreach (var issue in report.issues)
{
switch (issue.severity.ToLower())
{
case "critical": criticalIssues.Add(issue); break;
case "high": highIssues.Add(issue); break;
case "medium": mediumIssues.Add(issue); break;
case "low": lowIssues.Add(issue); break;
}
}

if (criticalIssues.Count > 0)
{
recommendations.AppendLine("紧急修复:");
foreach (var issue in criticalIssues)
{
recommendations.AppendLine($" - {issue.description}: {issue.recommendation}");
}
}

if (highIssues.Count > 0)
{
recommendations.AppendLine("高优先级修复:");
foreach (var issue in highIssues)
{
recommendations.AppendLine($" - {issue.description}: {issue.recommendation}");
}
}

if (mediumIssues.Count > 0)
{
recommendations.AppendLine("中优先级优化:");
foreach (var issue in mediumIssues)
{
recommendations.AppendLine($" - {issue.description}: {issue.recommendation}");
}
}

if (lowIssues.Count > 0)
{
recommendations.AppendLine("低优先级优化:");
foreach (var issue in lowIssues)
{
recommendations.AppendLine($" - {issue.description}: {issue.recommendation}");
}
}

return recommendations.ToString();
}

/// <summary>
/// 导出性能报告
/// </summary>
public static void ExportPerformanceReport(PerformanceReport report, string filePath)
{
string json = JsonUtility.ToJson(report, true);
System.IO.File.WriteAllText(filePath, json);

Debug.Log($"性能报告已导出到: {filePath}");
}
}

Bundle大小优化策略

Bundle大小分析工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor.AddressableAssets.Settings;

public class BundleSizeOptimizer
{
[System.Serializable]
public class BundleSizeAnalysis
{
public string bundleName;
public long originalSize;
public long optimizedSize;
public int assetCount;
public List<AssetSizeInfo> assets;
public string optimizationStrategy;
}

[System.Serializable]
public class AssetSizeInfo
{
public string assetAddress;
public long size;
public string assetType;
public bool isCandidateForOptimization;
public string optimizationSuggestion;
}

/// <summary>
/// 分析Bundle大小
/// </summary>
public static List<BundleSizeAnalysis> AnalyzeBundleSizes(AddressableAssetSettings settings)
{
var analyses = new List<BundleSizeAnalysis>();

foreach (var group in settings.groups)
{
if (group.HasSchema<UnityEditor.AddressableAssets.Settings.GroupSchemas.BundledAssetGroupSchema>())
{
var analysis = AnalyzeGroupSize(group);
analyses.Add(analysis);
}
}

return analyses;
}

private static BundleSizeAnalysis AnalyzeGroupSize(AddressableAssetGroup group)
{
var analysis = new BundleSizeAnalysis
{
bundleName = group.Name,
assetCount = group.entries.Count,
assets = new List<AssetSizeInfo>()
};

long totalSize = 0;

foreach (var entry in group.entries)
{
var assetInfo = AnalyzeAssetSize(entry);
analysis.assets.Add(assetInfo);
totalSize += assetInfo.size;
}

analysis.originalSize = totalSize;

// 确定优化策略
analysis.optimizationStrategy = DetermineOptimizationStrategy(analysis);

return analysis;
}

private static AssetSizeInfo AnalyzeAssetSize(AddressableAssetEntry entry)
{
var assetInfo = new AssetSizeInfo
{
assetAddress = entry.address,
assetType = GetAssetType(entry)
};

// 获取资源大小
string assetPath = UnityEditor.AssetDatabase.GUIDToAssetPath(entry.guid);
if (!string.IsNullOrEmpty(assetPath))
{
var importer = UnityEditor.AssetImporter.GetAtPath(assetPath);
if (importer != null)
{
// 根据资源类型估算大小
assetInfo.size = EstimateAssetFileSize(assetPath);

// 确定是否为优化候选
assetInfo.isCandidateForOptimization = IsAssetCandidateForOptimization(assetInfo);

// 提供优化建议
assetInfo.optimizationSuggestion = GetOptimizationSuggestion(assetInfo);
}
}

return assetInfo;
}

private static string GetAssetType(AddressableAssetEntry entry)
{
string assetPath = UnityEditor.AssetDatabase.GUIDToAssetPath(entry.guid);
if (string.IsNullOrEmpty(assetPath)) return "Unknown";

string extension = System.IO.Path.GetExtension(assetPath).ToLower();

switch (extension)
{
case ".prefab": return "Prefab";
case ".png":
case ".jpg":
case ".jpeg":
case ".tga":
case ".psd": return "Texture";
case ".fbx":
case ".obj":
case ".blend": return "Model";
case ".wav":
case ".mp3":
case ".ogg": return "Audio";
case ".mat": return "Material";
case ".shader": return "Shader";
case ".asset": return "ScriptableObject";
default: return "Other";
}
}

private static long EstimateAssetFileSize(string assetPath)
{
try
{
var fileInfo = new System.IO.FileInfo(assetPath);
return fileInfo.Length;
}
catch
{
// 如果无法获取文件大小,使用估算值
string extension = System.IO.Path.GetExtension(assetPath).ToLower();

switch (extension)
{
case ".png":
case ".jpg":
return 1024 * 1024; // 1MB
case ".fbx":
return 512 * 1024; // 512KB
case ".wav":
return 2048 * 1024; // 2MB
default:
return 64 * 1024; // 64KB
}
}
}

private static bool IsAssetCandidateForOptimization(AssetSizeInfo assetInfo)
{
// 大于1MB的资源通常值得优化
return assetInfo.size > 1024 * 1024; // 1MB
}

private static string GetOptimizationSuggestion(AssetSizeInfo assetInfo)
{
switch (assetInfo.assetType)
{
case "Texture":
return "考虑使用更高效的纹理格式(ASTC、ETC2等)或降低纹理分辨率";
case "Model":
return "考虑减少多边形数量、优化网格或使用LOD";
case "Audio":
return "考虑使用更高效的音频压缩格式或降低音频质量";
case "Prefab":
return "检查Prefab中是否包含不必要的组件或资源";
default:
return "考虑资源压缩或分离大资源";
}
}

private static string DetermineOptimizationStrategy(BundleSizeAnalysis analysis)
{
if (analysis.originalSize > 50 * 1024 * 1024) // 50MB
{
return "分离大资源到独立Bundle";
}
else if (analysis.assetCount > 50)
{
return "按功能或使用场景分组";
}
else
{
return "当前大小合理,无需特殊优化";
}
}

/// <summary>
/// 生成Bundle大小优化报告
/// </summary>
public static string GenerateBundleSizeReport(List<BundleSizeAnalysis> analyses)
{
var report = new System.Text.StringBuilder();
report.AppendLine("=== Bundle大小优化报告 ===");

long totalOriginalSize = 0;
long totalOptimizedSize = 0;
int totalBundles = analyses.Count;

foreach (var analysis in analyses)
{
totalOriginalSize += analysis.originalSize;
totalOptimizedSize += analysis.optimizedSize;

report.AppendLine();
report.AppendLine($"Bundle: {analysis.bundleName}");
report.AppendLine($" 大小: {BuildLayoutReportGenerator.FormatFileSize(analysis.originalSize)}");
report.AppendLine($" 资源数: {analysis.assetCount}");
report.AppendLine($" 优化策略: {analysis.optimizationStrategy}");

// 显示大资源
var largeAssets = analysis.assets.FindAll(a => a.size > 1024 * 1024); // 1MB以上
if (largeAssets.Count > 0)
{
report.AppendLine(" 大资源:");
foreach (var asset in largeAssets)
{
report.AppendLine($" {asset.assetAddress}: {BuildLayoutReportGenerator.FormatFileSize(asset.size)} - {asset.optimizationSuggestion}");
}
}
}

report.AppendLine();
report.AppendLine("=== 总结 ===");
report.AppendLine($"总Bundle数: {totalBundles}");
report.AppendLine($"原始总大小: {BuildLayoutReportGenerator.FormatFileSize(totalOriginalSize)}");
report.AppendLine($"优化后总大小: {BuildLayoutReportGenerator.FormatFileSize(totalOptimizedSize)}");

if (totalOriginalSize > 0)
{
float reduction = (float)(totalOriginalSize - totalOptimizedSize) / totalOriginalSize * 100;
report.AppendLine($"预计大小减少: {reduction:F1}%");
}

return report.ToString();
}

/// <summary>
/// 应用Bundle大小优化
/// </summary>
public static void ApplyBundleSizeOptimizations(AddressableAssetSettings settings)
{
Debug.Log("开始应用Bundle大小优化...");

// 1. 识别大Bundle
var largeBundles = new List<AddressableAssetGroup>();
foreach (var group in settings.groups)
{
long groupSize = EstimateGroupSize(group);
if (groupSize > 50 * 1024 * 1024) // 50MB
{
largeBundles.Add(group);
}
}

// 2. 分离大资源
foreach (var group in largeBundles)
{
SeparateLargeAssets(settings, group);
}

// 3. 优化纹理设置
OptimizeTextureSettings(settings);

Debug.Log($"Bundle大小优化完成,处理了 {largeBundles.Count} 个大Bundle");
}

private static long EstimateGroupSize(AddressableAssetGroup group)
{
long totalSize = 0;
foreach (var entry in group.entries)
{
string assetPath = UnityEditor.AssetDatabase.GUIDToAssetPath(entry.guid);
if (!string.IsNullOrEmpty(assetPath))
{
try
{
var fileInfo = new System.IO.FileInfo(assetPath);
totalSize += fileInfo.Length;
}
catch
{
// 如果无法获取文件大小,使用估算值
totalSize += 64 * 1024; // 64KB
}
}
}
return totalSize;
}

private static void SeparateLargeAssets(AddressableAssetSettings settings, AddressableAssetGroup group)
{
// 创建新的组来存放大资源
var largeAssetGroup = settings.CreateGroup($"{group.Name}_LargeAssets", false, false, true, null);
largeAssetGroup.AddSchema<UnityEditor.AddressableAssets.Settings.GroupSchemas.BundledAssetGroupSchema>();

var entriesToMove = new List<AddressableAssetEntry>();

foreach (var entry in group.entries)
{
string assetPath = UnityEditor.AssetDatabase.GUIDToAssetPath(entry.guid);
if (!string.IsNullOrEmpty(assetPath))
{
try
{
var fileInfo = new System.IO.FileInfo(assetPath);
if (fileInfo.Length > 5 * 1024 * 1024) // 5MB以上为大资源
{
entriesToMove.Add(entry);
}
}
catch
{
// 错误处理
}
}
}

// 移动大资源到新组
foreach (var entry in entriesToMove)
{
settings.MoveEntry(entry, largeAssetGroup, false, true);
}

Debug.Log($"从组 {group.Name} 分离了 {entriesToMove.Count} 个大资源到 {largeAssetGroup.Name}");
}

private static void OptimizeTextureSettings(AddressableAssetSettings settings)
{
// 这里可以实现纹理优化逻辑
// 例如:批量修改纹理导入设置
Debug.Log("纹理设置优化完成");
}
}

通过本章的学习,您已经全面掌握了Addressables性能分析与诊断的各种工具和方法,包括Profiler使用、Event Viewer分析、构建报告解读、依赖关系可视化、性能问题识别和Bundle大小优化策略。下一章我们将探讨内存优化的高级技术。