Unity性能优化3【内存基础篇】

1.性能、内存管理和垃圾收集的关系

先说结论,内存管理影响着游戏性能,垃圾收集是内存管理的重要部分。

当我们的游戏运行时,它使用内存来存储数据。当不再需要此数据时,存储该数据的内存将被释放,以便可以重复使用。垃圾是指已预留用于存储数据但不再使用的内存的术语。垃圾收集是使内存再次可供重用的进程的名称。 Unity 使用垃圾收集作为管理内存的一部分。如果垃圾收集发生得太频繁或有太多工作要做,我们的游戏可能会表现不佳,这意味着垃圾收集是性能问题的常见原因。

2.Unity内存管理简介

要了解垃圾收集的工作原理以及何时发生,我们必须首先了解 Unity 中内存使用的工作原理。首先,我们必须了解 Unity 在运行其自己的核心引擎代码和运行我们在脚本中编写的代码时使用不同的方法。

Unity 在运行自己的核心 Unity 引擎代码时管理内存的方式称为手动内存管理。这意味着核心引擎代码必须明确说明如何使用内存。手动内存管理不使用垃圾收集,因此本文不会进一步讨论。

Unity 在运行我们的代码时管理内存的方式称为自动内存管理。这意味着我们的代码不需要明确地告诉Unity如何详细地管理内存。 Unity 会帮我们处理这个问题。Unity 中的自动内存管理是这样工作的:

  1. Unity可以访问两个内存池:栈和堆(栈用于短期存储小块数据,堆用于长期存储和较大块)

  2. 创建变量时,Unity 会从栈或堆中请求一块内存。

  3. 只要变量在作用域内(我们的代码仍然可以访问),分配给它的内存就会保留在其中。我们说这个内存已经被分配了,我们将保存在栈内存中的变量描述为栈上的对象,将保存在堆内存中的变量描述为堆上的对象。

  4. 当变量超出范围时,不再需要该内存,并且可以将其返回到它来自的池中。当内存返回到其池中时,我们说内存已被释放。一旦它引用的变量超出范围,栈中的内存就会被释放。然而,此时堆中的内存并未被释放,并且仍处于已分配状态,即使它引用的变量超出了范围。

  5. 垃圾收集器识别并释放未使用的堆内存。垃圾收集器定期运行以清理堆。

3.栈的分配和释放

栈分配和释放既快速又简单。这是因为栈仅用于在短时间内存储小数据。分配和释放总是以可预测的顺序发生,并且具有可预测的大小。

栈的工作方式类似于栈数据类型:它是元素的简单集合,在本例中是内存块,其中只能按照严格的顺序添加和删除元素。这种简单性和严格性使得它如此之快:当变量存储在栈上时,它的内存只是从栈的"末尾"分配。当栈变量超出范围时,用于存储该变量的内存将立即返回到堆栈以供重用。

4.堆的分配

堆分配比栈分配复杂得多。这是因为堆可用于存储长期和短期数据以及许多不同类型和大小的数据。分配和释放并不总是以可预测的顺序发生,并且可能需要不同大小的内存块。

创建堆变量时,会发生以下步骤:

  1. Unity必须检查堆中是否有足够的可用内存。如果堆中有足够的可用内存,则会为变量分配内存。

  2. 如果堆中没有足够的可用内存,Unity 会触发垃圾收集器以尝试释放未使用的堆内存。这可能是一个缓慢的操作。如果堆中现在有足够的可用内存,则会为变量分配内存。

  3. 如果垃圾回收后堆中没有足够的可用内存,Unity 会增加堆中的内存量。这可能是一个缓慢的操作。然后分配变量的内存。

堆分配可能会很慢,尤其是在必须运行垃圾收集器并且必须扩展堆的情况下。

5.垃圾收集期间会发生什么?

当堆变量超出范围时,用于存储它的内存不会立即释放。未使用的堆内存仅在垃圾收集器运行时才被释放。

每次垃圾收集器运行时,都会发生以下步骤:

  1. 垃圾收集器检查堆上的每个对象。

  2. 垃圾收集器搜索所有当前对象引用,以确定堆上的对象是否仍在作用域内。

  3. 任何不再在范围内的对象都会被标记为删除。

  4. 标记的对象被删除,分配给它们的内存返回到堆。

垃圾收集可能是一项昂贵的操作。堆上的对象越多,它必须做的工作就越多,代码中的对象引用越多,它必须做的工作就越多。

6.垃圾收集何时发生?

三种情况可能导致垃圾收集器运行:

  1. 每当请求堆分配而无法使用堆中的可用内存来满足时,垃圾收集器就会运行。

  2. 垃圾收集器会不时自动运行(尽管频率因平台而异)。

3.垃圾收集器可以强制手动运行。

垃圾收集可能是一项频繁的操作。每当可用堆内存无法完成堆分配时,就会触发垃圾收集器,这意味着频繁的堆分配和释放可能会导致频繁的垃圾收集。

7.垃圾收集存在的问题

垃圾回收可能出现如下3种类型的问题:

1.垃圾收集可能占用较长的CPU时间。如果垃圾收集器在堆上有很多对象和对象引用,则检查所有这些对象的过程可能会很慢。这可能会导致我们的游戏卡顿或运行缓慢。

2.垃圾收集可能发生在性能尖峰期。如果 CPU 已经在游戏的性能关键部分中努力工作,那么即使垃圾收集带来的少量额外开销也会导致帧速率下降和性能显着变化。

3.垃圾收集可能产生较多的堆碎片。当从堆中分配内存时,根据必须存储的数据大小,从不同大小的块中的可用空间中获取内存。当这些内存块返回到堆时,堆可以分成许多由分配块分隔的小空闲块。这意味着虽然可用内存总量可能很高,但我们无法在不运行垃圾收集器或扩展堆的情况下分配大内存块,因为现有块都不够大。堆碎片有两个后果。第一个是我们的游戏的内存使用量将高于所需的内存使用量,第二个是垃圾收集器将更频繁地运行。

8.栈和堆上分配了什么?

在 Unity 中,值类型的局部变量在栈上分配,其他所有变量都在堆上分配。以下代码堆栈分配的示例,因为变量 localInt 既是本地变量又是值类型的。为该变量分配的内存将在该函数完成运行后立即从栈中释放。

cs 复制代码
void ExampleFunction()
{
    int localInt = 5;
}

以下代码是堆分配的示例,因为变量 localList 是本地变量,但是引用类型的。当垃圾收集器运行时,为此变量分配的内存将被释放。

cs 复制代码
void ExampleFunction()
{
    List localList = new List();
}

9.减少垃圾收集的影响

从控制垃圾收集器的角度,可通过三种方式减少垃圾收集对游戏的影响:

  1. 我们可以减少垃圾收集器运行的时间。

  2. 我们可以降低垃圾收集器运行的频率。

  3. 我们可以故意触发垃圾收集器,以便它在性能不关键的时间运行,例如在加载屏幕期间。

从代码的角度,有三种策略可以帮助我们:

  1. 组织代码,减少堆分配和对象引用。堆上的对象越少,要检查的引用越少,这意味着触发垃圾收集时,运行时间会更少。

  2. 减少堆分配和释放的频率,特别是在性能关键时刻。更少的分配和释放意味着触发垃圾收集的机会更少。这也降低了堆碎片的风险。

  3. 安排垃圾收集的时间,让其可预测且方便的时间发生。这是一种更困难且不太可靠的方法,但当用作整体内存管理策略的一部分时可以减少垃圾收集的影响。

相关推荐
彭亚川Allen4 小时前
优化了2年的性能,没想到最后被数据库连接池坑了一把
数据库·后端·性能优化
MClink4 小时前
Go怎么做性能优化工具篇之pprof
开发语言·性能优化·golang
m0_748251725 小时前
Android webview 打开本地H5项目(Cocos游戏以及Unity游戏)
android·游戏·unity
benben0445 小时前
Unity3D仿星露谷物语开发7之事件创建动画
unity·游戏引擎
京东零售技术6 小时前
Taro小程序开发性能优化实践
性能优化·taro
林枫依依8 小时前
Unity2021.3.16f1可以正常打开,但是Unity2017.3.0f3却常常打开闪退或者Unity2017编辑器运行起来就闪退掉
unity
理想不理想v8 小时前
wepack如何进行性能优化
性能优化
异次元的归来20 小时前
Unity DOTS中的share component
unity·游戏引擎
fantasy_arch1 天前
CPU性能优化-磁盘空间和解析时间
网络·性能优化
向宇it1 天前
【从零开始入门unity游戏开发之——C#篇25】C#面向对象动态多态——virtual、override 和 base 关键字、抽象类和抽象方法
java·开发语言·unity·c#·游戏引擎