在简单的Unity项目中,构建时可以轻松达到1000fps。但是,在这些类型的项目中,我倾向于遇到GC问题。我的问题是(请问这是不是一个愚蠢的问题),FPS是否会对GC产生不良影响。我背后的逻辑是=>脚本运行并分配内存->脚本每帧执行一次->因此,如果fps高,是否应该更快地将垃圾堆积成很大的块?
例如假设我们有一个简单的脚本,它创建一个列表,用数据填充它,然后在每个帧中销毁该列表。
答案 0 :(得分:1)
花费在垃圾收集上的时间在很大程度上取决于垃圾达到垃圾收集所需数量的频率,它可以表示为平均帧产生多少垃圾以及每秒运行多少帧的乘积
因此,是的,如果您确实无法减少每帧创建的垃圾数量,则可以通过每秒运行更少的帧来减少垃圾收集所花费的时间。
这就是advised to design your application to reduce the amount of garbage that is created的原因:
减少创建的垃圾量
让我们研究一些可以帮助我们减少数量的技术 代码产生的大量垃圾。
缓存
如果我们的代码反复调用导致堆分配的函数 然后丢弃结果,这会产生不必要的垃圾。 相反,我们应该存储对这些对象的引用并重用它们。 这种技术称为缓存。在下面的示例中,代码 每次调用都会导致堆分配。这是因为新 数组已创建。
void OnTriggerEnter(Collider other) { Renderer[] allRenderers = FindObjectsOfType<Renderer>(); ExampleFunction(allRenderers); }
以下代码仅导致一次堆分配,因为该数组为 创建并填充一次,然后缓存。缓存的数组可以是 一次又一次地重复使用,而不会产生更多的垃圾。
private Renderer[] allRenderers; void Start() { allRenderers = FindObjectsOfType<Renderer>(); } void OnTriggerEnter(Collider other) { ExampleFunction(allRenderers); }
不要分配经常调用的功能
如果我们必须在MonoBehaviour中分配堆内存,那是最糟糕的地方 我们可以在经常运行的函数中做到这一点。 Update()和 例如,LateUpdate()每帧调用一次,因此如果我们的代码 在这里产生垃圾,它将迅速累加。我们应该考虑 在可能的情况下在Start()或Awake()中缓存对对象的引用,或者 确保导致分配的代码仅在需要时运行。 让我们看一个非常简单的移动代码示例, 当事情改变时运行。在下面的代码中,一个导致 每次调用Update()时都会调用一个分配,从而创建 经常垃圾:
void Update() { ExampleGarbageGeneratingFunction(transform.position.x); }
通过一个简单的更改,我们现在确保分配函数为 仅当transform.position.x的值更改时调用。我们是 现在只在必要时进行堆分配,而不是在每个 单帧。
private float previousTransformPositionX; void Update() { float transformPositionX = transform.position.x; if (transformPositionX != previousTransformPositionX) { ExampleGarbageGeneratingFunction(transformPositionX); previousTransformPositionX = transformPositionX; } }
另一种减少Update()中产生的垃圾的技术是使用 一个计时器。这适用于我们有生成垃圾代码的情况 必须定期运行,但不一定每帧运行。在里面 在以下示例代码中,生成垃圾的函数运行一次 每帧:
void Update() { ExampleGarbageGeneratingFunction(); }
在下面的代码中,我们使用一个计时器来确保该功能 每秒生成一次垃圾运行。
private float timeSinceLastCalled; private float delay = 1f; void Update() { timeSinceLastCalled += Time.deltaTime; if (timeSinceLastCalled > delay) { ExampleGarbageGeneratingFunction(); timeSinceLastCalled = 0f; } }
对经常运行的代码进行这样的小的更改可以 大大减少了产生的垃圾量。
清除收藏集
创建新集合会导致在堆上进行分配。如果我们发现 我们在代码中多次创建了新集合,我们 应该缓存对集合的引用,并使用Clear()清空 它的内容,而不是反复调用new。
在下面的示例中,每次新的分配都会发生新的堆分配 使用。
void Update() { List myList = new List(); PopulateList(myList); }
在以下示例中,仅当 创建集合或必须在后面调整集合大小时 现场。这样可以大大减少垃圾的产生。
private List myList = new List(); void Update() { myList.Clear(); PopulateList(myList); }
对象池
即使我们减少脚本中的分配,我们仍然可能拥有 如果我们创建和销毁许多对象,则会产生垃圾回收问题 在运行时。对象池是一种可以减少分配的技术 和重分配通过重用对象而不是重复创建 并摧毁他们。对象池在游戏中被广泛使用,并且 最适合我们经常产卵和破坏的情况 类似物体例如,用枪射击子弹时。