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中使用到的垃圾回收算法. ...
随机推荐
- python第八课——random模块的使用
2.2.如何获取随机整数值? 引入random模块的使用 randint(a,b)函数:作用:返回给程序一个[a,b]范围内的随机整数注意:含头含尾闭区间 思路步骤: 第一步:导入random模块到相 ...
- AOP的本质
AOP的本质是HOOK: HOOK的本质是:新函数包含原函数或新函数替换原函数: 需要解决的问题: 1.新函数的生成: 2.新函数的调用机制: 3.原函数的调用机制: 新函数的生成: 1.将已有的动态 ...
- crontab定时执行
一.crond简介 crond是linux下用来周期性的执行某种任务或等待处理某些事件的一个守护进程,与windows下的计划任务类似,当安装完成操作系统后,默认会安装此服务工具,并且会自动启动cro ...
- 造成MySQL全表扫描的原因
全表扫描是数据库搜寻表的每一条记录的过程,直到所有符合给定条件的记录返回为止.通常在数据库中,对无索引的表进行查询一般称为全表扫描:然而有时候我们即便添加了索引,但当我们的SQL语句写的不合理的时候也 ...
- BZOJ 3168 Heoi2013 钙铁锌硒维生素 矩阵求逆+匈牙利算法
题目大意:给定一个n∗n的满秩矩阵A和一个n∗n的矩阵B.求一个字典序最小的1...n的排列a满足将随意一个Ai换成Bai后矩阵A仍然满秩 我们考虑建立一个二分图.假设Ai能换成Bj.就在i−> ...
- 【ui】amazeui前端框架
amazeui Amaze UI是一款较全面的轻量级 (更适于mobile,但也可以用于web)的前端框架. 她综合了业界一些优良插件,直接拿来用而不用一个个去搜索
- 将SharePoint站点另存为模板并根据模板创建站点!
1,将SharePoint站点模板另存为模板. 在网站设置—网站操作一栏下面可以将网站另存为模板. 这儿应该注意:有的时候“将网站另存为模板这个”链接看不到,这个时候打开管理网站功能链接,查看一下“S ...
- Android :Activity、Adapter、List的初步学习
Activity Activity 是一个应用组件,用户可与其提供的屏幕进行交互,以执行对手机应用操作. 每个 Activity 都会获得一个用于绘制其用户界面的窗口.窗口一般是会充满屏幕,但也不一定 ...
- 关于JQ中ready()方法的几种写法总结
——习惯贵在坚持,天才在于积累. 好久没写博客的我,似乎是忘记了当初写博客的初衷是要在博客笔记中提升自己的写作能力和积累自己的知识要点. 废话不多说. ready()方法作用: 在页面加载完成后,立即 ...
- jQuery----选择器(重点是层次选择器)
基本选择器 1.id选择器 ---------------------------->根据id来获取,只有一个.---------------------------------------- ...