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中使用到的垃圾回收算法. ...
随机推荐
- nginx配置解析之客户端真实IP的传递
前后端分离之后,采用nginx作为静态服务器,并通过反向代理的方式实现接口跨域的方式,在降低开发成本的同时也带来了诸多问题,例如客户端真实IP的获取. 在一些特殊场景下,比如风控和支付流程,往往需要获 ...
- Angular总结三:组件
Angular 的应用就是一棵组件树,一个页面可以是一个组件,某一页面的一个区块也可以是一个组件.为了弄明白组件及组件树,我将原来做过的一个静态网站进行组件改造. 原项目地址 https://gith ...
- Django在admin模块中显示auto_now_add=True或auto_now=True的时间类型列
转载自: http://www.tuicool.com/articles/ZryE7f 在Django如果model中的列定义了auto_now_add或auto_now属性,那么这种列不会在admi ...
- C语言不使用加号实现加法运算的几种方法
今天看到<编码:隐匿在计算机软硬件背后的语言>的第十二章:二进制加法器.讲述了全加器,半加器的原理以及如何实现加法.实现加法时所使用的全加器,半加器中包含的所有逻辑门在C语言中都有相应的运 ...
- robotframwork接口测试(五)—接口分层测试粗解
个人小结,仅供参考. 接口测试很简单,但是很重要. 可以写代码,也可以用工具进行测试.工具说说就很多了,简单介绍一下我目前用过的几个能够测试接口的工具, Burpsuite:这类偏请求攻击类软件 Fi ...
- 为什么会有object这么一个根基类
先问一个问题,为什么需要有一个统一的基类:Object?甚至,我们在编程语言中也常常见到这种模式,比如Java中的object.C#的object,甚至一些纯对象的脚本语言(Ruby里连数字123都是 ...
- error info: boost not variable 问题解决
错误信息:error info: boost not variable 解决办法:sudo apt-get install libboost-dev 出现这个问题的原因是我在搭建DOMJudgeOJ平 ...
- Luogu五月月赛
首先,到此为止,我只会\(t1\).\(t2\) T1: \(\color{red}{Description}\) \(Alice\) 和 \(Bob\) 在玩游戏. 他们有 \(n\) 堆石子,第\ ...
- 关于Android
1:Handle与多线程 Handle是什么?官方说明:handle是Android给我们提供用来更新UI的一套机制,也是一套消息处理的机制.可以看出handle主要就是两个功能,一个是更新UI,另一 ...
- 将CSV文件导入到hive数据库
将csv文件导入hive后出现了所有的字段只显示在新建的表的第一个字段中,后面的字段全是null. 出现这种的原因是hive以行分隔数据,需要修改为按逗号' , ‘ 进行分隔读取, 具体操作如下, ...