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中使用到的垃圾回收算法. ...
随机推荐
- Service Mesh服务网格新生代——Istio
Istio 是什么?使用云平台可以为组织提供丰富的好处.然而,不可否认的是,采用云可能会给 DevOps 团队带来压力.开发人员必须使用微服务已满足应用的可移植性,同时运营商管理了极其庞大的混合和多云 ...
- HTML5的新标签-整体布局
过去:<div class="header"> <div class="hgroup"> <h1>....</h1&g ...
- Bottle + WebUploader 修改Bottle框架从而大文件上传实现方案
Bottle 是个轻量级的Web框架,小巧又强大,真不愧是个轻量级的框架.可扩展性非常好,可以扩展很多功能,但是有些功能就不得不自己动手修改了. Bottle:http://www.bottlepy. ...
- [AHOI2009]最小割
题目 最小割的可行边和必须边 可行边\((u,v)\)需要满足以下两个条件 满流 残量网络中不存在\(u\)到\(v\)的路径 这个挺好理解的呀,如果存在还存在路径的话那么这条边就不会是瓶颈了 必须边 ...
- SPOJ-SUBSET Balanced Cow Subsets
嘟嘟嘟spoj 嘟嘟嘟vjudge 嘟嘟嘟luogu 这个数据范围都能想到是折半搜索. 但具体怎么搜呢? 还得扣着方程模型来想:我们把题中的两个相等的集合分别叫做左边和右边,令序列前一半中放到左边的数 ...
- 【转】decorView和window之间的层级及关系
转载请注明出处:http://blog.csdn.net/guxiao1201/article/details/41744107 首先贴出实现Activity对话框圆角的核心代码 @Override ...
- 拼多多java后台笔试题目总结(20180830)
1.回合攻击问题 package com.hone.pdd; import java.util.Scanner; /** * 题目:模拟一个游戏场景,两种伤害,一种正常伤害,一种是先蓄力(也算一个回合 ...
- Linux磁盘与文件系统管理(一)
fdisk 常用的磁盘分区工具,受mbr分区表的限制,只能给小于2TB的磁盘划分分区,如果使用fdisk对大于2TB的磁盘进行分区,虽然可以分区,但只能识别2T的空间,一般使用parted分区工具 - ...
- Linux 和 ubuntu安装redis
Linux 下安装reids 下载地址:http://redis.io/download,下载最新稳定版本. 本教程使用的最新文档版本为 2.8.17,下载并安装: $ wget http://dow ...
- MS522低功耗寻卡
方案:单片机处于低功耗模式,MS522处于软掉电模式.单片机用RTC定时(比如每隔1s)唤醒,单片机唤醒后唤醒MS522寻卡.寻到卡则做进一步处理,否则MS522继续进入软掉电模式,单片机进入低功耗模 ...