我发现它不适用新版本的unity

  Unity的GC是我们身边的一个持续的刺。我们不断地通过池化对象、限制使用语言特性和避免使用API来解决它。我们甚至在加载屏幕上调用GC.Collect,希望GC不会在游戏过程中运行。今天的文章更进一步,展示了如何完全禁用GC,这样它就没有机会运行了。我们还将看到如何在我们准备好再次使用它的时候把它重新打开。

  Unity 的 GC 是用 Unity 附带的 C 代码实现的。从 2017.3 开始,它在 Mac 安装中的位置如下:

/Applications/Unity/Unity.app/Contents/il2cpp/external/boehmgc

  在 misc.c 文件中,我们可以看到大约 1800 行以下的这些函数:

GC_API void GC_CALL GC_enable(void)
{
    DCL_LOCK_STATE;
 
    LOCK();
    GC_ASSERT(GC_dont_gc != 0); /* ensure no counter underflow */
    GC_dont_gc--;
    UNLOCK();
}
 
GC_API void GC_CALL GC_disable(void)
{
    DCL_LOCK_STATE;
    LOCK();
    GC_dont_gc++;
    UNLOCK();
}

  这些都是非常简单的功能。它们只是增加一个整数 (GC_dont_gc),作为是否启用 GC 的全局标志。如果为零,则启用 GC。如果它不为零,则 GC 被禁用。所以我们应该能够调用 GC_disable 来禁用 GC,然后 GC_enable 重新启用它。

  因此,让我们尝试使用 [DllImport] 属性从 C# 调用这些函数:

using System.Runtime.InteropServices;
 
public static class GcControl
{
	// Unity engine function to disable the GC
	[DllImport("__Internal")]
	public static extern void GC_disable();
 
	// Unity engine function to enable the GC
	[DllImport("__Internal")]
	public static extern void GC_enable();
}

  现在我们可以从应用程序的任何地方调用 GcControl.GC_disable() 或 GcControl.GC_enable() 来禁用和重新启用 GC。重要的是要记住,这些函数以类似于引用计数的方式递增和递减整数。因此,对 GC_disable 的调用应与对 GC_enable 的调用相匹配。

  现在让我们用一个显示 GC 运行次数的小应用程序和一个用于打开和关闭 GC 的按钮来尝试一下。每一帧,我们都会在 byte[] 中分配 100 KB 的托管内存并保留它,这样内存压力就会不断增加并导致 GC 运行。

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.UI;
 
class GCTestScript : MonoBehaviour
{
	// UI
	public Text gcCountText;
	public Text toggleButtonText;
 
	// GC status
	private bool isGcDisabled;
 
	// Holds allocated memory
	// Prevents GC from collecting it
	private List<byte[]> storage;
 
	void Awake()
	{
		storage = new List<byte[]>();
		toggleButtonText.text = "Disable GC";
	}
 
	void Update()
	{
		// Display how many times GC has run
		int gcCount = GC.CollectionCount(0);
		gcCountText.text = gcCount.ToString();
 
		// Add managed memory pressure
		storage.Add(new byte[100*1024]);
	}
 
	// Toggle GC on or off
	public void OnToggle()
	{
		isGcDisabled = !isGcDisabled;
		if (isGcDisabled)
		{
			GcControl.GC_disable();
			toggleButtonText.text = "Enable GC";
		}
		else
		{
			GcControl.GC_enable();
			toggleButtonText.text = "Disable GC";
		}
	}
 
	// Re-enable GC on exit
	void OnApplicationQuit()
	{
		if (isGcDisabled)
		{
			GcControl.GC_enable();
		}
	}
}

这是它的实际效果:

unity 关闭所有携程 怎么关掉unity的plastic_System


  单击按钮禁用 GC 确实会阻止 GC 收集计数上升。当 GC 开始尝试回收内存以为新分配的 byte[] 对象腾出空间时,单击按钮重新启用 GC 会恢复数字的上升。

  这适用于 Unity 编辑器和 Android 设备。不能保证它可以在 Unity 支持的每个平台上运行,但至少这两个平台可以运行。

  那么既然我们有了这个工具,我们应该如何使用它呢?要决定策略,重要的是要了解该工具的效果。对于我们大多数人来说,在禁用 GC 的情况下运行游戏是一个新领域。

  通常,在启用 GC 的情况下,当我们分配托管内存(例如使用 new Class())并且托管堆中没有足够的空间来存储该对象时,会发生两件事。首先,运行 GC 以收集任何垃圾,以便可以重用内存。其次,如果仍然没有足够的空间,则会扩展托管堆以为新对象腾出空间。

  禁用 GC 后,第一步将不会发生,并且不可能重用一个或多个垃圾对象的内存。相反,我们将直接进行第二步并扩展托管堆。这意味着随着我们分配越来越多的对象,内存使用量将继续增长。如果我们分配得足够多,我们可能会耗尽内存并面临诸如由于使用“虚拟内存”(例如在 Mac 上)或我们的应用程序被操作系统直接终止(例如在 Android 上)而导致性能下降等后果。禁用 GC 并不能让我们放弃所有的自我控制并根据需要分配多少。

  由于我们真的不希望这些事情发生,因此在禁用 GC 时不要使用太多内存至关重要。通常可以为此安排。例如,如果我们在加载游戏关卡后以 100 MB 的内存使用量开始,并且我们知道在关卡期间我们不会分配超过 100 MB 的内存,那么我们在任何可以使用的设备上都可以最多使用 200 MB。作为一种保护措施,我们始终可以定期查询内存使用情况,并在达到被认为对设备造成危险的阈值(例如 250 MB)时重新启用 GC。

  还值得注意的是,我们调用的这些 C 函数是 Unity 引擎内部的,而不是公共 API 的一部分。不能保证函数名称、签名或行为会在不同版本之间保持不变。但实际上,这些函数是 Unity 使用的现成 GC 的一部分,文件头中的最新日期是 2001 年,因此该技术很可能会继续工作,直到 Unity 最终取代 GC。这目前在“开发”下的路线图上,描述为“进行中,时间表很长或不确定”。

  尽管如此,这绝对是一种先进的技术,应该非常谨慎地使用它。为游戏的帧率关键部分禁用 GC 并在之后重新启用它(例如在加载屏幕上)可能是一个很大的好处,对于某些游戏来说是值得的,但应该非常小心地使用它。