包含此页的版本:
不含此页的版本:
Unity Web 中的内存约束可以限制您运行的内容的复杂性。
Web 内容在浏览器中运行。浏览器在其内存空间中分配应用程序运行内容所需的内存。 可用内存量因以下因素而异:
注意:有关与 Web 内存相关的安全风险的信息,请参阅安全和内存资源。
Unity Web 内容的以下区域需要浏览器分配大量内存。
Unity 使用内存堆来存储所有 Unity 引擎运行时对象。其中包括托管对象和本机对象、加载的资产、场景场景包含游戏的环境和菜单。将每个唯一的场景文件视为一个独特的关卡。在每个场景中,你放置你的环境、障碍物和装饰品,基本上是将你的游戏设计和构建成碎片。更多信息
请参阅术语表和着色器在 GPU 上运行的程序。更多信息
请参阅术语表.这就像 Unity Player 在任何其他平台上使用的内存一样。
Unity 堆是分配内存的连续块。Unity 支持自动调整堆大小以满足应用程序的需求。堆大小会随着应用程序的运行而扩展,并且最多可以扩展到 4 GB,具体取决于在播放器设置中设置的最大内存大小。Unity 将此内存堆创建为 Memory 对象。Memory 对象的 buffer 属性是一个可调整大小的 ArrayBuffer,它保存 WebAssembly 代码访问的内存的原始字节。
如果浏览器无法在地址空间中分配连续的内存块,则自动调整堆大小可能会导致应用程序崩溃。因此,保持 Unity 堆大小尽可能小非常重要。因此,在规划应用程序的内存使用时,请注意。如果您想测试 Unity 堆的大小,您可以使用分析器帮助您优化游戏的窗口。它显示了在游戏的各个领域花费了多少时间。例如,它可以报告渲染、动画制作或游戏逻辑所花费的时间百分比。更多信息
请参阅术语表以分析内存块的内容。
可以使用 Web 播放器设置中的内存增长模式选项来控制堆的初始大小和增长。默认选项配置为适用于所有桌面用例。但是,对于移动浏览器,您需要使用高级调整选项。对于移动浏览器,建议将初始内存大小配置为应用程序的典型堆使用情况。
当您创建 Unity Web 构建时,Unity 会生成一个.data文件。这包含应用程序启动所需的所有场景和资产。由于 Unity Web 无权访问真实文件系统,因此它会创建一个虚拟内存文件系统,浏览器会解压缩.data在这里提交。Emscripten 框架 (JavaScript) 在浏览器内存空间中分配此内存文件系统。当您的内容运行时,浏览器内存会保留未压缩的数据。为了保持较低的下载时间和内存使用率,请尽量将未压缩的数据保持在尽可能小的状态。
为了减少内存使用,你可以将资产数据打包到资产包中。AssetBundle 提供对资源下载的完全控制。您可以控制应用程序何时下载资产,以及运行时何时卸载资产。您可以卸载未使用的资产以释放内存。
AssetBundles直接下载到 Unity 堆中,因此不会导致浏览器进行额外分配。
启用数据缓存以自动缓存用户计算机上内容中的资产数据。这意味着您无需在以后的运行期间重新下载该数据。Unity Web 加载程序使用 IndexedDB API 和 Caching API 实现数据缓存。此选项允许您缓存通常对于浏览器缓存来说太大的文件。
要启用数据缓存选项,请转到 文件> 构建配置文件 > 播放器设置 > 发布设置。
垃圾回收是定位和释放未使用内存的过程。有关 Unity 垃圾回收工作原理的概述,请参阅自动内存管理。要调试垃圾回收过程,请使用 Unity Profiler。
由于 WebAssembly 的安全限制,不允许用户程序检查本机执行堆栈以防止可能的漏洞利用。这意味着在 Web 平台上,垃圾回收器只能在没有托管代码执行时运行(可能引用实时 C# 对象),并且只能在每个程序帧的末尾运行。与其他平台相比,这种差异会导致 Web 上的垃圾回收行为存在差异。
由于这些差异,每帧执行大量临时分配的代码(尤其是当这些分配可能表现出一系列线性大小增长时)可能会导致垃圾回收器出现临时二次内存增长压力。
例如,如果您有一个长时间运行的循环,则在 Web 上运行它时,以下代码可能会失败,因为垃圾回收器不会在 for 循环的迭代之间运行。在这种情况下,垃圾回收器无法释放中间字符串对象使用的内存,并且会耗尽 Unity 堆中的内存。
string hugeString = "";
for (int i = 0; i < 100000; i++)
{
hugeString += "foo";
}
在此示例中,长度hugeString循环结束时为 3 * 100000 = 300000 个字符。但是,该代码在生成最终字符串之前会生成十万个临时字符串。整个循环中分配的总内存为 3 * (1 + 2 + 3 + ... + 100000) = 3 * (100000 * 100001 / 2) = 15 GB。
要避免此问题,请使用StringBuilder构造大字符串。在本机平台上,垃圾回收器在循环执行时不断清理字符串的先前临时副本。因此,此代码总共不需要 15 GB 的 RAM 即可运行:
using System.Text;
var stringBuilder = new StringBuilder();
for (int i = 0; i < 10000; i++)
{
stringBuilder.Append("foo");
}
string hugeString = stringBuilder.ToString();
如果您知道字符串的最大长度,最佳做法是将Capacity的StringBuilder初始化时。
在 Web 平台上,垃圾回收器直到帧结束才回收临时字符串副本。结果,尝试分配 15 GB RAM 的代码内存不足。
以下代码显示了可能出现这种类型的临时二次内存压力的第二个示例:
byte[] data;
for (int i = 0; i < 100000; i++)
{
data = new byte[i];
// do something temporary with data[]
}
这里,代码临时分配 1 + 2 + 3 + ... + 100000 字节 = 5 GB 的字节,即使只保留最后 100 KB 数组。这会导致程序在 Web 平台上似乎耗尽内存,即使最终输出中只需要 100 KB。
若要限制这些类型的问题,请避免执行二次方增加的临时内存分配量的代码构造。相反,要么预先分配最终所需的数据大小,要么使用List<T>或类似的数据结构,执行几何级数增加的容量预留,从而减轻临时内存压力。
例如,使用List<T>容器,请考虑使用List<T>.ReserveCapacity()函数,如果您知道数据结构的最终大小,则可以预先分配所需的容量。同样,考虑使用List<T>.TrimExcess()函数,用于缩小以前容纳数兆字节内存的容器的大小。
注意:使用 C# 委托或事件(如Delegate,Action,Func,这些类在内部使用与上述类似的线性增长分配。避免使用这些类进行过多的每帧委托注册和取消注册,以最大程度地减少 Web 平台上垃圾回收器的临时内存压力。
使用NativeArray<T>用于临时分配。NativeArray<T>绕过垃圾回收器并减少 Unity 堆上的内存压力。有关NativeArray<T>,请参阅线程安全类型。
NativeArray<T>与使用垃圾回收器的内存管理相比,具有以下限制:
Dispose()以避免内存泄漏或使用 using 语句。 // Updating an element in NativeArray<MyStruct> requires a temporary
// copy
MyStruct temp = myNativeArray[i];
temp.memberVariable = 0;
myNativeArray[i] = temp;
// Incrementing an element in NativeArray<int> needs to use explicit
// assignment since intNativeArrayInt[0]++ won't work
intNativeArrayInt[0] = intNativeArrayInt[0]++;
以下示例演示如何使用NativeArray<T>对于 Web 上的临时分配:
using Unity.Collections;
for (int = 0; i < 10000; i++)
{
using(var data = new NativeArray<byte>(i, Allocator.Temp))
{
// Do something with data[] here
// data.Dispose() will be automatically called at the end of the using
// scope which immediately frees the memory
}
}