Unity 的开源内存分析工具 MemoryProfiler 非常有用,可以提供所有由 Unity 分配的 C++ 对象的内存信息,在该工具内被称为 NativeUnityEngineObject
(Native-only Mode)。当 C# 脚本经由 il2cpp 编译为 C++ 时,此工具可以提供额外的所有 C# 对象的信息,在该工具内被称为 ManagedObject
(Full Mode)。
本文简单地描述了该工具的工作机制,并探讨了一下基于该工具的一些可能的改进。
这个工具中所能提供的所有的内存数据均来源于一个 Unity API:
UnityEditor.MemoryProfiler.MemorySnapshot.RequestNewSnapshot();
通过调用这个函数,我们可以向一个运行着的 Unity 程序请求一个新的内存快照。如果是运行于编辑器内的程序,该请求同步地返回上面说到的 Native-only Mode 数据;如果是运行于 iOS 上的基于 il2cpp 的应用,该请求异步地返回上面说到的 Full Mode 数据。
刚收到的快照存在于下面这个紧凑数据对象里:
public class PackedMemorySnapshot { public Connection[] connections { get; } public PackedGCHandle[] gcHandles { get; } public MemorySection[] managedHeapSections { get; } public PackedNativeUnityEngineObject[] nativeObjects { get; } public PackedNativeType[] nativeTypes { get; } public TypeDescription[] typeDescriptions { get; } public VirtualMachineInformation virtualMachineInformation { get; } }
收到这个紧凑数据对象后,MemoryProfiler 做了一些展开的工作,得到下面这个展开后的对象,内含完整的信息和交叉的引用:
public class CrawledMemorySnapshot { public NativeUnityEngineObject[] nativeObjects; public GCHandle[] gcHandles; public ManagedObject[] managedObjects; public StaticFields[] staticFields; //contains concatenation of nativeObjects, gchandles, managedobjects and staticfields public ThingInMemory[] allObjects; public MemorySection[] managedHeap; public TypeDescription[] typeDescriptions; public PackedNativeType[] nativeTypes; public VirtualMachineInformation virtualMachineInformation; }
这个过程中,最重要的是:所有的内存对象被展开到 ThingInMemory[] allObjects 这个多态数组里。有了所有的对象及它们间的引用关系,我们就可以做进一步的分类,调查和分析了。
在上面的展开后的数据对象里,前四项值得分别说明一下:
NativeUnityEngineObject[] nativeObjects
这是前面提到过的所有的 C++ 对象。我们无法看到这些对象的实际内容,但是可以看到下面这些信息:
ManagedObject[] managedObjects
这是所有的 C# 对象。
GCHandle[] gcHandles
用于 C#/C++ 对象的交叉生命期管理
StaticFields[] staticFields
则是所有的静态变量 (C#)
除了这些实例对象,还有 C# 堆的数据和其他一些类型信息,这里就不多说了。
以下是一些针对 MemoryProfiler 的一些可以改进之处。
除了展示方式的改进之外,针对类型或实例的全文搜索,快照间的对比,分配时的细节诊断增强,都是非常有价值的潜在改进。在 ResourceTracker 中,可以看到我们基于开源的 Unity MemoryProfiler 做出的部分改进工作。
(完)
Gu Lu
[2017-01-25]
[注]