unity webgl内存_Unity WebGL内存:Unity堆

时间:2024-03-25 21:28:49
unity webgl内存_Unity WebGL内存:Unity堆

unity webgl内存

在先前的Unity WebGL 博客文章中 ,我们解释了与其他平台相比,内存的工作方式有所不同。 我们告诉您Unity Heap应该尽可能小,并且我们还强调了浏览器内存中还有其他分配。 (In the previous Unity WebGL blog post, we explained that memory works differently compared to other platforms; we told you the Unity Heap should be as small as possible and we also emphasized that there are other allocations in browser memory.)

This time we would like to dive into what’s inside the Unity Heap so that when it comes to reducing its size, you can do that based on actual data, instead of having to find out a small value that works with your content by trial and error.

这次,我们希望深入了解Unity Heap的内部功能,以便在减小其大小时可以根据实际数据进行操作,而不必通过反复试验找出适合您的内容的较小值。

So, let’s see what’s the Unity Heap, what’s inside it, and how to profile it.

因此,让我们看一下什么是Unity Heap,其内部是什么以及如何对其进行概要分析。

什么是Unity Heap? (What is the Unity Heap?)

First of all, just a reminder that Unity Heap is not to be confused with the Browser Heap, in fact it’s a block of memory inside the Browser Heap. You will find more information in the previous blog post.

首先,仅提醒您不要将Unity Heap与Browser Heap混淆,实际上,这是Browser Heap内部的一块内存。 您可以在以前的 博客文章中 找到更多信息 。

In general terms, the Heap is an area of memory used for dynamic allocations in which native applications allocate via malloc/free or new/delete.

一般而言,堆是用于动态分配的内存区域,本机应用程序通过malloc / free或new / delete进行分配。

In Unity, we use our own Memory Allocators for better memory utilization as well as profiling and debugging purposes, but at the low-level, we still use malloc/free.

在Unity中,我们使用自己的内存分配器来提高内存利用率以及进行概要分析和调试,但是在低级别,我们仍然使用malloc / free。

In Unity WebGL, we refer to this memory, which contains all run-time Unity engine objects, as Unity Heap. Allocations in the Unity Heap are done using dlmalloc.

在Unity WebGL中,我们将此内存(包含所有运行时Unity引擎对象)称为Unity Heap。 Unity 堆中的 分配使用 dlmalloc 完成 。

On console platforms, the size of the heap is defined by the hardware specs and by how much memory is reserved by the OS, so applications must ensure they will not exceed the maximum amount of memory available at run-time.

在控制台平台上,堆的大小由硬件规格和操作系统保留的内存量定义,因此应用程序必须确保它们不会超过运行时可用的最大内存量。

Similarly on WebGL, we must define the size of the Unity Heap in advance (at build-time). What that means is that the Unity Heap can never shrink or grow after initialization.

同样在WebGL上,我们必须预先定义Unity Heap的大小(在构建时)。 这意味着 在初始化之后Unity Heap永远不会收缩或增长

Unity堆中有什么? (What is in the Unity Heap?)

On Unity WebGL, we can categorize allocations in the Unity Heap as follows:

在Unity WebGL上,我们可以对Unity Heap中的分配进行如下分类:

  • Static memory area

    静态存储区

  • Stack

    叠放

  • Dynamic Memory

    动态记忆体

  • Unallocated memory

    未分配的内存

The first blocks to be allocated are the stack and the area for all static objects. The size of the stack is usually 5mb and the size of the static area depends on the code compiled, which basically means the version of Unity and the project.

要分配的第一个块是所有静态对象的堆栈和区域。 堆栈的大小通常为5mb,静态区域的大小取决于所编译的代码,这基本上意味着Unity和项目的版本。

Once those are allocated, all remaining memory is available for Dynamic allocations that will occur at run-time.

分配完这些之后,所有剩余的内存可用于在运行时进行的动态分配。

As code is executed, the Dynamic area will start to occupy more space in the Unity Heap and if it grows too much, it will cause the Unity content to run out of memory.

在执行代码时,动态区域将开始在Unity Heap中占用更多空间,并且如果其增长过多,将导致Unity内容用完内存。

Over time, even though some objects will be freed and others allocated, the size of the Dynamic area will not shrink because there is no compacting mechanism. Instead, this will create gaps of free memory within the Dynamic area.

随着时间的流逝,即使将释放一些对象并分配其他对象,由于没有压缩机制,动态区域的大小也不会缩小。 相反,这将在动态区域内创建可用内存的间隙。

So, just be aware that there can be fragmentation.

因此,请注意可能会有碎片。

By this time, you might be wondering, what about Managed Memory?

到此时,您可能会想知道托管内存又如何呢?

Well…one (or more) of those run-time allocations in the Dynamic area is the Managed Heap where all your scripting objects will be created. So, the Managed Heap is inside the Unity Heap which is inside the Browser JS VM Heap. It does sound a bit complicated so think about it as the movie Inception, or The Matrix. A Heap inside another Heap, and so on…

好吧……“动态”区域中的一个(或多个)运行时分配是“托管堆”,将在其中创建所有脚本对象。 因此,托管堆位于Unity堆内部,而Unity堆位于Browser JS VM堆内部。 听起来确实有点复杂,所以可以将其视为电影《盗梦空间》或《黑客帝国》。 另一个堆中的一个堆,依此类推...

托管内存 (Managed Memory)

This is the memory used to store all scripting objects. It’s called managed because when an object is no longer referenced, its memory will be automatically reclaimed by the garbage collector (Boehm).

这是用于存储所有脚本对象的内存。 之所以称为“托管”,是因为当不再引用对象时,其内存将被垃圾收集器( Boehm ) 自动回收 。

The first important thing to understand here is that this memory is allocated from the Unity Heap (or from the OS on other platforms). Second, this memory is never returned to the system, therefore the Managed Heap can only grow. In fact, when an object is garbage collected, its memory is kept inside the Managed heap for future use.

在此首先要了解的是,此内存是从Unity Heap(或从其他平台上的OS)分配的。 其次,该内存永远不会返回给系统,因此 托管堆只能增长 。 实际上,当对象被垃圾回收时,其内存将保留在托管堆中,以备将来使用。

In Unity WebGL terms, when we say that the memory is not returned to the system we actually mean it is not returned to the pool of available memory blocks in the Unity Heap.

用Unity WebGL术语来说,当我们说内存没有返回到系统时,实际上是指内存没有返回到Unity Heap中的可用内存块池中。

It’s also important to mention that unlike the Unity Heap, which is a single block of memory, Boehm GC can allocate multiple buffers. In addition, each of them can be subdivided into smaller blocks if necessary. However, when a scripting object is created, a contiguous amount of memory large enough for the object is required. If that is not available among the free blocks managed by Boehm GC, a new one block will be taken from the Unity Heap.

还有一点很重要,要提到的是,与Unity Heap是单个内存块不同, Boehm GC可以分配多个缓冲区 。 此外,如有必要,可以将它们每个细分为较小的块。 但是,创建脚本对象时,需要足够大的对象连续内存。 如果Boehm GC管理的空闲块中没有该块,那么将从Unity Heap中获取一个新的块。

For more information about managed memory, read the manual.

有关托管内存的更多信息,请阅读 手册

当您用完托管内存后会发生什么? (What happens when you run out of Managed Memory?)

If Boehm GC fails to find a free block of memory for a new object and then fails to allocate from the Unity Heap, the Unity WebGL content will stop execution with an out-of-memory error suggesting to increase WebGLMemorySize.

如果Boehm GC无法为新对象找到可用的内存块,然后无法从Unity Heap进行分配,则Unity WebGL内容将停止执行,并出现内存不足错误,提示增加WebGLMemorySize。

System.GC.Collect()在WebGL上不起作用吗? (Does System.GC.Collect() not work on WebGL?)

Calling GC.Collect() has no effect on Unity WebGL. This is because we can’t garbage collect when the call stack is not empty. More details about this limitation can be found in the manual.

调用 GC.Collect() 对Unity WebGL没有影响。 这是因为当调用堆栈不为空时,我们无法进行垃圾回收。 有关此限制的更多详细信息,请参见 手册

At the moment, Unity WebGL will try to perform a little bit of garbage collection at the beginning of every frame. Then when a new scene is loaded, a full garbage collection is performed.

目前,Unity WebGL将尝试在每个帧的开始执行一些垃圾收集。 然后,当加载新场景时,将执行完整的垃圾回收。

System.GC.GetTotalMemory()是做什么的? (What does System.GC.GetTotalMemory() do ?)

On Unity WebGL, this method works identically as on other platforms, with the exception of the optional garbage collection: System.GC.GetTotalMemory() returns the total managed memory currently used, like our Profiler.GetMonoUsedSize(). To know the total size of the managed heap (used+free), use Profiler.GetMonoHeapSize() instead.

在Unity WebGL上,此方法与其他平台上的工作原理相同,但可选的垃圾回收除外: System.GC.GetTotalMemory() 返回当前使用的总托管内存,例如 Profiler.GetMonoUsedSize() 。 要了解托管堆的总大小(已用+可用),请改用 Profiler.GetMonoHeapSize()

如何为托管堆保留一定数量的内存? (How can I reserve a certain amount of memory for the Managed Heap ?)

If you ever used c++ std containers like string, vector, etc you should know that they might resize when appending/inserting new elements to the container. In games and other applications that need to keep memory usage under control this can be a problem, which can be mitigated by reserving memory upfront using the reserve method (for example: std::string::reserve, std::vector::reserve)

如果您曾经使用过c ++ std容器,例如字符串,向量等,则应该知道在向容器添加/插入新元素时它们可能会调整大小。 在需要控制内存使用量的游戏和其他应用程序中,这可能是个问题,可以通过使用reserve方法(例如: std :: string :: reservestd :: vector :: reserve) 预先预留内存来缓解此问题。 )

Unlike c++ std containers, in Unity there is no API to reserve memory upfront for the Managed Heap. Having said that, there might be a way around it.

与c ++ std容器不同,Unity中没有API可以为托管堆预先保留内存。 话虽如此,可能有解决的办法。

Assuming you know the maximum size of Managed Heap that your content will ever need, you could create an unused array of that size early on at run-time, then get the garbage collector to run. That way you would implicitly reserve that memory for the Managed Heap so that it will never need to grow!

假设您知道内容将需要的最大托管堆大小,则可以在运行时尽早创建该大小的未使用数组,然后使垃圾收集器运行。 这样,您将隐式地为托管堆保留该内存,这样它就不再需要增长了!

That sounds like a great idea! …except that, as we mentioned earlier, calling GC.Collect() does not do anything, and the only time a full GC pass is performed is upon scene-load. Well… that’s easily solved by starting your content with a preload scene with a single game object and one script to allocate the array:       

这听起来像一个好主意! …除了,正如我们前面提到的,调用GC.Collect()不会执行任何操作,并且只有在场景加载时才执行完整的GC传递。 好吧……这很容易解决,方法是从一个 带有一个游戏对象和一个分配数组的脚本 的 预加载 场景 开始您的内容 :        

1
2
3
4
5
void Prealloc(int sizeInMB)
{
byte[] buf = new byte[sizeInMB * 1024 * 1024];
buf = null;
}
1
2
3
4
5
void Prealloc ( int sizeInMB )
{
byte [ ] buf = new byte [ sizeInMB * 1024 * 1024 ] ;
buf = null ;
}

then load the first scene of your project. Now, the only thing you are missing is the size of Managed Heap you need to pre-allocate. To know that you could run your content from start to finish, then use Profiler.GetMonoHeapSize() to get the total reserved memory.

然后加载项目的第一个场景。 现在,您唯一缺少的是您需要预先分配的托管堆的大小。 要知道您可以从头到尾运行您的内容,请使用 Profiler.GetMonoHeapSize() 获取总的保留内存。

Just keep in mind that, with this approach, you will always pay the cost for the maximum usage of managed memory.

请记住,使用这种方法,您将始终为最大程度地使用托管内存付出代价。

选择Unity堆的大小 (Choosing the size of the Unity Heap)

After explaining what the Unity Heap is and making a necessary detour to scripting memory land, let’s go back to the original problem: what’s the best strategy for choosing the size of the Unity Heap?

在解释了什么是Unity Heap并绕过脚本编写内存区域后,让我们回到最初的问题:选择Unity Heap大小的最佳策略是什么?

The basic idea is that you need to determine the maximum amount of memory that your content will ever use, so that WebGLMemorySize can be set to a slightly larger value (rounded to the next multiple of 16).

基本思想是,您需要确定内容将使用的最大内存量,以便可以将WebGLMemorySize设置为稍大的值(四舍五入到16的下一个整数)。

In practice, what you have to do is test your content extensively on WebGL, while keeping track of the high-watermark total reserved memory. Then take that final high watermark value, add a few extra megs just to be safe and round it to 16mb.

实际上,您要做的是在WebGL上广泛测试您的内容,同时跟踪高水位标记的总保留内存。 然后取最后的高水印值,为安全起见添加一些额外的兆​​,并将其舍入到16mb。

Thankfully, the Profiler API provides a function to get the Total Reserved Memory:

幸运的是,探查器API提供了一个获取总保留内存的功能:

Profiler.GetTotalReservedMemory(), which corresponds to the ReservedTotal value in the Unity Profiler Window:

Profiler.GetTotalReservedMemory() ,它对应于Unity Profiler窗口中的ReservedTotal值:

However, there are two problems with this approach. The first one regards memory spikes: temporary allocations that are deallocated within the same frame, will not be taken into account in ReservedTotal. The second problem is that the Unity Profiler does not track all allocations.

但是,这种方法存在两个问题。 第一个是关于内存峰值的:在同一帧内重新分配的临时分配不会在ReservedTotal中考虑在内。 第二个问题是Unity Profiler不会跟踪所有分配。

未跟踪的分配 (Untracked Allocations)

The information you get from the profiler is very useful, however, there is something important you need to take into consideration: the profiler will tell you everything it knows but it won’t tell you what it does not! Obvious right?

从探查器获得的信息非常有用,但是,您需要考虑一些重要的事项: 探查器将告诉您所知道的一切,但不会告诉您不知道的一切! 很明显吧?

Unity keeps track of current memory used because memory is allocated via our internal MemoryManager::Allocate(), which will store additional information about name and size of the new memory allocation. However, there are other allocations that for some reason we don’t track and this is obviously a problem if you need to know exactly how much memory is actually used inside the Unity Heap.

Unity跟踪当前使用的内存,因为内存是通过内部MemoryManager :: Allocate()分配的,该内存将存储有关新内存分配的名称和大小的其他信息。 但是,由于某些原因,我们无法跟踪其他分配,如果您需要确切知道Unity Heap内部实际使用了多少内存,这显然是个问题。

This is usually due to internal sub-systems or 3rd party libraries allocating/freeing memory using malloc/free instead of our internal MemoryManager. In other cases, this is caused by memory allocations in user’s plugins, via malloc() in c/c++, or _malloc() in JavaScript  (e.g. StringReturnValueFunction in the example JS plugin), or by writing to file (which will actually write to memory).

这通常是由于内部子系统或第三方库使用malloc / free而不是内部MemoryManager分配/释放内存。 在其他情况下,这是由用户 插件中 的内存分配 ,通过c / c ++中的malloc()或JavaScript中的_malloc()(例如示例JS插件中的StringReturnValueFunction)或写入文件(实际上将写入文件)引起的记忆)。

To give you an idea about how much untracked memory you need to take into account, for a simple project in Unity 5.5 this is ~7mb. The good news is that we are working on fixing this problem wherever possible, so you can expect the total amount of Untracked memory to decrease in the future.

为了使您了解需要考虑多少未跟踪的内存,对于Unity 5.5中的一个简单项目,此内存约为7mb。 好消息是我们正在努力解决此问题,因此您可以期望将来未跟踪的内存总量会减少。

真的没有办法确切知道使用了多少内存吗? (Is there really no way to know EXACTLY how much memory is used?)

Actually, there is a way. If we look again a the first image of the Heap, it’s pretty straightforward to realize that Total Reserved Memory=static+stack+dynamic area sizes.

实际上,有一种方法。 如果我们再次看一下堆的第一张图,那么很容易意识到,总保留内存=静态+堆栈+动态区域大小。

Luckily, we can get the size of those areas of memory at run-time from the emscripten-generated variables and constants. Here is a simple plugin for that:

幸运的是,我们可以在运行时从脚本生成的变量和常量中获得这些内存区域的大小。 这是一个简单的插件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var MemoryStatsPlugin = {
GetTotalMemorySize: function() {
return TOTAL_MEMORY; // WebGLMemorySize in bytes
},
GetTotalStackSize: function() {
return TOTAL_STACK;
},
GetStaticMemorySize: function() {
return STATICTOP - STATIC_BASE;
},
GetDynamicMemorySize: function() {
if (typeof DYNAMICTOP !== 'undefined') {
    return DYNAMICTOP - DYNAMIC_BASE;
}
else {
// Unity 5.6+
return HEAP32[DYNAMICTOP_PTR >> 2] - DYNAMIC_BASE;
}
}
};
mergeInto(LibraryManager.library, MemoryStatsPlugin);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var MemoryStatsPlugin = {
GetTotalMemorySize : function ( ) {
return TOTAL_MEMORY ; // WebGLMemorySize in bytes
} ,
GetTotalStackSize : function ( ) {
return TOTAL_STACK ;
} ,
GetStaticMemorySize : function ( ) {
return STATICTOP - STATIC_BASE ;
} ,
GetDynamicMemorySize : function ( ) {
if ( typeof DYNAMICTOP !== 'undefined' ) {
     return DYNAMICTOP - DYNAMIC_BASE ;
}
else {
// Unity 5.6+
return HEAP32 [ DYNAMICTOP_PTR >> 2 ] - DYNAMIC_BASE ;
}
}
} ;
mergeInto ( LibraryManager . library , MemoryStatsPlugin ) ;

Just drop it into a new jslib file in your project and create the corresponding c-sharp bindings:

只需将其放入项目中的新jslib文件中,然后创建相应的c-sharp绑定即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using UnityEngine;
using System.Runtime.InteropServices;
public class WebGLMemoryStats : MonoBehaviour {
[DllImport("__Internal")]
private static extern uint GetTotalMemorySize();
[DllImport("__Internal")]
private static extern uint GetTotalStackSize();
[DllImport("__Internal")]
private static extern uint GetStaticMemorySize();
[DllImport("__Internal")]
private static extern uint GetDynamicMemorySize();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using UnityEngine ;
using System . Runtime . InteropServices ;
public class WebGLMemoryStats : MonoBehaviour {
[ DllImport ( "__Internal" ) ]
private static extern uint GetTotalMemorySize ( ) ;
[ DllImport ( "__Internal" ) ]
private static extern uint GetTotalStackSize ( ) ;
[ DllImport ( "__Internal" ) ]
private static extern uint GetStaticMemorySize ( ) ;
[ DllImport ( "__Internal" ) ]
private static extern uint GetDynamicMemorySize ( ) ;
}

An additional benefit of this method is that, unlike the profiler API, it will work in release builds too.

这种方法的另一个好处是,与探查器API不同, 它也可以在发行版中使用 。

Just be aware that the above jslib code relies on the emscripten-generated JS code, so for future versions of Unity, the plugin might need to be updated. Having said that, we might look at adding a Unity WebGL-specific API that would make such code unnecessary.

请注意,上面的jslib代码依赖于脚本生成的JS代码,因此对于Unity的未来版本,可能需要更新插件。 话虽如此,我们可能会考虑添加特定于Unity WebGL的API,这将使此类代码变得不必要。

如何配置Unity Heap? (How can I profile the Unity Heap?)

First of all by using the Unity Memory Profiler, which will provide you both an overview of memory and a more detailed view of all allocation categories.

首先,使用Unity Memory Profiler ,它将为您提供内存概述以及所有分配类别的更详细视图。

If you are specifically looking for memory leaks you can use the CPU Profiler GC Alloc column, which shows how much memory has been allocated in a particular frame:

如果您专门查找内存泄漏,则可以使用 CPU Profiler GC Alloc 列,该列显示在特定帧中分配了多少内存:

By the way, if you experienced problems in using the Profiler, there was a bug in 5.3, that is now fixed in 5.3.6 Patch 8.

顺便说一句,如果您在使用Profiler时遇到问题,则5.3中存在一个错误,该错误已在 5.3.6补丁8中 修复 。

If you want to dig further, you could try the new (Unity 5.3) Memory Profiler:

如果要进一步挖掘,可以尝试使用新的(Unity 5.3)Memory Profiler:

Which can be found at this repo: https://bitbucket.org/Unity-Technologies/memoryprofiler

可以在此仓库中找到: https//bitbucket.org/Unity-Technologies/memoryprofiler

This is a great tool but comes with caveats: it is il2cpp-specific (not a problem because that’s what we use on Unity WebGL) and it is still an unsupported preview feature so there might be issues here and there.

这是一个很棒的工具,但带有警告:它是il2cpp特有的(这不是问题,因为这是我们在Unity WebGL上使用的),并且它仍然是不受支持的预览功能,因此可能到处都是问题。

结论 (Conclusions)

Does this blog post answer all your remaining memory questions? This is a very broad and important topic that we could write much more about, but the really vital thing is that now you have the tools that allow you to know how much memory your Unity WebGL content requires. If you are interested in knowing more about profiling and optimizations there are a lot of available resources, including the Performance Optimization guides which we just published as well as the Unite Europe 2016 talk Optimizing Mobile Applications.

此博客文章是否回答您所有剩余的记忆问题? 这是一个非常广泛而重要的主题,我们可以写很多东西,但是真正重要的是,现在您有了可以让您知道Unity WebGL内容需要多少内存的工具。 如果您想了解更多有关性能分析和优化的信息,则有很多可用资源,包括 我们刚刚发布 的《 性能优化 指南》以及Unite Europe 2016的《 优化移动应用程序》

Please let us know if anything is not clear and remember that you can always reach us in the forums.

如果有任何不清楚的地方,请告诉我们,并记住您可以随时通过 论坛与 我们联系 。

翻译自: https://blogs.unity3d.com/2016/12/05/unity-webgl-memory-the-unity-heap/

unity webgl内存