Unity优化方向——优化Unity游戏中的垃圾回收(译)
介绍
诊断垃圾回收的问题
Unity内存管理简介
- Unity可以访问两个内存池:栈(stack)和堆(heap,也称为托管堆)。栈用于短期存储小块数据,堆用于长期存储和大块数据。
- 当一个变量被创建时,Unity会从栈或堆中请求一个内存块。
- 只要变量在作用域内(仍然可由代码访问),分配给它的内存就会一直使用。我们说这个内存已经分配了。我们将栈内存中保存的变量描述为栈上的对象,将堆内存中保存的变量描述为堆上的对象。
- 当变量超出作用域时,内存不再需要,可以将其返回到它所来自的池中。当内存被返回到它的池时,我们说内存已被释放。栈中内存在它引用的变量超出作用域时立即释放。但是,堆中的内存此时没有释放,并且仍然处于已分配的状态,即使它引用的变量超出了作用域。
- 垃圾回收器标识和释放未使用的堆内存。垃圾回收器定期运行以清理堆。
栈分配和释放期间发生了什么?
堆分配期间发生了什么?
- 首先,Unity必须检查堆中是否有足够的空闲内存。如果堆中有足够的空闲内存,则为变量分配内存。
- 如果堆中没有足够的空闲内存,Unity会触发垃圾回收器,试图释放未使用的堆内存。这可能是一个缓慢的操作。如果堆中现在有足够的空闲内存,则分配变量的内存。
- 如果垃圾回收后堆中没有足够的空闲内存,Unity会增加堆中的内存容量。这可能是一个缓慢的操作。然后分配变量的内存。
垃圾回收期间发生了什么?
- 垃圾回收器检查堆上的每个对象。
- 垃圾回收器搜索所有当前对象引用,以确定堆上的对象是否仍在作用域内。
- 任何不再在作用域中的对象都被标记为删除。
- 删除标记的对象,并将分配给它们的内存返回堆中。
- 每当请求使用堆中的空闲内存无法完成堆分配时,垃圾回收器就会运行。
- 垃圾回收器不时自动运行(尽管频率随平台而变化)。
- 垃圾回收器可以强制手动运行。
垃圾回收的问题
发现堆分配
在栈和堆上分配了什么?
void ExampleFunction()
{
int localInt = ;
}
void ExampleFunction()
{
List localList = new List();
}
减少垃圾回收的影响
- 我们可以减少垃圾回收器运行的时间。
- 我们可以减少垃圾回收器运行的频率。
- 我们可以故意触发垃圾回收器,使其在性能不重要的时候运行(例如在加载屏幕期间)。
- 我们可以阻止我们游戏,这样我们就有更少的堆分配和更少的对象引用。堆上的对象越少,要检查的引用越少,这意味着在触发垃圾回收时,运行垃圾回收所需的时间越少。
- 我们可以减少堆分配和回收的频率,特别是在性能关键时候。更少的分配和回收意味着触发垃圾回收的情况更少。这也降低了堆碎片的风险。
- 我们可以尝试计时垃圾回收和堆扩展,以便在可预测和方便的时间进行。这是一种更困难、更不可靠的方法,但是如果将其作为整体内存管理策略的一部分使用,则可以减少垃圾回收的影响。
减少垃圾的创建
缓存
void OnTriggerEnter(Collider other)
{
Renderer[] allRenderers = FindObjectsOfType<Renderer>();
ExampleFunction(allRenderers);
}
private Renderer[] allRenderers; void Start()
{
allRenderers = FindObjectsOfType<Renderer>();
} void OnTriggerEnter(Collider other)
{
ExampleFunction(allRenderers);
}
不要在频繁调用的函数中进行分配
void Update()
{
ExampleGarbageGeneratingFunction(transform.position.x);
}
private float previousTransformPositionX; void Update()
{
float transformPositionX = transform.position.x;
if (transformPositionX != previousTransformPositionX)
{
ExampleGarbageGeneratingFunction(transformPositionX);
previousTransformPositionX = transformPositionX;
}
}
void Update()
{
ExampleGarbageGeneratingFunction();
}
private float timeSinceLastCalled; private float delay = 1f; void Update()
{
timeSinceLastCalled += Time.deltaTime;
if (timeSinceLastCalled > delay)
{
ExampleGarbageGeneratingFunction();
timeSinceLastCalled = 0f;
}
}
清除集合
void Update()
{
List myList = new List();
PopulateList(myList);
}
private List myList = new List();
void Update()
{
myList.Clear();
PopulateList(myList);
}
对象池
不必要的堆分配的常见原因
字符串(strings)
- 我们应该减少不必要的字符串创建。如果我们不止一次使用相同得字符串值,我们应该创建一个字符串并缓存该值。
- 我们应该减少不必要的字符串操作。例如,如果我们有一个经常更新的文本(Text)组件,并且包含一个连接的字符串,我们可以考虑将它分成两个文本组件。
- 如果我们必须在运行时构建字符串,我们应该使用StringBuilder类。StringBuilder类用于构建没有分配的字符串,并且可以节省在连接复杂字符串时产生的垃圾数量。
- 我们应该在调试不再需要Debug.Log()调用时立即删除它们。对Debug.Log()的调用仍然在游戏的所有构建中执行,即使它们没有输出任何内容。对Debug. Log()的调用至少会创建并处理一个字符串,因此如果游戏包含许多此类调用,那么垃圾就会堆积起来。
public Text timerText;
private float timer; void Update()
{
timer += Time.deltaTime;
timerText.text = "TIME:" + timer.ToString();
}
public Text timerHeaderText;
public Text timerValueText;
private float timer; void Start()
{
timerHeaderText.text = "TIME:";
} void Update()
{
timerValueText.text = timer.toString();
}
Unity的函数调用
void ExampleFunction()
{
for (int i = ; i < myMesh.normals.Length; i++)
{
Vector3 normal = myMesh.normals[i];
}
}
void ExampleFunction()
{
Vector3[] meshNormals = myMesh.normals;
for (int i = ; i < meshNormals.Length; i++)
{
Vector3 normal = meshNormals[i];
}
}
private string playerTag = "Player"; void OnTriggerEnter(Collider other)
{
bool isPlayer = other.gameObject.tag == playerTag;
}
private string playerTag = "Player"; void OnTriggerEnter(Collider other)
{
bool isPlayer = other.gameObject.CompareTag(playerTag);
}
装箱(boxing)
void ExampleFunction()
{
int cost = ;
string displayString = String.Format("Price: {0} gold", cost);
}
协程(Cotoutines)
yield return ;
yield return null;
while (!isComplete)
{
yield return new WaitForSeconds(1f);
}
WaitForSeconds delay = new WaitForSeconds(1f); while (!isComplete)
{
yield return delay;
}
foreach循环
void ExampleFunction(List listOfInts)
{
foreach (int currentInt in listOfInts)
{
DoSomething(currentInt);
}
}
void ExampleFunction(List listOfInts)
{
for (int i = ; i < listOfInts.Count; i ++)
{
int currentInt = listOfInts[i];
DoSomething(currentInt);
}
}
函数引用
LINQ和正则表达式
构造代码以达到最小化垃圾回收的影响
public struct ItemData
{
public string name;
public int cost;
public Vector3 position;
}
private ItemData[] itemData;
private string[] itemNames;
private int[] itemCosts;
private Vector3[] itemPositions;
public class DialogData
{
private DialogData nextDialog; public DialogData GetNextDialog()
{
return nextDialog;
}
}
public class DialogData
{
private int nextDialogID; public int GetNextDialogID()
{
return nextDialogID;
}
}
手动强制垃圾回收
System.GC.Collect();
总结
延伸阅读
- Unity Manual: Understanding Optimization in Unity
- Unity Manual: Understanding Automatic Memory Management
- Gamasutra: C# Memory Management for Unity Developers by Wendelin Reich
- Gamasutra: C# memory and performance tips for Unity by Robert Zubek
- Gamasutra: Reducing memory allocations to avoid Garbage Collection on Unity by Grhyll JDD
- Gamasutra: Unity Garbage Collection Tips and Tricks by Megan Hughes
Unity优化方向——优化Unity游戏中的垃圾回收(译)的更多相关文章
- .NET中的垃圾回收
目录 l 导言 l 关于垃圾回收 l 垃圾回收算法 m 应用程序根(Application Roots) l 实现 m ...
- 浅谈V8引擎中的垃圾回收机制
最近在看<深入浅出nodejs>关于V8垃圾回收机制的章节,转自:http://blog.segmentfault.com/skyinlayer/1190000000440270 这篇文章 ...
- JVM调优-Jva中基本垃圾回收算法
从不同的的角度去划分垃圾回收算法. 按照基本回收策略分 引用计数(Reference Counting) 比较古老的回收算法.原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数.垃圾回 ...
- 浅谈Chrome V8引擎中的垃圾回收机制
垃圾回收器 JavaScript的垃圾回收器 JavaScript使用垃圾回收机制来自动管理内存.垃圾回收是一把双刃剑,其好处是可以大幅简化程序的内存管理代码,降低程序员的负担,减少因 长时间运转而带 ...
- 【java虚拟机序列】java中的垃圾回收与内存分配策略
在[java虚拟机系列]java虚拟机系列之JVM总述中我们已经详细讲解过java中的内存模型,了解了关于JVM中内存管理的基本知识,接下来本博客将带领大家了解java中的垃圾回收与内存分配策略. 垃 ...
- Python中的垃圾回收与del语句
python中的垃圾回收采用计数算法 一个对象如果被引用N次,则需要N次(即计算引用次数为零时)执行del 才能回收此对象. a = 100 b = a del a print(b) print(a) ...
- 浅谈jvm中的垃圾回收策略
下面小编就为大家带来一篇浅谈jvm中的垃圾回收策略.小编觉得挺不错的,现在就分享给大家,也给大家做个参考.一起跟随小编过来看看吧 java和C#中的内存的分配和释放都是由虚拟机自动管理的,此前我已 ...
- 深入理解Node.js中的垃圾回收和内存泄漏的捕获
深入理解Node.js中的垃圾回收和内存泄漏的捕获 文章来自:http://wwsun.github.io/posts/understanding-nodejs-gc.html Jan 5, 2016 ...
- Java中的垃圾回收算法详解
一.前言 前段时间大致看了一下<深入理解Java虚拟机>这本书,对相关的基础知识有了一定的了解,准备写一写JVM的系列博客,这是第二篇.这篇博客就来谈一谈JVM中使用到的垃圾回收算法. ...
随机推荐
- 获取URL网页信息
static string GetHtml(string url) {string strHTML = ""; WebClient myWebClient = new WebCli ...
- Hadoop HA on Yarn——集群启动
这里分两部分,第一部分是NameNode HA,第二部分是ResourceManager HA (ResourceManager HA是hadoop-2.4.1之后加上的) NameNode HA 1 ...
- Python全栈开发:list 、tuple以及dict的总结
总结: 列表:增:append(),inset(),extend() 删:pop(),remove(),clear(),del 改:a.通过指定元素和切片重新赋值.b.可以使用repelace替换列表 ...
- weblogic之CVE-2018-3191漏洞分析
weblogic之CVE-2018-3191漏洞分析 理解这个漏洞首先需要看这篇文章:https://www.cnblogs.com/afanti/p/10193169.html 引用廖新喜说的,说白 ...
- Hive学习之路 (四)Hive的连接3种连接方式
一.CLI连接 进入到 bin 目录下,直接输入命令: [hadoop@hadoop3 ~]$ hive SLF4J: Class path contains multiple SLF4J bindi ...
- P1776 宝物筛选_NOI导刊2010提高(02)
题目描述 终于,破解了千年的难题.小FF找到了王室的宝物室,里面堆满了无数价值连城的宝物……这下小FF可发财了,嘎嘎.但是这里的宝物实在是太多了,小FF的采集车似乎装不下那么多宝物.看来小FF只能含泪 ...
- iPhone将NSString转换编码集为gb2312或者gbk的方法
很多时候软件读取的中文网页编码集是gb2312,所以显示出来的是乱码.这时需要将NSString文字编码转换.你可以试试以下代码 NSURL *url = [NSURL URLWithString:u ...
- 清除浮动元素的margin-top失效原因(更改之前的错误)
//样式代码body,div{ margin:; padding:; } .box1{ background:#900; width:200px; height:200px; margin:20px ...
- #leetcode刷题之路33-搜索旋转排序数组
假设按照升序排序的数组在预先未知的某个点上进行了旋转.( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] ).搜索一个给定的目标值,如果数组中存在这个目标值,则返 ...
- mysql where语句多条件查询是and和or联合使用bug
公司项目中有段功能是全局搜索框和下拉列表的联合查询,在联调开发中发现单独用下拉查询是正确的,单独用全局搜索框也是正确的,测试发现是sql语法有问题. 问题截图: 出现问题的核心还是在于搜索框是用于多个 ...