Unity引擎Profiling工具使用技术总结
/ 创建持久化的Profiler标记// 使用标记包裹代码块// 进行角色模拟计算// 一些计算...// 嵌套标记示例// 物理相关计算...i < 1000;i++)// 自定义Profiler窗口示例// 自定义标记信息结构recordData;// 启动记录else// 停止记录if (!Repaint();
·
Unity引擎Profiling工具使用技术总结
一、性能分析概述
Unity的性能分析(Profiling)是游戏优化过程中不可或缺的环节,它帮助开发者识别性能瓶颈并提供解决方案。Unity提供了多种性能分析工具,适用于不同的分析场景和平台。
主要性能分析工具
- Unity Profiler - 内置的主要性能分析工具
- Frame Debugger - 分析渲染调用和绘制顺序
- Memory Profiler - 详细的内存使用分析
- Profile Analyzer - 多帧性能对比分析
- Physics Debugger - 物理系统可视化调试
- GPU Profiler - GPU性能分析
二、Unity Profiler基础
开启Profiler
// 在代码中控制Profiler
using UnityEngine.Profiling;
void Start()
{
// 开启Profiler记录
Profiler.enabled = true;
// 设置是否深度采样
Profiler.deepProfiling = true;
// 设置Profiler是否在后台运行
Profiler.enableBinaryLog = true;
Profiler.logFile = "myLog.raw";
}
基本界面组成
- 工具栏 - 控制记录、连接目标等
- 模块视图 - CPU、GPU、内存、音频等不同模块
- 时间线视图 - 按时间轴展示性能数据
- 详情视图 - 显示所选时刻的详细信息
- 层级视图 - 函数调用堆栈和耗时占比
常见模块分析
- CPU Usage - 处理器使用情况
- GPU Usage - 图形处理器使用情况
- Memory - 内存分配与使用
- Audio - 音频系统性能
- Physics - 物理系统计算耗时
- Rendering - 渲染管线性能
- UI - 用户界面性能
- Network - 网络传输性能
三、自定义性能分析
使用Profiler Markers手动标记
using Unity.Profiling;
using UnityEngine;
public class CustomProfilerExample : MonoBehaviour
{
// 创建持久化的Profiler标记
private static readonly ProfilerMarker s_SimulationMarker =
new ProfilerMarker("MyGame.CharacterController.Simulation");
private static readonly ProfilerMarker s_PhysicsMarker =
new ProfilerMarker("MyGame.CharacterController.Physics");
void Update()
{
// 使用标记包裹代码块
s_SimulationMarker.Begin();
// 进行角色模拟计算
SimulateCharacter();
s_SimulationMarker.End();
}
void SimulateCharacter()
{
// 一些计算...
// 嵌套标记示例
s_PhysicsMarker.Begin();
// 物理相关计算...
for (int i = 0; i < 1000; i++)
{
float calculation = Mathf.Sin(i) * Mathf.Cos(i);
}
s_PhysicsMarker.End();
}
}
使用代码计时器
using System.Diagnostics;
using UnityEngine;
using Debug = UnityEngine.Debug;
public class TimerExample : MonoBehaviour
{
private Stopwatch stopwatch = new Stopwatch();
void HeavyMethod()
{
stopwatch.Reset();
stopwatch.Start();
// 执行需要计时的代码
for (int i = 0; i < 100000; i++)
{
float result = Mathf.Sqrt(i) * Mathf.Log10(i + 1);
}
stopwatch.Stop();
Debug.Log($"Method execution time: {stopwatch.ElapsedMilliseconds}ms");
}
}
使用Profiler.BeginSample/EndSample
using UnityEngine;
using UnityEngine.Profiling;
public class ProfilerSampleExample : MonoBehaviour
{
void Update()
{
// 开始一个分析样本
Profiler.BeginSample("Custom Update Logic");
// 执行代码...
for (int i = 0; i < 1000; i++)
{
float calculation = Mathf.Pow(i, 0.5f);
}
// 结束分析样本
Profiler.EndSample();
// 嵌套样本
Profiler.BeginSample("AI Processing");
// AI相关计算...
Profiler.BeginSample("Pathfinding");
// 寻路相关代码...
Profiler.EndSample();
Profiler.EndSample();
}
}
四、远程Profiling技术
设置远程Profiling
// 在Build Settings中:
// 1. 勾选 "Development Build"
// 2. 勾选 "Autoconnect Profiler"
// 3. 设置 "Profiling Functor"
// 在代码中设置
#if ENABLE_PROFILER
using UnityEngine.Profiling;
void Start()
{
// 设置详细的性能数据收集
Profiler.maxUsedMemory = 512 * 1024 * 1024; // 512MB缓冲区
Profiler.enabled = true;
}
#endif
使用ADB连接Android设备
# 列出连接的设备
adb devices
# 转发Profiler端口
adb forward tcp:34999 tcp:34999
# 启用USB调试并确保开发者选项已开启
使用Wi-Fi连接设备
// 在设备上启动的应用程序中
void Start()
{
Debug.Log("Device IP: " + Network.player.ipAddress);
// 记下IP地址,在Unity Profiler中填入"<IP>:34999"进行连接
}
五、Memory Profiler详解
开启内存分析器
// 安装Memory Profiler包
// 通过Package Manager -> Unity Registry -> Memory Profiler
// 启动Memory Profiler
// Window -> Analysis -> Memory Profiler
内存快照与比较
// 在代码中拍摄内存快照
using Unity.MemoryProfiler;
void CaptureMemorySnapshot()
{
string snapshotPath = "MemoryCapture_" + System.DateTime.Now.ToString("yyyyMMdd_HHmmss");
MemoryProfiler.TakeSnapshot(snapshotPath, OnSnapshotComplete);
}
void OnSnapshotComplete(string snapshotPath, bool success)
{
if (success)
{
Debug.Log("Memory snapshot saved to: " + snapshotPath);
}
else
{
Debug.LogError("Failed to take memory snapshot");
}
}
分析内存泄漏
public class MemoryLeakExample : MonoBehaviour
{
private List<Texture2D> leakingTextures = new List<Texture2D>();
// 有问题的方法 - 创建但从不释放纹理
void CreateTexturesWithoutCleanup()
{
for (int i = 0; i < 10; i++)
{
Texture2D texture = new Texture2D(1024, 1024, TextureFormat.RGBA32, false);
leakingTextures.Add(texture);
}
}
// 修复的方法
void CleanupTextures()
{
foreach (Texture2D texture in leakingTextures)
{
Destroy(texture);
}
leakingTextures.Clear();
}
void OnDestroy()
{
// 在此清理资源可避免内存泄漏
CleanupTextures();
}
}
六、Frame Debugger实战
启用Frame Debugger
// 打开Frame Debugger
// Window -> Analysis -> Frame Debugger
// 或在代码中使用
#if UNITY_EDITOR
using UnityEditor;
void TriggerFrameDebugger()
{
FrameDebuggerUtility.EnableFrameDebugger(true);
}
#endif
分析渲染调用
// 创建自定义绘制事件进行标记
using UnityEngine.Rendering;
void OnRenderObject()
{
// 用于在Frame Debugger中标识这个绘制块
CommandBuffer cmd = new CommandBuffer();
cmd.name = "MyCustomRenderingPass";
// 添加一些绘制命令...
// 执行命令
Graphics.ExecuteCommandBuffer(cmd);
}
优化DrawCall
// 常见的DrawCall优化技术
public class DrawCallOptimizer : MonoBehaviour
{
void OptimizeMeshRenderers()
{
// 1. 静态批处理
// 将相关静态对象标记为Static
// 2. 动态批处理 - 确保符合条件:
// - 少于900个顶点 (300 for skinned mesh)
// - 使用相同材质
// - 不使用光照贴图
// 3. GPU实例化
MaterialPropertyBlock props = new MaterialPropertyBlock();
MeshRenderer[] renderers = GetComponentsInChildren<MeshRenderer>();
foreach (MeshRenderer renderer in renderers)
{
props.SetColor("_Color", Random.ColorHSV());
renderer.SetPropertyBlock(props);
}
}
}
七、Profile Analyzer深度分析
捕获与比较帧数据
// 通过Package Manager安装Profile Analyzer
// 使用代码触发帧捕获
#if UNITY_EDITOR
using Unity.ProfileAnalyzer;
void CaptureFrames()
{
ProfileAnalyzer analyzer = ProfileAnalyzer.CreateInstance();
// 捕获100帧数据
analyzer.SetFrameRange(100, 200);
ProfilerFrameDataIterator frameData = analyzer.GetFrameDataIterator();
// 处理帧数据...
}
#endif
分析热点函数
// 在Profile Analyzer中识别热点函数后优化示例
// 优化前
void InefficiencyMethod()
{
for (int i = 0; i < entities.Count; i++)
{
for (int j = 0; j < entities.Count; j++)
{
CalculateDistanceBetween(entities[i], entities[j]);
}
}
}
// 优化后
void ImprovedMethod()
{
// 预先计算距离
if (needsDistanceUpdate)
{
CalculateAllDistances();
needsDistanceUpdate = false;
}
// 直接使用缓存结果
for (int i = 0; i < entities.Count; i++)
{
for (int j = 0; j < entities.Count; j++)
{
float distance = distanceCache[i, j];
// 使用缓存距离...
}
}
}
八、GPU Profiler深入解析
启用GPU Profiling
// 1. 在Profiler窗口中选择GPU模块
// 2. 启用GPU Profiling
// 创建GPU事件标记
using UnityEngine;
using UnityEngine.Rendering;
public class GPUProfilerExample : MonoBehaviour
{
private CommandBuffer cmd;
void Start()
{
cmd = new CommandBuffer();
}
void Update()
{
// 创建命令缓冲区以添加GPU Profiler标记
cmd.Clear();
// 添加开始采样标记
cmd.BeginSample("Custom GPU Effect");
// 添加你的GPU操作...
cmd.SetGlobalColor("_EffectColor", Color.red);
// 结束采样标记
cmd.EndSample("Custom GPU Effect");
// 执行命令
Graphics.ExecuteCommandBuffer(cmd);
}
}
分析着色器性能
// 使用着色器变种收集器
#if UNITY_EDITOR
using UnityEditor;
public static class ShaderVariantCollector
{
[MenuItem("Tools/Collect Shader Variants")]
public static void CollectVariants()
{
ShaderVariantCollection collection = new ShaderVariantCollection();
// 收集项目中所有材质使用的着色器变种
foreach (Material material in FindAllMaterials())
{
Shader shader = material.shader;
ShaderVariantCollection.ShaderVariant variant = new ShaderVariantCollection.ShaderVariant(
shader,
PassType.Normal,
new string[] { material.GetShaderKeywords() }
);
collection.Add(variant);
}
// 保存收集的变种
string path = "Assets/CollectedVariants.shadervariants";
AssetDatabase.CreateAsset(collection, path);
AssetDatabase.SaveAssets();
}
private static Material[] FindAllMaterials()
{
// 查找项目中的所有材质...
return new Material[0]; // 实际实现会返回项目中的所有材质
}
}
#endif
九、Physics Profiler与优化
物理性能监控
using UnityEngine;
using UnityEngine.Profiling;
public class PhysicsProfiler : MonoBehaviour
{
CustomSampler physicsSampler;
void Start()
{
physicsSampler = CustomSampler.Create("PhysicsSimulation");
}
void FixedUpdate()
{
physicsSampler.Begin();
// 执行物理相关计算
SimulateCustomPhysics();
physicsSampler.End();
}
void SimulateCustomPhysics()
{
Rigidbody[] rigidbodies = FindObjectsOfType<Rigidbody>();
// 对每个物体进行一些自定义物理模拟
foreach (Rigidbody rb in rigidbodies)
{
if (rb.IsSleeping()) continue;
// 记录详细信息
Profiler.BeginSample("PhysicsCalculation: " + rb.name);
// 进行计算...
Profiler.EndSample();
}
}
}
物理优化技术
// 物理优化示例
public class PhysicsOptimizer : MonoBehaviour
{
void Start()
{
OptimizePhysics();
}
void OptimizePhysics()
{
// 1. 调整物理时间步长
Time.fixedDeltaTime = 0.02f; // 50Hz, 默认但可根据需求调整
// 2. 使用合适的碰撞检测模式
Rigidbody rb = GetComponent<Rigidbody>();
if (rb)
{
if (rb.velocity.magnitude > 10f)
{
rb.collisionDetectionMode = CollisionDetectionMode.Continuous;
}
else
{
rb.collisionDetectionMode = CollisionDetectionMode.Discrete;
}
}
// 3. 优化碰撞体
// 尽量使用基本碰撞体类型: 球体、胶囊体、盒体
// 4. 设置物理层级掩码减少碰撞检测
Physics.IgnoreLayerCollision(8, 9, true); // 例如:忽略8和9层之间的碰撞
}
void SetupCompoundCollider()
{
// 5. 使用复合碰撞体替代复杂Mesh Collider
GameObject compoundObj = new GameObject("CompoundCollider");
compoundObj.transform.SetParent(transform);
// 添加基本碰撞体模拟复杂形状
SphereCollider sphere = compoundObj.AddComponent<SphereCollider>();
sphere.center = new Vector3(0, 1, 0);
sphere.radius = 0.5f;
BoxCollider box = compoundObj.AddComponent<BoxCollider>();
box.center = new Vector3(0, 0, 0);
box.size = new Vector3(1, 1, 1);
}
}
十、UI性能分析与优化
UI Profiler标记
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Profiling;
public class UIPerformanceMonitor : MonoBehaviour
{
void Update()
{
// 标记UI更新
Profiler.BeginSample("UI Updates");
UpdateComplexUI();
Profiler.EndSample();
}
void UpdateComplexUI()
{
// UI更新细分
Profiler.BeginSample("Text Updates");
UpdateTextElements();
Profiler.EndSample();
Profiler.BeginSample("Layout Recalculation");
RecalculateLayouts();
Profiler.EndSample();
}
void UpdateTextElements()
{
// 更新文本元素...
}
void RecalculateLayouts()
{
// 重新计算布局...
LayoutRebuilder.ForceRebuildLayoutImmediate(GetComponent<RectTransform>());
}
}
UI批处理分析
// UI批处理分析工具
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
public class UIBatchingAnalyzer : MonoBehaviour
{
// 在编辑器模式使用
#if UNITY_EDITOR
[ContextMenu("Analyze UI Batching")]
public void AnalyzeUIBatching()
{
Canvas[] canvases = FindObjectsOfType<Canvas>();
Dictionary<Material, List<Graphic>> materialToGraphic = new Dictionary<Material, List<Graphic>>();
foreach (Canvas canvas in canvases)
{
Graphic[] graphics = canvas.GetComponentsInChildren<Graphic>();
foreach (Graphic graphic in graphics)
{
Material material = graphic.materialForRendering;
if (!materialToGraphic.ContainsKey(material))
{
materialToGraphic[material] = new List<Graphic>();
}
materialToGraphic[material].Add(graphic);
}
}
// 输出分析结果
Debug.Log($"Found {canvases.Length} canvases and {materialToGraphic.Count} unique materials");
foreach (var kvp in materialToGraphic)
{
Debug.Log($"Material: {kvp.Key.name} - Used by {kvp.Value.Count} graphics");
if (kvp.Value.Count == 1)
{
Debug.LogWarning($"Material {kvp.Key.name} is only used by one graphic. Consider combining or sharing materials.");
}
}
}
#endif
}
十一、网络Profiling
网络性能分析
using UnityEngine;
using UnityEngine.Networking;
using System.Collections;
using UnityEngine.Profiling;
public class NetworkProfiler : MonoBehaviour
{
CustomSampler downloadSampler;
CustomSampler uploadSampler;
void Start()
{
downloadSampler = CustomSampler.Create("Network Download");
uploadSampler = CustomSampler.Create("Network Upload");
}
public IEnumerator DownloadWithProfiling(string url)
{
downloadSampler.Begin();
UnityWebRequest request = UnityWebRequest.Get(url);
yield return request.SendWebRequest();
if (request.result == UnityWebRequest.Result.Success)
{
long downloadedBytes = request.downloadedBytes;
float downloadTimeSec = request.downloadProgress > 0
? downloadedBytes / (request.downloadedBytes * request.downloadProgress)
: 0;
Debug.Log($"Downloaded {downloadedBytes} bytes in {downloadTimeSec}s " +
$"({downloadedBytes/downloadTimeSec/1024} KB/s)");
}
downloadSampler.End();
}
public IEnumerator UploadWithProfiling(string url, byte[] data)
{
uploadSampler.Begin();
UnityWebRequest request = new UnityWebRequest(url, "POST");
request.uploadHandler = new UploadHandlerRaw(data);
request.downloadHandler = new DownloadHandlerBuffer();
float startTime = Time.realtimeSinceStartup;
yield return request.SendWebRequest();
float uploadTime = Time.realtimeSinceStartup - startTime;
if (request.result == UnityWebRequest.Result.Success)
{
Debug.Log($"Uploaded {data.Length} bytes in {uploadTime}s " +
$"({data.Length/uploadTime/1024} KB/s)");
}
uploadSampler.End();
}
}
十二、自动化性能测试
性能测试框架
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using Unity.Profiling;
using System.IO;
public class AutomatedPerformanceTest : MonoBehaviour
{
[System.Serializable]
public class TestCase
{
public string name;
public GameObject scenePrefab;
public float duration = 10f;
public bool captureMemory = true;
public bool captureCPU = true;
public bool captureGPU = true;
}
public List<TestCase> testCases = new List<TestCase>();
public bool saveResultsToFile = true;
public string resultsDirectory = "PerformanceResults";
// 性能metrics
private ProfilerRecorder cpuRecorder;
private ProfilerRecorder gpuRecorder;
private ProfilerRecorder memoryRecorder;
private class TestResult
{
public string testName;
public float avgCpuTime;
public float maxCpuTime;
public float avgGpuTime;
public float maxGpuTime;
public float avgMemory;
public float maxMemory;
public List<float> frameTimeSamples = new List<float>();
}
private List<TestResult> results = new List<TestResult>();
private void Start()
{
StartCoroutine(RunTestSuite());
}
IEnumerator RunTestSuite()
{
foreach (TestCase test in testCases)
{
yield return StartCoroutine(RunTestCase(test));
}
if (saveResultsToFile)
{
SaveResultsToFile();
}
Debug.Log("Performance tests completed.");
}
IEnumerator RunTestCase(TestCase test)
{
Debug.Log($"Starting test: {test.name}");
// 清理之前的场景和垃圾收集
Resources.UnloadUnusedAssets();
System.GC.Collect();
yield return new WaitForSeconds(1f);
// 初始化测试场景
GameObject testScene = null;
if (test.scenePrefab != null)
{
testScene = Instantiate(test.scenePrefab);
}
// 设置Profiler记录器
SetupRecorders(test);
// 创建结果对象
TestResult result = new TestResult
{
testName = test.name,
avgCpuTime = 0,
maxCpuTime = 0,
avgGpuTime = 0,
maxGpuTime = 0,
avgMemory = 0,
maxMemory = 0
};
// 记录数据点数量
int sampleCount = 0;
// 设置测试开始时间
float startTime = Time.realtimeSinceStartup;
float lastFrameTime = startTime;
// 测试循环
while (Time.realtimeSinceStartup - startTime < test.duration)
{
// 记录帧时间
float currentTime = Time.realtimeSinceStartup;
float frameTime = currentTime - lastFrameTime;
lastFrameTime = currentTime;
result.frameTimeSamples.Add(frameTime * 1000f); // 转换为毫秒
// 收集CPU数据
if (test.captureCPU && cpuRecorder.Valid)
{
float cpuTime = cpuRecorder.LastValue / 1000000f; // 纳秒转换为毫秒
result.avgCpuTime += cpuTime;
result.maxCpuTime = Mathf.Max(result.maxCpuTime, cpuTime);
}
// 收集GPU数据
if (test.captureGPU && gpuRecorder.Valid)
{
float gpuTime = gpuRecorder.LastValue / 1000000f; // 纳秒转换为毫秒
result.avgGpuTime += gpuTime;
result.maxGpuTime = Mathf.Max(result.maxGpuTime, gpuTime);
}
// 收集内存数据
if (test.captureMemory && memoryRecorder.Valid)
{
float memoryUsage = memoryRecorder.LastValue / (1024f * 1024f); // 转换为MB
result.avgMemory += memoryUsage;
result.maxMemory = Mathf.Max(result.maxMemory, memoryUsage);
}
sampleCount++;
yield return null;
}
// 计算平均值
if (sampleCount > 0)
{
result.avgCpuTime /= sampleCount;
result.avgGpuTime /= sampleCount;
result.avgMemory /= sampleCount;
}
// 输出测试结果
Debug.Log($"Test: {test.name} completed");
Debug.Log($"Avg CPU: {result.avgCpuTime:F2}ms, Max CPU: {result.maxCpuTime:F2}ms");
Debug.Log($"Avg GPU: {result.avgGpuTime:F2}ms, Max GPU: {result.maxGpuTime:F2}ms");
Debug.Log($"Avg Memory: {result.avgMemory:F2}MB, Max Memory: {result.maxMemory:F2}MB");
// 停止记录器
DisposeRecorders();
// 保存结果
results.Add(result);
// 清理测试场景
if (testScene != null)
{
Destroy(testScene);
}
yield return null;
}
private void SetupRecorders(TestCase test)
{
if (test.captureCPU)
{
cpuRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Internal, "MainThreadTime");
}
if (test.captureGPU)
{
gpuRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Render, "GPU");
}
if (test.captureMemory)
{
memoryRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Memory, "Total Used Memory");
}
}
private void DisposeRecorders()
{
if (cpuRecorder.Valid) cpuRecorder.Dispose();
if (gpuRecorder.Valid) gpuRecorder.Dispose();
if (memoryRecorder.Valid) memoryRecorder.Dispose();
}
private void SaveResultsToFile()
{
string directory = Path.Combine(Application.persistentDataPath, resultsDirectory);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
string timestamp = System.DateTime.Now.ToString("yyyyMMdd_HHmmss");
string filename = Path.Combine(directory, $"PerformanceResults_{timestamp}.csv");
using (StreamWriter writer = new StreamWriter(filename))
{
// 写入标题行
writer.WriteLine("Test,AvgCPU(ms),MaxCPU(ms),AvgGPU(ms),MaxGPU(ms),AvgMemory(MB),MaxMemory(MB)");
// 写入测试结果
foreach (TestResult result in results)
{
writer.WriteLine($"{result.testName},{result.avgCpuTime:F2},{result.maxCpuTime:F2}," +
$"{result.avgGpuTime:F2},{result.maxGpuTime:F2}," +
$"{result.avgMemory:F2},{result.maxMemory:F2}");
}
// 写入帧时间详情
writer.WriteLine("\nFrame times (ms):");
// 写入每个测试的帧时间
foreach (TestResult result in results)
{
writer.Write($"{result.testName}");
foreach (float frametime in result.frameTimeSamples)
{
writer.Write($",{frametime:F2}");
}
writer.WriteLine();
}
}
Debug.Log($"Results saved to: {filename}");
}
}
十三、高级Profiling技术
自定义Profiler窗口
#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.Linq;
// 自定义Profiler窗口示例
public class CustomProfilerWindow : EditorWindow
{
[MenuItem("Window/Analysis/Custom Profiler")]
public static void ShowWindow()
{
GetWindow<CustomProfilerWindow>("Custom Profiler");
}
private List<ProfilerMarkerInfo> customMarkers = new List<ProfilerMarkerInfo>();
private Vector2 scrollPosition;
private bool recordData = false;
private float recordTimer = 0f;
private float recordInterval = 0.5f;
// 自定义标记信息结构
private class ProfilerMarkerInfo
{
public string name;
public float avgTime;
public float maxTime;
public int callCount;
public List<float> timeHistory = new List<float>();
public ProfilerMarkerInfo(string markerName)
{
name = markerName;
avgTime = 0f;
maxTime = 0f;
callCount = 0;
}
}
void OnGUI()
{
GUILayout.Label("Custom Performance Markers", EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button(recordData ? "Stop Recording" : "Start Recording"))
{
recordData = !recordData;
if (recordData)
{
// 启动记录
EditorApplication.update += OnEditorUpdate;
ClearData();
}
else
{
// 停止记录
EditorApplication.update -= OnEditorUpdate;
}
}
if (GUILayout.Button("Clear Data"))
{
ClearData();
}
EditorGUILayout.EndHorizontal();
recordInterval = EditorGUILayout.Slider("Record Interval (s)", recordInterval, 0.1f, 2.0f);
EditorGUILayout.Space();
DrawMarkersTable();
}
void OnEditorUpdate()
{
if (!recordData) return;
recordTimer += Time.deltaTime;
if (recordTimer >= recordInterval)
{
UpdateMarkerData();
recordTimer = 0f;
Repaint();
}
}
void UpdateMarkerData()
{
// 获取自定义Marker数据
// 注意:此方法需要你已经使用了自定义标记
// 这里只是模拟数据,实际应用中需要连接到Unity Profiler API
string[] markerNames = new string[] {
"MyGame.Update",
"MyGame.Physics",
"MyGame.Rendering",
"MyGame.AI"
};
foreach (string markerName in markerNames)
{
// 查找或创建标记信息
ProfilerMarkerInfo marker = customMarkers.FirstOrDefault(m => m.name == markerName);
if (marker == null)
{
marker = new ProfilerMarkerInfo(markerName);
customMarkers.Add(marker);
}
// 模拟获取标记数据
float time = Random.Range(0.1f, 10.0f);
int calls = Random.Range(1, 100);
// 更新数据
marker.timeHistory.Add(time);
if (marker.timeHistory.Count > 100)
marker.timeHistory.RemoveAt(0);
marker.maxTime = Mathf.Max(marker.maxTime, time);
marker.callCount = calls;
// 计算平均时间
marker.avgTime = marker.timeHistory.Count > 0 ?
marker.timeHistory.Average() : 0f;
}
}
void ClearData()
{
customMarkers.Clear();
recordTimer = 0f;
}
void DrawMarkersTable()
{
EditorGUILayout.BeginVertical(GUI.skin.box);
// 表头
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
GUILayout.Label("Marker Name", EditorStyles.toolbarButton, GUILayout.Width(200));
GUILayout.Label("Avg Time (ms)", EditorStyles.toolbarButton, GUILayout.Width(100));
GUILayout.Label("Max Time (ms)", EditorStyles.toolbarButton, GUILayout.Width(100));
GUILayout.Label("Call Count", EditorStyles.toolbarButton, GUILayout.Width(80));
GUILayout.Label("Graph", EditorStyles.toolbarButton);
EditorGUILayout.EndHorizontal();
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
// 表行
foreach (ProfilerMarkerInfo marker in customMarkers.OrderByDescending(m => m.avgTime))
{
EditorGUILayout.BeginHorizontal();
GUILayout.Label(marker.name, GUILayout.Width(200));
GUILayout.Label(marker.avgTime.ToString("F2"), GUILayout.Width(100));
GUILayout.Label(marker.maxTime.ToString("F2"), GUILayout.Width(100));
GUILayout.Label(marker.callCount.ToString(), GUILayout.Width(80));
// 简单的性能图表
Rect graphRect = GUILayoutUtility.GetRect(100, 20, GUILayout.ExpandWidth(true));
DrawPerformanceGraph(graphRect, marker);
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.EndScrollView();
EditorGUILayout.EndVertical();
}
void DrawPerformanceGraph(Rect rect, ProfilerMarkerInfo marker)
{
if (marker.timeHistory.Count == 0) return;
EditorGUI.DrawRect(rect, new Color(0.2f, 0.2f, 0.2f));
float maxValue = marker.timeHistory.Max();
if (maxValue <= 0) return;
// 绘制背景网格
DrawGraphGrid(rect);
// 绘制图形
int count = marker.timeHistory.Count;
float barWidth = rect.width / count;
for (int i = 0; i < count; i++)
{
float value = marker.timeHistory[i];
float normalizedValue = value / maxValue;
float barHeight = rect.height * normalizedValue;
// 根据性能状况选择颜色
Color barColor = GetPerformanceColor(value);
Rect barRect = new Rect(
rect.x + i * barWidth,
rect.y + rect.height - barHeight,
barWidth,
barHeight
);
EditorGUI.DrawRect(barRect, barColor);
}
// 显示阈值线
DrawThresholdLine(rect, 16.67f, maxValue, Color.yellow); // 60 FPS
DrawThresholdLine(rect, 33.33f, maxValue, Color.red); // 30 FPS
}
void DrawGraphGrid(Rect rect)
{
// 绘制水平线
for (int i = 1; i < 4; i++)
{
float y = rect.y + rect.height * (i / 4f);
EditorGUI.DrawRect(new Rect(rect.x, y, rect.width, 1), new Color(0.3f, 0.3f, 0.3f, 0.5f));
}
// 绘制垂直线
for (int i = 1; i < 4; i++)
{
float x = rect.x + rect.width * (i / 4f);
EditorGUI.DrawRect(new Rect(x, rect.y, 1, rect.height), new Color(0.3f, 0.3f, 0.3f, 0.5f));
}
}
void DrawThresholdLine(Rect rect, float thresholdMs, float maxValue, Color color)
{
if (thresholdMs > maxValue) return;
float normalizedThreshold = thresholdMs / maxValue;
float y = rect.y + rect.height * (1f - normalizedThreshold);
EditorGUI.DrawRect(new Rect(rect.x, y, rect.width, 1), color);
}
Color GetPerformanceColor(float timeMs)
{
if (timeMs < 8.33f) return Color.green; // 120+ FPS: 绿色
if (timeMs < 16.67f) return Color.cyan; // 60-120 FPS: 青色
if (timeMs < 33.33f) return Color.yellow; // 30-60 FPS: 黄色
return Color.red; // <30 FPS: 红色
}
}
#endif
集成CI/CD性能测试
using UnityEngine;
using System.IO;
using System.Collections.Generic;
// CI/CD性能测试辅助类
public static class CIPerfTestHelper
{
// 性能测试预设路径
private const string TEST_PRESET_PATH = "Assets/CI/PerformanceTests";
// 性能阈值
[System.Serializable]
public class PerformanceThresholds
{
public float maxAverageCpuTime = 16.0f; // 60 FPS
public float maxPeakCpuTime = 33.0f; // 30 FPS
public float maxMemoryUsage = 1024.0f; // 1 GB
}
// 运行性能测试,返回通过/失败结果
public static bool RunPerformanceTests(string outputPath = null, PerformanceThresholds thresholds = null)
{
if (thresholds == null)
{
thresholds = new PerformanceThresholds();
}
if (string.IsNullOrEmpty(outputPath))
{
outputPath = Path.Combine(Application.persistentDataPath, "CIPerformanceResults.json");
}
// 查找和运行所有测试
List<TestResult> results = new List<TestResult>();
// 查找测试预设
string[] testAssets = GetAllPerformanceTestAssets();
foreach (string testPath in testAssets)
{
// 加载测试预设
GameObject testPrefab = Resources.Load<GameObject>(testPath);
if (testPrefab == null) continue;
// 运行测试
TestResult result = RunSingleTest(testPrefab, thresholds);
results.Add(result);
// 清理
Resources.UnloadAsset(testPrefab);
}
// 保存结果
SaveTestResults(results, outputPath);
// 检查是否所有测试都通过
bool allPassed = results.TrueForAll(r => r.passed);
// 输出摘要
OutputTestSummary(results);
return allPassed;
}
private static string[] GetAllPerformanceTestAssets()
{
// 此处需要根据实际项目中的资源管理方式调整
// 示例中假设测试预设位于Resources文件夹下
return new string[] {
"PerformanceTests/CPUTest",
"PerformanceTests/GPUTest",
"PerformanceTests/MemoryTest"
};
}
[System.Serializable]
private class TestResult
{
public string testName;
public bool passed;
public float averageCpuTime;
public float peakCpuTime;
public float memoryUsage;
public string[] failures;
}
private static TestResult RunSingleTest(GameObject testPrefab, PerformanceThresholds thresholds)
{
TestResult result = new TestResult
{
testName = testPrefab.name,
passed = true
};
List<string> failures = new List<string>();
// 实例化测试场景
GameObject testInstance = Object.Instantiate(testPrefab);
// 运行测试逻辑 (简化示例)
// 在实际项目中,这里应该有更复杂的逻辑来收集性能数据
result.averageCpuTime = Random.Range(5.0f, 25.0f);
result.peakCpuTime = result.averageCpuTime * Random.Range(1.2f, 2.0f);
result.memoryUsage = Random.Range(512.0f, 1536.0f);
// 验证结果
if (result.averageCpuTime > thresholds.maxAverageCpuTime)
{
failures.Add($"Average CPU time ({result.averageCpuTime}ms) exceeds threshold ({thresholds.maxAverageCpuTime}ms)");
result.passed = false;
}
if (result.peakCpuTime > thresholds.maxPeakCpuTime)
{
failures.Add($"Peak CPU time ({result.peakCpuTime}ms) exceeds threshold ({thresholds.maxPeakCpuTime}ms)");
result.passed = false;
}
if (result.memoryUsage > thresholds.maxMemoryUsage)
{
failures.Add($"Memory usage ({result.memoryUsage}MB) exceeds threshold ({thresholds.maxMemoryUsage}MB)");
result.passed = false;
}
result.failures = failures.ToArray();
// 清理
Object.Destroy(testInstance);
return result;
}
private static void SaveTestResults(List<TestResult> results, string outputPath)
{
string json = JsonUtility.ToJson(new { results = results.ToArray() }, true);
File.WriteAllText(outputPath, json);
Debug.Log($"Performance test results saved to: {outputPath}");
}
private static void OutputTestSummary(List<TestResult> results)
{
int passCount = results.FindAll(r => r.passed).Count;
int failCount = results.Count - passCount;
Debug.Log($"Performance Tests Summary: {passCount} passed, {failCount} failed");
foreach (TestResult result in results)
{
if (result.passed)
{
Debug.Log($"✓ {result.testName} - CPU: {result.averageCpuTime:F2}ms, Peak: {result.peakCpuTime:F2}ms, Memory: {result.memoryUsage:F1}MB");
}
else
{
Debug.LogError($"✗ {result.testName} - Failed");
foreach (string failure in result.failures)
{
Debug.LogError($" - {failure}");
}
}
}
}
}
十四、Profiling实践与案例分析
内存泄漏检测案例
using UnityEngine;
using System.Collections.Generic;
using System.Runtime.InteropServices;
public class MemoryLeakDetector : MonoBehaviour
{
// 事件订阅引起的内存泄漏示例
public class EventManager
{
public delegate void TestEventHandler();
public event TestEventHandler OnTestEvent;
public void RaiseEvent()
{
OnTestEvent?.Invoke();
}
}
public class EventSubscriber
{
private EventManager eventManager;
public EventSubscriber(EventManager manager)
{
eventManager = manager;
// 添加事件监听
eventManager.OnTestEvent += OnEventRaised;
}
private void OnEventRaised()
{
Debug.Log("Event received");
}
// 问题:没有提供取消订阅的方法,导致内存泄漏
// 正确做法应该是实现一个Cleanup方法:
public void Cleanup()
{
eventManager.OnTestEvent -= OnEventRaised;
}
}
// 未释放的GDI资源示例
public class NativeResourceExample
{
private IntPtr nativeResource;
public NativeResourceExample()
{
// 分配一个原生资源 (模拟)
nativeResource = Marshal.AllocHGlobal(1024 * 1024);
}
// 问题:没有正确实现IDisposable或析构函数
// 正确做法应该是:
~NativeResourceExample()
{
if (nativeResource != IntPtr.Zero)
{
Marshal.FreeHGlobal(nativeResource);
nativeResource = IntPtr.Zero;
}
}
public void Dispose()
{
if (nativeResource != IntPtr.Zero)
{
Marshal.FreeHGlobal(nativeResource);
nativeResource = IntPtr.Zero;
}
System.GC.SuppressFinalize(this);
}
}
// 循环引用示例
public class NodeA
{
public NodeB Reference { get; set; }
}
public class NodeB
{
public NodeA Reference { get; set; }
}
// 创建循环引用 (无法被GC回收)
void CreateCircularReference()
{
NodeA nodeA = new NodeA();
NodeB nodeB = new NodeB();
nodeA.Reference = nodeB;
nodeB.Reference = nodeA;
// 两个对象都无法被垃圾回收,因为它们互相引用
}
// 查找静态集合泄漏示例
private static List<object> staticCollection = new List<object>();
void AddToStaticCollection()
{
for (int i = 0; i < 1000; i++)
{
// 创建1MB的数据并添加到静态集合
byte[] data = new byte[1024 * 1024];
staticCollection.Add(data);
}
// 静态集合不会被清理,导致内存持续增长
}
// 检测泄漏的方法示例
public void DetectLeaks()
{
// 1. 触发垃圾回收
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
System.GC.Collect();
// 2. 记录初始内存使用量
long startMemory = System.GC.GetTotalMemory(true);
// 3. 执行潜在的泄漏操作
for (int i = 0; i < 100; i++)
{
EventManager manager = new EventManager();
EventSubscriber subscriber = new EventSubscriber(manager);
// 如果不调用subscriber.Cleanup(),会导致内存泄漏
}
// 4. 再次触发垃圾回收
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
System.GC.Collect();
// 5. 检查内存增长
long endMemory = System.GC.GetTotalMemory(true);
long memoryLeaked = endMemory - startMemory;
Debug.Log($"Memory potentially leaked: {memoryLeaked / 1024} KB");
if (memoryLeaked > 1024 * 100) // 超过100KB
{
Debug.LogWarning("Potential memory leak detected!");
}
}
}
渲染管线优化案例
using UnityEngine;
using UnityEngine.Rendering;
public class RenderingPipelineOptimizer : MonoBehaviour
{
[Header("Optimization Settings")]
[SerializeField] private bool optimizeBatching = true;
[SerializeField] private bool optimizeCulling = true;
[SerializeField] private bool optimizeShaders = true;
[SerializeField] private bool optimizeTextures = true;
[Header("Profiling")]
[SerializeField] private bool enableProfiling = true;
private CommandBuffer profilingCommandBuffer;
private void Start()
{
if (enableProfiling)
{
SetupProfiling();
}
// 应用优化
ApplyOptimizations();
}
private void SetupProfiling()
{
profilingCommandBuffer = new CommandBuffer();
profilingCommandBuffer.name = "Rendering Pipeline Profiling";
// 向渲染管线添加性能标记
Camera.main.AddCommandBuffer(CameraEvent.BeforeForwardOpaque, profilingCommandBuffer);
// 设置渲染标记
profilingCommandBuffer.BeginSample("CameraRender");
// 在特定渲染阶段添加细分标记
profilingCommandBuffer.BeginSample("OpaqueGeometry");
profilingCommandBuffer.EndSample("OpaqueGeometry");
profilingCommandBuffer.BeginSample("Shadows");
profilingCommandBuffer.EndSample("Shadows");
profilingCommandBuffer.BeginSample("Transparency");
profilingCommandBuffer.EndSample("Transparency");
profilingCommandBuffer.BeginSample("PostProcessing");
profilingCommandBuffer.EndSample("PostProcessing");
profilingCommandBuffer.EndSample("CameraRender");
}
private void ApplyOptimizations()
{
if (optimizeBatching)
{
OptimizeBatching();
}
if (optimizeCulling)
{
OptimizeCulling();
}
if (optimizeShaders)
{
OptimizeShaders();
}
if (optimizeTextures)
{
OptimizeTextures();
}
}
private void OptimizeBatching()
{
// 1. 分析当前场景中的批处理情况
MeshRenderer[] renderers = FindObjectsOfType<MeshRenderer>();
// 按材质分组
Dictionary<Material, List<MeshRenderer>> materialGroups = new Dictionary<Material, List<MeshRenderer>>();
foreach (MeshRenderer renderer in renderers)
{
Material[] materials = renderer.sharedMaterials;
foreach (Material material in materials)
{
if (material == null) continue;
if (!materialGroups.ContainsKey(material))
{
materialGroups[material] = new List<MeshRenderer>();
}
materialGroups[material].Add(renderer);
}
}
// 2. 输出批处理分析报告
Debug.Log($"Found {materialGroups.Count} different materials in the scene");
foreach (var group in materialGroups)
{
Debug.Log($"Material '{group.Key.name}' is used by {group.Value.Count} renderers");
// 检查材质是否可以批处理
if (group.Value.Count > 1 && !IsBatchable(group.Key))
{
Debug.LogWarning($"Material '{group.Key.name}' has properties that prevent batching!");
}
}
// 3. 启用GPU实例化 (对于共享相同材质但无法静态批处理的对象)
foreach (var group in materialGroups)
{
if (group.Value.Count >= 5 && group.Key.enableInstancing == false)
{
Debug.Log($"Enabling GPU instancing for material: {group.Key.name}");
group.Key.enableInstancing = true;
}
}
}
private bool IsBatchable(Material material)
{
// 检查材质属性是否允许批处理
if (material.HasProperty("_MainTex") && material.mainTexture != null && material.mainTexture.wrapMode == TextureWrapMode.Repeat)
{
return false; // 重复纹理模式可能阻止批处理
}
// 检查其他批处理障碍...
return true;
}
private void OptimizeCulling()
{
// 1. 优化摄像机剔除设置
Camera mainCamera = Camera.main;
if (mainCamera != null)
{
// 调整摄像机远近裁剪面
float sceneSize = CalculateSceneSize();
mainCamera.farClipPlane = Mathf.Min(1000f, sceneSize * 1.5f);
// 根据场景复杂度设置剔除遮罩
// mainCamera.cullingMask = ...
}
// 2. 为大型静态对象添加遮挡剔除器
MeshRenderer[] largeRenderers = FindObjectsOfType<MeshRenderer>();
foreach (MeshRenderer renderer in largeRenderers)
{
if (IsLargeStaticObject(renderer) && !renderer.gameObject.GetComponent<OcclusionArea>())
{
Debug.Log($"Adding OcclusionArea to large static object: {renderer.gameObject.name}");
renderer.gameObject.AddComponent<OcclusionArea>();
}
}
}
private float CalculateSceneSize()
{
Renderer[] allRenderers = FindObjectsOfType<Renderer>();
if (allRenderers.Length == 0) return 100f;
Bounds sceneBounds = new Bounds(allRenderers[0].bounds.center, Vector3.zero);
foreach (Renderer renderer in allRenderers)
{
sceneBounds.Encapsulate(renderer.bounds);
}
// 返回场景的最大尺寸
return Mathf.Max(sceneBounds.size.x, sceneBounds.size.y, sceneBounds.size.z);
}
private bool IsLargeStaticObject(MeshRenderer renderer)
{
// 检查对象是否足够大且是静态的
bool isStatic = (renderer.gameObject.isStatic);
float volume = renderer.bounds.size.x * renderer.bounds.size.y * renderer.bounds.size.z;
return isStatic && volume > 100f; // 体积阈值可以根据场景调整
}
private void OptimizeShaders()
{
// 1. 查找并分析场景中使用的着色器
Renderer[] renderers = FindObjectsOfType<Renderer>();
Dictionary<Shader, int> shaderUsage = new Dictionary<Shader, int>();
foreach (Renderer renderer in renderers)
{
foreach (Material material in renderer.sharedMaterials)
{
if (material != null && material.shader != null)
{
Shader shader = material.shader;
if (!shaderUsage.ContainsKey(shader))
{
shaderUsage[shader] = 0;
}
shaderUsage[shader]++;
}
}
}
// 2. 输出着色器分析报告
Debug.Log($"Found {shaderUsage.Count} different shaders in the scene");
foreach (var entry in shaderUsage)
{
Debug.Log($"Shader '{entry.Key.name}' is used {entry.Value} times");
// 检查复杂着色器
if (IsComplexShader(entry.Key) && entry.Value > 10)
{
Debug.LogWarning($"Complex shader '{entry.Key.name}' is used frequently!");
}
}
}
private bool IsComplexShader(Shader shader)
{
// 检查复杂度的简单启发式方法 (基于名称)
string name = shader.name.ToLower();
return name.Contains("pbr") || name.Contains("specular") ||
name.Contains("tessellation") || name.Contains("parallax");
}
private void OptimizeTextures()
{
// 1. 分析场景中的纹理
Renderer[] renderers = FindObjectsOfType<Renderer>();
HashSet<Texture> sceneTextures = new HashSet<Texture>();
foreach (Renderer renderer in renderers)
{
foreach (Material material in renderer.sharedMaterials)
{
if (material != null)
{
// 收集材质中的所有纹理
int[] texturePropertyIds = material.GetTexturePropertyNameIDs();
foreach (int propertyId in texturePropertyIds)
{
Texture texture = material.GetTexture(propertyId);
if (texture != null)
{
sceneTextures.Add(texture);
}
}
}
}
}
// 2. 输出纹理分析报告
Debug.Log($"Found {sceneTextures.Count} textures in the scene");
long totalMemory = 0;
foreach (Texture texture in sceneTextures)
{
// 估算纹理内存用量
long estimatedMemory = EstimateTextureMemory(texture);
totalMemory += estimatedMemory;
Debug.Log($"Texture '{texture.name}': {texture.width}x{texture.height}, " +
$"Format: {GetTextureFormat(texture)}, " +
$"~{estimatedMemory / (1024 * 1024)}MB");
// 检查大型纹理
if (texture.width > 2048 || texture.height > 2048)
{
Debug.LogWarning($"Large texture detected: '{texture.name}' ({texture.width}x{texture.height})");
}
}
Debug.Log($"Total estimated texture memory: {totalMemory / (1024 * 1024)}MB");
}
private long EstimateTextureMemory(Texture texture)
{
// 简单的纹理内存估算
int width = texture.width;
int height = texture.height;
int bpp = GetTextureBytesPerPixel(texture);
// 计算基本大小
long baseSize = (long)width * height * bpp;
// 考虑mipmaps (如果有)
Texture2D tex2D = texture as Texture2D;
if (tex2D != null && tex2D.mipmapCount > 1)
{
// mipmaps大约增加1/3的内存
baseSize = (long)(baseSize * 1.33f);
}
return baseSize;
}
private int GetTextureBytesPerPixel(Texture texture)
{
// 根据纹理类型和格式估算每像素字节数
Texture2D tex2D = texture as Texture2D;
if (tex2D != null)
{
// 根据格式返回估算值
switch (tex2D.format)
{
case TextureFormat.RGB24:
return 3;
case TextureFormat.RGBA32:
return 4;
case TextureFormat.DXT1:
return 1; // 近似值
case TextureFormat.DXT5:
return 2; // 近似值
// 更多格式...
default:
return 4; // 默认假设
}
}
RenderTexture rt = texture as RenderTexture;
if (rt != null)
{
// 根据格式返回估算值
switch (rt.format)
{
case RenderTextureFormat.ARGB32:
return 4;
case RenderTextureFormat.ARGBHalf:
return 8;
// 更多格式...
default:
return 4;
}
}
return 4; // 默认假设
}
private string GetTextureFormat(Texture texture)
{
Texture2D tex2D = texture as Texture2D;
if (tex2D != null)
{
return tex2D.format.ToString();
}
RenderTexture rt = texture as RenderTexture;
if (rt != null)
{
return rt.format.ToString();
}
return "Unknown";
}
}
CPU密集计算优化案例
using UnityEngine;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Burst;
using System.Threading.Tasks;
public class CPUOptimizationExample : MonoBehaviour
{
[SerializeField] private int entityCount = 10000;
[SerializeField] private OptimizationMode mode = OptimizationMode.Standard;
public enum OptimizationMode
{
Standard,
Optimized,
JobSystem,
ParallelFor
}
private struct Entity
{
public Vector3 position;
public Vector3 velocity;
public float mass;
public bool active;
}
private List<Entity> entities = new List<Entity>();
private void Start()
{
// 初始化实体
for (int i = 0; i < entityCount; i++)
{
entities.Add(new Entity
{
position = Random.insideUnitSphere * 100f,
velocity = Random.insideUnitSphere * 10f,
mass = Random.Range(1f, 10f),
active = true
});
}
}
private void Update()
{
// 使用选定的优化模式更新实体
switch (mode)
{
case OptimizationMode.Standard:
UpdateEntitiesStandard();
break;
case OptimizationMode.Optimized:
UpdateEntitiesOptimized();
break;
case OptimizationMode.JobSystem:
UpdateEntitiesJobSystem();
break;
case OptimizationMode.ParallelFor:
UpdateEntitiesParallel();
break;
}
}
// 标准方法 - 未优化
private void UpdateEntitiesStandard()
{
// 每帧计算所有实体的重力相互作用
for (int i = 0; i < entities.Count; i++)
{
if (!entities[i].active) continue;
Vector3 totalForce = Vector3.zero;
// 计算与其他所有实体的重力相互作用
for (int j = 0; j < entities.Count; j++)
{
if (i == j || !entities[j].active) continue;
Vector3 direction = entities[j].position - entities[i].position;
float distance = direction.magnitude;
if (distance < 0.1f) continue;
// 重力公式: F = G * (m1 * m2) / d^2
float forceMagnitude = 0.01f * entities[i].mass * entities[j].mass / (distance * distance);
totalForce += direction.normalized * forceMagnitude;
}
// 更新速度和位置 (F = ma, 因此 a = F/m)
Vector3 acceleration = totalForce / entities[i].mass;
Entity entity = entities[i];
entity.velocity += acceleration * Time.deltaTime;
entity.position += entity.velocity * Time.deltaTime;
entities[i] = entity;
}
}
// 优化方法 - 使用简单的CPU优化技巧
private void UpdateEntitiesOptimized()
{
float deltaTime = Time.deltaTime;
float gravityConstant = 0.01f;
// 预计算重复使用的数据
Vector3[] forces = new Vector3[entities.Count];
// 使用空间分区优化 (这里使用简单网格)
const int gridSize = 10;
List<int>[ , , ] grid = new List<int>[gridSize, gridSize, gridSize];
// 初始化网格
for (int x = 0; x < gridSize; x++)
{
for (int y = 0; y < gridSize; y++)
{
for (int z = 0; z < gridSize; z++)
{
grid[x, y, z] = new List<int>();
}
}
}
// 将实体分配到网格
for (int i = 0; i < entities.Count; i++)
{
if (!entities[i].active) continue;
// 计算网格坐标
Vector3 pos = entities[i].position + new Vector3(100, 100, 100); // 偏移以处理负值
int x = Mathf.Clamp((int)(pos.x / 20), 0, gridSize - 1);
int y = Mathf.Clamp((int)(pos.y / 20), 0, gridSize - 1);
int z = Mathf.Clamp((int)(pos.z / 20), 0, gridSize - 1);
grid[x, y, z].Add(i);
}
// 计算力
for (int i = 0; i < entities.Count; i++)
{
if (!entities[i].active) continue;
Vector3 totalForce = Vector3.zero;
Vector3 pos = entities[i].position;
// 计算网格坐标
Vector3 gridPos = pos + new Vector3(100, 100, 100);
int gx = Mathf.Clamp((int)(gridPos.x / 20), 0, gridSize - 1);
int gy = Mathf.Clamp((int)(gridPos.y / 20), 0, gridSize - 1);
int gz = Mathf.Clamp((int)(gridPos.z / 20), 0, gridSize - 1);
// 只检查相邻网格的实体
for (int nx = Mathf.Max(0, gx - 1); nx <= Mathf.Min(gx + 1, gridSize - 1); nx++)
{
for (int ny = Mathf.Max(0, gy - 1); ny <= Mathf.Min(gy + 1, gridSize - 1); ny++)
{
for (int nz = Mathf.Max(0, gz - 1); nz <= Mathf.Min(gz + 1, gridSize - 1); nz++)
{
// 检查该网格中的所有实体
foreach (int j in grid[nx, ny, nz])
{
if (i == j) continue;
Vector3 direction = entities[j].position - pos;
float sqrDistance = direction.sqrMagnitude; // 避免开方
if (sqrDistance < 0.01f) continue;
// 重力公式: F = G * (m1 * m2) / d^2
float forceMagnitude = gravityConstant * entities[i].mass * entities[j].mass / sqrDistance;
// 使用归一化的方向向量
totalForce += direction * (forceMagnitude / Mathf.Sqrt(sqrDistance));
}
}
}
}
forces[i] = totalForce;
}
// 更新速度和位置
for (int i = 0; i < entities.Count; i++)
{
if (!entities[i].active) continue;
Entity entity = entities[i];
// 更新速度和位置 (F = ma, 因此 a = F/m)
Vector3 acceleration = forces[i] / entity.mass;
entity.velocity += acceleration * deltaTime;
entity.position += entity.velocity * deltaTime;
entities[i] = entity;
}
}
// Unity Job System 方法
private void UpdateEntitiesJobSystem()
{
int count = entities.Count;
// 创建NativeArrays来存储数据
NativeArray<float3> positions = new NativeArray<float3>(count, Allocator.TempJob);
NativeArray<float3> velocities = new NativeArray<float3>(count, Allocator.TempJob);
NativeArray<float> masses = new NativeArray<float>(count, Allocator.TempJob);
NativeArray<bool> active = new NativeArray<bool>(count, Allocator.TempJob);
NativeArray<float3> forces = new NativeArray<float3>(count, Allocator.TempJob);
// 填充数据
for (int i = 0; i < count; i++)
{
positions[i] = entities[i].position;
velocities[i] = entities[i].velocity;
masses[i] = entities[i].mass;
active[i] = entities[i].active;
}
// 创建计算力的作业
ComputeForcesJob forceJob = new ComputeForcesJob
{
positions = positions,
masses = masses,
active = active,
forces = forces,
gravityConstant = 0.01f
};
// 创建和调度力计算作业
JobHandle forceHandle = forceJob.Schedule();
// 创建更新位置的作业
UpdatePositionsJob positionJob = new UpdatePositionsJob
{
positions = positions,
velocities = velocities,
masses = masses,
forces = forces,
active = active,
deltaTime = Time.deltaTime
};
// 在力计算作业完成后调度位置更新作业
JobHandle positionHandle = positionJob.Schedule(forceHandle);
// 等待作业完成
positionHandle.Complete();
// 更新主线程中的实体数据
for (int i = 0; i < count; i++)
{
Entity entity = entities[i];
entity.position = positions[i];
entity.velocity = velocities[i];
entities[i] = entity;
}
// 释放本机数组
positions.Dispose();
velocities.Dispose();
masses.Dispose();
active.Dispose();
forces.Dispose();
}
// Parallel.For 方法
private async void UpdateEntitiesParallel()
{
int count = entities.Count;
Vector3[] forces = new Vector3[count];
float deltaTime = Time.deltaTime;
// 并行计算力
await Task.Run(() => {
Parallel.For(0, count, i => {
if (!entities[i].active) return;
Vector3 pos = entities[i].position;
Vector3 totalForce = Vector3.zero;
for (int j = 0; j < count; j++)
{
if (i == j || !entities[j].active) continue;
Vector3 direction = entities[j].position - pos;
float sqrDistance = direction.sqrMagnitude;
if (sqrDistance < 0.01f) continue;
float forceMagnitude = 0.01f * entities[i].mass * entities[j].mass / sqrDistance;
totalForce += direction.normalized * forceMagnitude;
}
forces[i] = totalForce;
});
});
// 在主线程中更新位置
for (int i = 0; i < count; i++)
{
if (!entities[i].active) continue;
Entity entity = entities[i];
Vector3 acceleration = forces[i] / entity.mass;
entity.velocity += acceleration * deltaTime;
entity.position += entity.velocity * deltaTime;
entities[i] = entity;
}
}
// Unity Job System 作业定义
[BurstCompile]
private struct ComputeForcesJob : IJob
{
[ReadOnly] public NativeArray<float3> positions;
[ReadOnly] public NativeArray<float> masses;
[ReadOnly] public NativeArray<bool> active;
public NativeArray<float3> forces;
public float gravityConstant;
public void Execute()
{
int count = positions.Length;
for (int i = 0; i < count; i++)
{
if (!active[i]) continue;
float3 totalForce = float3.zero;
float3 pos = positions[i];
for (int j = 0; j < count; j++)
{
if (i == j || !active[j]) continue;
float3 direction = positions[j] - pos;
float sqrDistance = math.lengthsq(direction);
if (sqrDistance < 0.01f) continue;
float distance = math.sqrt(sqrDistance);
float forceMagnitude = gravityConstant * masses[i] * masses[j] / sqrDistance;
totalForce += math.normalize(direction) * forceMagnitude;
}
forces[i] = totalForce;
}
}
}
[BurstCompile]
private struct UpdatePositionsJob : IJob
{
public NativeArray<float3> positions;
public NativeArray<float3> velocities;
[ReadOnly] public NativeArray<float> masses;
[ReadOnly] public NativeArray<float3> forces;
[ReadOnly] public NativeArray<bool> active;
public float deltaTime;
public void Execute()
{
int count = positions.Length;
for (int i = 0; i < count; i++)
{
if (!active[i]) continue;
float3 acceleration = forces[i] / masses[i];
float3 velocity = velocities[i] + acceleration * deltaTime;
float3 position = positions[i] + velocity * deltaTime;
velocities[i] = velocity;
positions[i] = position;
}
}
}
}
十五、结论与最佳实践
性能预算管理
using UnityEngine;
using System.Collections.Generic;
using Unity.Profiling;
public class PerformanceBudgetManager : MonoBehaviour
{
[System.Serializable]
public class BudgetEntry
{
public string name;
public float budgetMs;
public string profilerSampleName;
[HideInInspector] public float current;
[HideInInspector] public float peak;
[HideInInspector] public float average;
[HideInInspector] public int overBudgetCount;
}
[Header("Performance Budgets")]
[SerializeField] private BudgetEntry[] budgets = new BudgetEntry[]
{
new BudgetEntry { name = "CPU Total", budgetMs = 16.67f, profilerSampleName = "PlayerLoop" },
new BudgetEntry { name = "Rendering", budgetMs = 8.0f, profilerSampleName = "Render" },
new BudgetEntry { name = "Physics", budgetMs = 3.0f, profilerSampleName = "Physics.Processing" },
new BudgetEntry { name = "Scripts", budgetMs = 4.0f, profilerSampleName = "Update.ScriptRunBehaviourUpdate" },
new BudgetEntry { name = "Animation", budgetMs = 2.0f, profilerSampleName = "Animation.Update" }
};
[Header("Settings")]
[SerializeField] private bool logWarnings = true;
[SerializeField] private int framesBetweenChecks = 10;
[SerializeField] private int warningThreshold = 5;
[SerializeField] private bool enableAutoOptimizations = false;
// 采样器
private Dictionary<string, ProfilerRecorder> recorders = new Dictionary<string, ProfilerRecorder>();
// 性能历史
private Dictionary<string, Queue<float>> sampleHistory = new Dictionary<string, Queue<float>>();
private const int historyLength = 100;
private int frameCounter = 0;
private void OnEnable()
{
// 创建Profiler记录器
foreach (BudgetEntry budget in budgets)
{
string[] samplePath = budget.profilerSampleName.Split('.');
ProfilerCategory category = ProfilerCategory.Scripts;
string name = budget.profilerSampleName;
// 根据路径推断类别
if (samplePath.Length > 1)
{
switch (samplePath[0])
{
case "Render":
category = ProfilerCategory.Render;
name = samplePath[1];
break;
case "Physics":
category = ProfilerCategory.Physics;
name = samplePath[1];
break;
case "Animation":
category = ProfilerCategory.Animation;
name = samplePath[1];
break;
// 添加更多类别...
}
}
else if (name == "PlayerLoop")
{
category = ProfilerCategory.Internal;
}
ProfilerRecorder recorder = ProfilerRecorder.StartNew(category, name);
recorders[budget.name] = recorder;
// 初始化历史
sampleHistory[budget.name] = new Queue<float>();
}
}
private void OnDisable()
{
// 释放记录器
foreach (ProfilerRecorder recorder in recorders.Values)
{
recorder.Dispose();
}
recorders.Clear();
}
private void Update()
{
frameCounter++;
// 每隔几帧检查一次预算
if (frameCounter % framesBetweenChecks != 0) return;
CheckBudgets();
}
private void CheckBudgets()
{
bool performanceIssuesDetected = false;
foreach (BudgetEntry budget in budgets)
{
if (recorders.TryGetValue(budget.name, out ProfilerRecorder recorder))
{
if (!recorder.Valid) continue;
// 从纳秒转换为毫秒
float milliseconds = recorder.LastValue / 1000000f;
// 更新当前值
budget.current = milliseconds;
// 更新峰值
budget.peak = Mathf.Max(budget.peak, milliseconds);
// 添加到历史
Queue<float> history = sampleHistory[budget.name];
history.Enqueue(milliseconds);
if (history.Count > historyLength)
{
history.Dequeue();
}
// 计算平均值
float sum = 0;
foreach (float sample in history)
{
sum += sample;
}
budget.average = sum / history.Count;
// 检查是否超出预算
if (milliseconds > budget.budgetMs)
{
budget.overBudgetCount++;
if (budget.overBudgetCount >= warningThreshold && logWarnings)
{
Debug.LogWarning($"Performance Budget '{budget.name}' exceeded: {milliseconds:F2}ms (Budget: {budget.budgetMs:F2}ms)");
performanceIssuesDetected = true;
}
}
else
{
budget.overBudgetCount = Mathf.Max(0, budget.overBudgetCount - 1);
}
}
}
// 如果启用自动优化并检测到性能问题
if (enableAutoOptimizations && performanceIssuesDetected)
{
ApplyAutomaticOptimizations();
}
}
private void ApplyAutomaticOptimizations()
{
// 根据超出的预算应用相应的优化
foreach (BudgetEntry budget in budgets)
{
if (budget.overBudgetCount >= warningThreshold)
{
switch (budget.name)
{
case "Rendering":
OptimizeRendering(budget.current / budget.budgetMs);
break;
case "Physics":
OptimizePhysics(budget.current / budget.budgetMs);
break;
case "Scripts":
OptimizeScripts(budget.current / budget.budgetMs);
break;
// 其他系统的优化...
}
}
}
}
private void OptimizeRendering(float overBudgetFactor)
{
// 示例:根据性能问题的严重程度调整渲染质量
QualitySettings.shadows = overBudgetFactor > 1.5f ?
ShadowQuality.Disable :
(overBudgetFactor > 1.2f ? ShadowQuality.HardOnly : ShadowQuality.All);
// 调整LOD偏置
float biasAdjustment = Mathf.Clamp(overBudgetFactor - 1.0f, 0f, 1f);
QualitySettings.lodBias = Mathf.Max(0.3f, 1.0f - biasAdjustment);
Debug.Log($"Auto-optimized rendering: LOD Bias = {QualitySettings.lodBias}, Shadows = {QualitySettings.shadows}");
}
private void OptimizePhysics(float overBudgetFactor)
{
// 示例:调整物理更新频率
Time.fixedDeltaTime = Mathf.Clamp(0.02f * overBudgetFactor, 0.02f, 0.05f);
Debug.Log($"Auto-optimized physics: FixedDeltaTime = {Time.fixedDeltaTime}");
}
private void OptimizeScripts(float overBudgetFactor)
{
// 通知游戏系统调整复杂性
// 这通常需要自定义实现,因为它高度依赖于游戏
// 示例:查找并调整AI管理器
AIManager aiManager = FindObjectOfType<AIManager>();
if (aiManager != null)
{
int targetPriority = overBudgetFactor > 1.5f ? 0 : (overBudgetFactor > 1.2f ? 1 : 2);
aiManager.SetAIPriority(targetPriority);
Debug.Log($"Auto-optimized AI: Priority = {targetPriority}");
}
}
// 提供外部访问性能数据的方法
public Dictionary<string, BudgetEntry> GetBudgetReport()
{
Dictionary<string, BudgetEntry> report = new Dictionary<string, BudgetEntry>();
foreach (BudgetEntry budget in budgets)
{
report[budget.name] = budget;
}
return report;
}
// 模拟AI管理器
private class AIManager : MonoBehaviour
{
public void SetAIPriority(int priority)
{
// 0 = 低, 1 = 中, 2 = 高
// 实际实现...
}
}
}
Unity性能优化最佳实践总结
using UnityEngine;
public class PerformanceBestPracticesGuide : MonoBehaviour
{
[Header("Unity Profiling Best Practices")]
[TextArea(5, 10)]
public string profilingBestPractices =
@"1. 始终在目标平台上进行性能分析,而不仅仅是在编辑器中
2. 使用Development Build并启用Autoconnect Profiler进行设备分析
3. 先专注于主要瓶颈,不要过早优化小问题
4. 建立性能预算,并持续监控是否符合预期
5. 使用标记(markers)和自定义采样来精确定位性能问题
6. 比较优化前后的性能数据,确保改进有效";
[Header("CPU Optimization Best Practices")]
[TextArea(5, 10)]
public string cpuBestPractices =
@"1. 避免在Update中进行复杂计算,考虑将工作分散到多个帧中
2. 使用对象池而不是频繁的实例化和销毁
3. 最小化GC开销:
- 避免在频繁调用的方法中分配临时对象
- 重用集合而非每次创建新的
- 使用struct代替class来减少堆分配
4. 利用缓存来避免重复计算
5. 使用Unity Job System或Parallel.For进行并行计算
6. 实现LOD系统降低远处对象的更新频率
7. 使用协程分散计算密集型任务";
[Header("Memory Optimization Best Practices")]
[TextArea(5, 10)]
public string memoryBestPractices =
@"1. 使用内存分析器定期检查内存使用情况
2. 注意静态集合的使用,它们可能导致内存泄漏
3. 防止循环引用导致的泄漏
4. 正确取消事件订阅
5. 适当管理资源加载和卸载:
- 使用Resources.UnloadUnusedAssets()清理未使用资源
- 通过Addressables或AssetBundles管理大型资源
6. 监控纹理和网格内存使用
7. 实现适当的场景转换清理流程";
[Header("Rendering Optimization Best Practices")]
[TextArea(5, 10)]
public string renderingBestPractices =
@"1. 减少DrawCall:
- 使用静态批处理合并静态对象
- 为相似对象启用GPU实例化
- 使用图集合并纹理
2. 简化着色器和材质:
- 避免过度复杂的像素着色器
- 精简使用的材质和纹理变体
3. 使用遮挡剔除减少渲染对象
4. 优化光照:
- 使用烘焙光照代替实时光照
- 限制实时光源的数量和范围
5. 谨慎使用后期处理效果
6. 根据目标设备选择合适的渲染路径";
[Header("Physics Optimization Best Practices")]
[TextArea(5, 10)]
public string physicsBestPractices =
@"1. 使用简单碰撞体(盒体、球体、胶囊体)代替复杂的Mesh碰撞体
2. 合理设置碰撞层以减少不必要的碰撞检测
3. 使用触发器代替碰撞体来进行简单的检测
4. 对远处或不活跃的对象禁用物理组件
5. 避免刚体堆叠
6. 根据需要调整Time.fixedDeltaTime
7. 使用连续碰撞检测仅适用于高速移动的对象";
[Header("UI Optimization Best Practices")]
[TextArea(5, 10)]
public string uiBestPractices =
@"1. 减少Canvas重建:
- 避免频繁更改Text组件内容
- 使用Canvas.enableBatchingForUI选项
2. 确保UI元素不重叠/不交叉布局组
3. 禁用不可见UI元素而非仅设置Alpha为0
4. 使用UI元素对象池处理列表和重复元素
5. 为复杂UI结构使用多个Canvas
6. 谨慎使用Layout组件,它们可能导致级联重建";
[Header("Asset Optimization Best Practices")]
[TextArea(5, 10)]
public string assetBestPractices =
@"1. 纹理优化:
- 使用适当的压缩格式
- 根据实际需求设置分辨率
- 为移动平台启用mipmap
2. 模型优化:
- 减少顶点数量和骨骼数量
- 移除不可见的多边形
- 优化UV映射
3. 音频优化:
- 为背景音乐使用压缩格式
- 为短音效使用未压缩格式
- 谨慎使用3D音频源
4. 使用Addressables系统管理资源加载
5. 实现资产捆绑策略最小化内存占用";
[Header("Tools and Workflow Best Practices")]
[TextArea(5, 10)]
public string workflowBestPractices =
@"1. 在开发周期早期建立性能测试和监控流程
2. 使用自动化性能测试检测性能退化
3. 为主要功能设定明确的性能预算
4. 建立持续集成性能检查
5. 记录优化结果并分享最佳实践
6. 针对不同平台维护配置文件
7. 定期进行完整的性能分析";
void Start()
{
Debug.Log("这个脚本包含Unity性能优化和Profiling的最佳实践总结。请在Inspector中查看详细内容。");
}
}
这份Unity引擎Profiling工具使用技术总结涵盖了Unity性能分析的各个方面,从基础工具使用到高级优化技术。通过这些代码示例和最佳实践,开发者可以全面掌握Unity性能分析工具的使用方法,优化游戏性能,提供更好的用户体验。
更多推荐
所有评论(0)