一,问题


在之前的手游项目中,内存使用过多,都开始崩溃了,所以得做iOS内存统计。内存统计有好几种方法:XCode内存使用统计、UnityInternalProfile内存统计,Mono内存统计等方法。


二、测试研究


但是XCode统计需要连手机,UnityInternalProfile的内存统计值与XCode内存统计值差距又太大,崩溃时的内存值跟谁有关系?如何在手机上自己显示内存总量?后面就自己琢磨怎样实现一个适合的内存统计功能。


研究了下UnityInternalProfile,发现它拿的是mach_base_task_info里的resident_size(物理内存占用)。

 

如何让手游内存占用更小?从内存消耗iOS实时统计开始_ide

然后我做了个测试,每几帧分配使用一定大小的内存,然后打印出xcode统计的内存和resident_size。

 

如何让手游内存占用更小?从内存消耗iOS实时统计开始_ide_02

横坐标是时间,纵坐标是内存。


resident_size值的增长随着内存增长,但增长到一定程度就不怎么变了,当时猜测可能是被压缩了,查资料(MacOS有使用内存压缩技术)和代码,发现iOS还有task_vm_info 这个结构体,里面刚好有compress这项。

 

如何让手游内存占用更小?从内存消耗iOS实时统计开始_xml_03

然后增加compress这项数值的输出,重新测试!

 

如何让手游内存占用更小?从内存消耗iOS实时统计开始_ide_04

在内存使用持续增加过程中,​​​​当resident_size(物理内存)不再增加时,compress这项线性增长。


三、推论


由图可以看出:实际内存使用 = resident + compress。由此可以认为iOS通过压缩内存来减少内存占用。


并且在测试过程中,发现当实际使用内存达到系统物理内存一半时,系统会不断发送memorywarning的警告,达到60%时就会Q掉App。


四、应用


现在只需要实时拿到task_vm_info里的resident 和 compress 就可以统计App的实际内存的使用量了,对于Unity手机项目来说,需要写Native和C#代码,幸运的是,我已经帮你把代码写好了。


在XCodePostProcess::OnPostProcessBuild()里加入如下代码,会在Unity生成的XCode工程自动插入如下Native代码:

  XClass AppRender = new XClass(pathToBuiltProject + "/Classes/UnityAppController+Rendering.mm");         if( AppRender != null)         {             string TCode = "";             TCode +=   "#include <mach/mach_time.h>\n";             TCode +=   "#include <mach/mach.h>\n";             TCode +=   "#include <mach/mach_host.h>\n";             TCode +=   "#include <mach/task_info.h>\n";             TCode +=   "#include <mach/task.h>\n";             TCode +=   "static float GetTotalPhysicsMemory( )\n";             TCode +=   "{\n";             TCode +=   "    kern_return_t kr;\n";             TCode +=   "    mach_msg_type_number_t info_count = TASK_VM_INFO_COUNT;\n";             TCode +=   "    task_vm_info_data_t vm_info;\n";             TCode +=   "    kr = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t)&vm_info, &info_count);\n";             TCode +=   "    if (kr == KERN_SUCCESS) return (float)(vm_info.compressed  + vm_info.resident_size) / 1024.0 / 1024.0;\n";             TCode +=   "    return 0;\n";             TCode +=   "}\n";             TCode +=   "extern \"C\" float _Get_Profiler_TotalPhysicMemory(){return _fLockStepPhysicMemory;}\n";             TCode +=   "extern \"C\" void UnityRepaint()";
            AppRender.Replace("extern \"C\" void UnityRepaint()",TCode );         }



在UnityC#里加入以下托管代码,调用 Get_Profiler_TotalPhysicMemory()即可实时拿到内存使用值。

 

  #if ( UNITY_IPHONE && !UNITY_EDITOR )
    [DllImport("__Internal")]     static extern float _Get_Profiler_TotalPhysicMemory( );
    public static float Get_Profiler_TotalPhysicMemory( )     {         return _Get_Profiler_TotalPhysicMemory( );     } #endif

五、补充


由于系统有分页机制,即你申请使用1字节的内存,系统也有可能会给你一整页(16k大小的物理页),所以会导致这里的实际内存使用量(内存分页总和)与XCode内存统计(精确统计)不完成相等,但大致符合一定比例。


关于腾讯游戏学院专家团


如果你的游戏也富有想法充满创意,如果你的团队现在也遇到了一些开发瓶颈,那么欢迎你来联系我们。腾讯游戏学院聚集了腾讯及行业内策划、美术、程序等领域的游戏专家,我们将为全世界的创意游戏团队提供专业的技术指导和游戏调优建议,解决团队在开发过程中遇到的一系列问题。