Unity文档总结(2)-Understanding Automatic Memory Management

时间:2021-10-16 01:10:20

当一个对象、字符串、数组被创建的时候,从中间池申请分配需要存储的内存称为堆。当该部分不在使用时,一度占用的内存被释放收回,用于别的事物。在过去,它通常由开发人员分配和释放这些堆内存块,明确相应的功能调用。如今像Unity的Mono引擎运行时系统自动为你管理内存。自动内存管理不需要进行明确的分配/释放的编码工作,并大大降低了潜在的内存泄漏(内存分配,但从未被释放的情况)。
·值和引用类型
当一个函数被调用时,把其参数的值复制到一个内存区域来保留为了特定的调用,只占用几个字节的数据类型可以被非常容易和快速地创建。然而常见的对象、字符串和数组要大得多,如果这些类型的数据需要定期复制的话效率会变得非常低。幸运的是这是没有必要的;一个大型项目的实际存储空间是从堆中分配并且只使用一个小小的"指针"值来记住它的位置。因此,只有指针需要在复制过程中进行参数传递。只要运行系统就可以找到指针确定的项目,可以视需要将经常使用的数据做个单一副本。直接存储和复制的参数传递过程中的类型被称为值类型。这些包括整型,浮点型,布尔型和Unity的结构类型(例如,Color和Vector3)。被分配在堆里然后通过指针访问的类型被称为引用类型,因为存储在变量中的值只是"指向"真实的数据。引用类型的例子包括:对象,字符串和数组。
·分配和垃圾回收
内存管理会保留堆中它知道未使用的区域。当一个新的内存块请求时(例如当一个对象被实例化),管理机制选择一个未使用的区域分配给内存块,然后把这块未使用空间分配的内存删除。后续请求都以同样的方式处理,直到有没有足够大的空闲面积来分配所需的块大小。从堆中分配的所有内存同时都在使用中是非常不可能的。在堆上的一个参考项目只要仍然能找到它的参考变量就可以访问到它。如果一个内存块的所有引用都消失了(即引用变量都被重新分配,或者他们是超出范围的局部变量),那么它所占用的内存,可以安全地重新分配。
要确定这堆块不再被使用,内存管理器会通过搜索所有当前活动的参考变量和标记他们称之为"活动"的块。在搜索结束时,内存管理器把任何活动的块和块之间的空间视为是空闲的,可以用于后续的分配。显而易见,定位和释放未使用的内存的过程中被称为垃圾收集(简称GC)。
·优化
垃圾回收对开发人员来说是自动的和不可见的,但实际上回收过程在后台需要占用CPU大量的时间。在正确使用时,自动内存管理一般等于或高于手动分配的整体性能。然而,重要的是为程序员避免失误,引发不必要的垃圾回收和执行停顿。
也有一些非主流的算法,能成为的GC的噩梦,即使他们看上去似乎是无辜的。重复字符串连接是一个典型的例子:
//C# script example
using UnityEngine;
using System.Collections;
public class ExampleScript : MonoBehaviour {
void ConcatExample(int[] intArray) {
string line = intArray[0].ToString();
for (i = 1; i < intArray.Length; i++) {
line += ", " + intArray[i].ToString();
}
return line;
}
}
这里的关键细节是,新的块没有一个接一个添加到字符串的位置。实际情况是,每次循环的时候,以前的Line变量的内容成为死链-每次分配的都是一个包含原块加上结尾处新块组成的全新的字符串。随着i的不断增加,字符串也越长,消耗的堆空间量也随之增加,在函数被调用时每次数以百计的字节量很容易把空闲的堆空间耗光。如果您需要连接很多字符串在一起,更好的选择是使用Mono库里的System.Text.StringBuilder类。
不过,除非它被频繁调用,否则即使重复串联不会造成太大的麻烦。因为在Unity中通常是按帧刷新。像下面这样: -
//C# script example
using UnityEngine;
using System.Collections;

public class ExampleScript : MonoBehaviour {
public GUIText scoreBoard;
public int score;

void Update() {
string scoreText = "Score: " + score.ToString();
scoreBoard.text = scoreText;
}
}
每次进行更新时将分配新的字符串,并且垃圾像涓涓细流一样持续产生。大多数字符串可以在更新文本保存除了当score发生变化时: -
//C# script example
using UnityEngine;
using System.Collections;

public class ExampleScript : MonoBehaviour {
public GUIText scoreBoard;
public string scoreText;
public int score;
public int oldScore;

void Update() {
if (score != oldScore) {
scoreText = "Score: " + score.ToString();
scoreBoard.text = scoreText;
oldScore = score;
}
}
}
另一个潜在的问题发生在一个函数返回一个数组值
//C# script example
using UnityEngine;
using System.Collections;

public class ExampleScript : MonoBehaviour {
float[] RandomList(int numElements) {
var result = new float[numElements];

for (int i = 0; i < numElements; i++) {
result[i] = Random.value;
}

return result;
}
}
当创建一个新的数组值填充时,这种类型的函数是非常美观和方便的。然而,如果它被反复调用的话每次都要为它分配新的内存。由于数组可以是非常大的,所消耗完的空闲堆空间会迅速上升,导致频繁的垃圾收集。避免此问题的实际方法之一是使用的数组是一个引用类型。该函数内可以修改成一个函数作为参数传递的数组,并保留函数返回后的结果。像上面的函数常常可以被替换是这样的:
//C# script example
using UnityEngine;
using System.Collections;

public class ExampleScript : MonoBehaviour {
void RandomList(float[] arrayToFill) {
for (int i = 0; i < arrayToFill.Length; i++) {
arrayToFill[i] = Random.value;
}
}
}
这只是简单的用新值取代现有的数组内容。虽然这需要在调用代码时就初始化分配好数组(这似乎有些不美观),该函数在调用时将不会产生任何新的垃圾。
请求回收

如上所述,最好尽量避免内存分配。然而分配内存是无法避免的,所以提供两种方法来让您尽量减少它们在游戏运行时的使用:
·如果堆比较小,则进行快速而频繁的垃圾回收
这一策略比较适合运行时间较长的游戏,其中帧率是否平滑过渡是主要考虑的因素,像这样的游戏通常会频繁地分配小块内存,但这些小块内存只是暂时地被使用,如果在iOS系统上使用该策略,那么一个典型的堆大小是大约 200 KB,这样在iPhone 3G设备上,垃圾回收操作将耗时大约 5毫秒。如果堆大小增加到1 MB时,该回收操作将耗时大约 7ms。因此,在普通帧的间隔期进行垃圾回收有时候是一个不错的选择。通常,这种做法会让回收操作执行的更加频繁(有些回收操作并不是严格必须进行的),但它们可以快速处理并且对游戏的影响很小:
if (Time.frameCount % 30 == 0)
{
System.GC.Collect();
}
但是,您应该小心地使用这种技术,并且通过检查Profiler来确保这种操作确实可以降低您游戏的垃圾回收时间。
·如果堆比较大,则进行缓慢且不频繁的垃圾回收
这一策略适合于那些内存分配 (和回收)相对不频繁,并且可以在游戏停顿期间进行处理的游戏。如果堆足够大,但还没有大到被系统关掉的话,这种方法是比较适用的。但是,Mono运行时会尽可能地避免堆的自动扩大。因此,您需要通过在启动过程中预分配一些空间来手动扩展堆(ie,你实例化一个纯粹影响内存管理器分配的"无用"对象):
//C# script example
using UnityEngine;
using System.Collections;

public class ExampleScript : MonoBehaviour {
void Start() {
var tmp = new System.Object[1024];

//make allocations in smaller blocks to avoid them to be treated in a special way, which is designed for //large blocks
//在小的块中进行分配为了避免他们被一些专门为大型块设计的特别的方式处理掉
for (int i = 0; i < 1024; i++)
tmp[i] = new byte[1024];
// release reference
//释放引用
tmp = null;
}
}
游戏中的暂停是用来对堆内存进行回收,而一个足够大的堆应该不会在游戏的暂停与暂停之间被完全占满。所以,当这种游戏暂停发生时,您可以显式请求一次垃圾回收:
System.GC.Collect();
另外,您应该谨慎地使用这一策略并时刻关注Profiler的统计结果,而不是假定它已经达到了您想要的效果。
可重复使用的对象池
如果想避免垃圾回收,在很多情况下最简单的方法就是减少对象实例化和销毁的次数。在游戏中有一类游戏对象,例如子弹,他们会重复多次出现尽管只在游戏开始的一会。在这种情况下可以重用这些对象而不是销毁他们而创建新的对象。

Unity文档总结(2)-Understanding Automatic Memory Management的更多相关文章

  1. 转)Understanding Java Memory Management

    Understanding Java Memory Management - IBM Java Native Interface (JNI) Objects and Code Java Native ...

  2. Unity文档阅读 第一章 入门

    Before you learn about dependency injection and Unity, you need to understand why you should use the ...

  3. Unity文档阅读 第三章 依赖注入与Unity

    Introduction 简介In previous chapters, you saw some of the reasons to use dependency injection and lea ...

  4. Unity文档阅读 第二章 依赖注入

    Introduction 介绍Chapter 1 outlines how you can address some of the most common requirements in enterp ...

  5. MonoDevelop ctrl &plus; &&num;39&semi; 不能定位正确的unity文档

    Just Do This I had the same problem in MonoDevalop, but the url in it cannot be changed. So I tried ...

  6. Untiy文档总结(1)-Profiling

    这段时间上班了,不是什么大公司,虽说很闲但是不能断了学习,就开始看优化有关的文档,虽说自己英语渣的要死,但也要读下去,逼着自己翻译完了,里面有抄Unity圣典的,但自己是看Unity5.5文档写的,只 ...

  7. Unity历史版本的文档

    前言 在我们的开发过程中,如果要查找Unity文档,通常会有以下两种方式: 1. 打开Unity的官网,查找文档 2. 查找本地安装的Unity文档 但是Unity官网上的文档,总是当前最新版本的文档 ...

  8. Oracle 11g 单实例安装文档

    这里介绍在Red Hat Enterprise Linux Server release 5.7 (Tikanga)下安装ORACLE 11.2.0.1.0的过程,本文仅仅是为了写这样安装指导文档而整 ...

  9. redis module 学习—官网文档整理

    前言 redis在4.0版本中,推出了一个非常吸引的特性,可以通过编写插件的模式,来动态扩展redis的能力.在4.0之前,如果用户想拥有一个带TTL的INCRBY 命令,那么用户只能自己去改代码,重 ...

随机推荐

  1. 数据可视化 echarts3

    初识 echarts ECharts,一个纯 Javascript 的数据可视化图表库,可以流畅的运行在 PC 和移动设备上,兼容当前绝大部分浏览器(IE8/9/10/11,Chrome,Firefo ...

  2. 4&period;Powershell交互界面

    Powershell提供两种接口:交互式和自动化脚本 先学下如何与Powershell Console和平共处,通过Powershell Console和机器学会对话. 通过以上一个简单测试,知道Po ...

  3. 关于git&comma;你需要知道一点命令

    最近有朋友经常问git的一些操作,写在这里与大家分享,以后也不用一一解释了.惯例,这是基础分享,从安装开始说起: 安装: 去这里 https://git-scm.com/ 找到你所需要的版本,下载并安 ...

  4. PHP用户名用星号处理

    PHP用户名用*号处理: 用户名:英文.中文.中英文混合的.中英文字符混合的 处理为:首字母和末尾保留,中间用*号代替(一个字符直接显示,两个字符:张*,三个以上字符:宋*丹) 首先判断字符中是否包含 ...

  5. display&colon;none与visibility&colon; hidden的区别

    display:none和visibility: hidden都能把网页上某个元素隐藏起来,但两者有区别: display:none ---不为被隐藏的对象保留其物理空间,即该对象在页面上彻底消失. ...

  6. cdoj 03 BiliBili&comma; ACFun… And More&excl; 水题

    Article Time Limit: 20 Sec  Memory Limit: 256 MB 题目连接 http://acm.uestc.edu.cn/#/problem/show/3 Descr ...

  7. 嵌入式环境搭建之NFS

    嵌入式环境搭建之NFS Author:tiger-johnTime:2013-08-04mail:jibo.tiger@gmail.comBlog:http://blog.csdn.net/tiger ...

  8. Android Material Design-Creating Apps with Material Design&lpar;用 Material Design设计App&rpar;-&lpar;零&rpar;

    转载请注明出处:http://blog.csdn.net/bbld_/article/details/40400031 翻译自:http://developer.android.com/trainin ...

  9. Hibernate3&period;0中的session&period;find&lpar;&rpar;问题

    我被Session.find()的方法困扰了好几天,今天才看到新的Hibernate里没有了Session.find()方法. 现在转载在此,方便你我. 查询性能往往是系统性能表现的一个重要方面,查询 ...

  10. Excel教程&lpar;11&rpar; - 查找和引用函数

    ADDRESS 用途:以文字形式返回对工作簿中某一单元格的引用.    语法: sheADDRESS(row_num,column_num,abs_num,a1,et_text) 参数:Row_num ...