Unity程序总内存优化记录

首先,上线各大渠道要看应用的总内存量。总内存包含了我们在Unity Profiler的Memory面板上看到的Reserved Total数字的值,再加上除Unity以外的内存。但是很多时候,这个总内存要比Unity Profiler多出来一个量级。以我们的项目为例,iOS总内存为408MB,Unity Reserved Total为279.4MB,多出来408-279.4=128.6MB。

天了噜!这些暗箱里的东西都是什么?我打算从完整项目开始做减法,分别测试一探究竟。

先上我测试用的代码,下面分别是iOS和Android的获取总内存的代码。

iOS的c代码:

//#import <mach/mach.h>
long getResidentMemory
{
    struct task_basic_info t_info;
    mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT;
    
    int r = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count);
    if (r == KERN_SUCCESS)
    {
        return t_info.resident_size;
    }
    else
    {
        return -1;
    }
}

Android的java代码:

package com.melody.memorytest;

import android.app.ActivityManager;
import android.content.Context;
import android.os.Debug;
import com.unity3d.player.UnityPlayer;

/**
 * Created by melody on 2017/1/17.
 */

public class MemoryUtil {
    /**
     * get the memory of process with certain pid.
     *
     * @param pid
     *            pid of process
     * @param context
     *            context of certain activity
     * @return memory usage of certain process
     */
    public static int getPidMemorySize(int pid,Context context) {
        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        int[] myMempid = new int[]{pid};
        Debug.MemoryInfo[] memoryInfo = am.getProcessMemoryInfo(myMempid);
        memoryInfo[0].getTotalSharedDirty();
        int memSize = memoryInfo[0].getTotalPss();
        return memSize;
    }
    public static int getUsedMemory(){
        int pid = android.os.Process.myPid();
        Context context = UnityPlayer.currentActivity;
        return getPidMemorySize(pid,context);
    }
}

c#代码:

using UnityEngine;
using System.Collections;
using System.Runtime.InteropServices;


public class ProfilerGUI : MonoBehaviour {
    [DllImport("__Internal")]
    public extern static long getResidentMemory();
    float interval = 1f;
    string totalMemory = "totalMemory:";

    GUIStyle style;
    void Start(){
        style = new GUIStyle();
        style.normal.textColor = Color.green;
        style.fontSize = 30;
        StartCoroutine(GetMemoryInterval());
    }
    IEnumerator GetMemoryInterval()
    {
        yield return new WaitForSeconds(interval);
#if UNITY_EDITOR
        totalMemory = "totalMemory:" + Random.Range(100f, 200f).ToString(".00");//print fake number in UnityEditor
#elif UNITY_IOS
        totalMemory = "totalMemory:" + ((float)getResidentMemory() / 1048576).ToString(".00");
#elif UNITY_ANDROID
        AndroidJavaClass jc = new AndroidJavaClass("com.melody.memorytest.MemoryUtil");
        totalMemory = "totalMemory:" + (float)jc.CallStatic<int>("getUsedMemory") / 1024;
#endif
        StartCoroutine(GetMemoryInterval());
    }
    Rect r = new Rect(0,Screen.height - 80 ,200,80);
    void OnGUI(){
        GUI.Label (r,totalMemory,style);
    }
}

然后分别测试的结果如下:

结论
游戏项目|完整 屏蔽oc的代码就是指替换UnityAppController.mm,UnityView.mm文件,使在objective-c层面的代码屏蔽
Xcode Run
内存值
带有sdk启动的代码|Development Build 104.81
屏蔽oc的代码|Development Build 86.14
Xcode Profile|Instrument
带有sdk启动的代码|Development Build 101
屏蔽oc的代码|Development Build 82.5
游戏项目|去掉了其他场景 内存减少了18.5MB,初步结论是各路sdk的native代码项就占用了18.5MB的app内存
Xcode Run
内存值
带有sdk启动的代码|Development Build
屏蔽oc的代码|Development Build
Xcode Profile|Instrument
内存值
带有sdk启动的代码|Development Build 101
屏蔽oc的代码|Development Build
游戏项目|去掉了其他场景|去掉了所有代码和引用库 内存减少了47.4MB,结论是il2cpp的代码的初始化内存占用了很大的一部分
如果可以用减法的话,47.4-18.5=28.9MB,这是il2cpp所占用的内存
Xcode Run
内存值
Development Build 55.2
Xcode Profile|Instrument
内存值
Development Build 53.6
游戏项目|去掉了其他场景|去掉了所有代码和引用库|去掉了StreamingAssets 无明显变化,结论是StreamingAssets在iOS下有绝对文件路径,所以不需要单独索引
而且项目的StreamingAssets文件数量不多
Xcode Profile|Instrument
内存值
Development Build 53.5
游戏项目|去掉了其他场景|去掉了所有代码和引用库|去掉了StreamingAssets|去掉了所有Resources文件夹 内存减少了20MB,结论是Unity在native层面进行的资源索引,这里和安卓的dalvik堆相似
Xcode Run
内存值
Development Build
Xcode Profile|Instrument
内存值
Development Build 35.4
无其他代码的项目
Xcode Run
内存值
Development Build 31.8
Xcode Profile|Instrument
内存值
Development Build 31
Xcode Profile|Instrument|Release
No Development Build 30

总结:iOS内存测试报表里的128MB内存,包含了:18MB的sdk相关oc代码,28.9MB的代码相关代码(猜测为il2cpp的初始化内存分配),20MB的Resources文件夹分配索引的代码。

在这个层面可优化的点不多。针对以上三项:

  1. sdk的代码层面的内存优化,我们接入的是MSDK,这里相信腾讯,问题先放置了。
  2. il2cpp的代码占用,只能通过减少总代码量来进行,及时清理无用代码,特别是第三方的库。
  3. Resources文件夹的内存:这个问题比较复杂,参考:https://unity3d.com/cn/learn/tutorials/topics/best-practices/guide-assetbundles-and-resources

总的来说就是,Resources文件夹下面的所有文件,无论使用与否都会被打包出来。Unity引擎会在打包的时候,判断项目文件夹下面的需要打包的资源文件。

这些资源将要被打包:

  • 所有Resources文件夹下的文件,及其引用到的所有资源。 例如我们把图集资源放到Resources文件夹外面,但是把引用到了这个图集的UIPrefab放到Resources文件夹里面,那么图集也会被打包。
  • 获取在BuildSetting面板里面Scenes In Build窗口,获取所有已激活场景文件,并且找到这些场景文件引用到的所有资源。
  • 所有的代码文件
  • 所有的Plugins/当前平台 的文件
  • StreamingAssets文件,这部分文件比较特殊,在iOS下有绝对文件路径,在Android下没有,具体可以参考手册。

而Unity引擎在程序启动时会对所有的资源文件弄一个映射,这个映射的内存就是多出来的这一块内存,内存大小和打包的所有资源文件的数量(非大小)成正比。在Android下同样也可以看见,随着增/减Resources文件夹的文件数量,dalvik堆的内存也随之增加/减少,也是这个原理。还有一份内存是可以在Unity Profiler/Memory/Detailed里面看见,显示在Assets/ResourceManager的内存。这部分内存也是常驻,并且也随打包的所有资源文件的数量(非大小)成正比。

关于Resources文件夹,有的Resources文件夹都不在Assets/Resources这里,而是随意放置,不能统一管理。而且有些也是无用资源,清理打包后的无用资源可以用这个插件 Build Report Tool:https://www.assetstore.unity3d.com/en/#!/content/8162

先总结到这里。。2017年的第一篇博客总算写出来了,Markdown还不熟悉很蛋疼啊。万事开头难,加油。

最近的文章

lua学习笔记

安装luaforwindows https://github.com/rjpcomputing/luaforwindows/releases,然后将lua添加进环境变量,并且使用SciTE就可以编辑并运行lua代码了。Lua在线教程 http://lua-users.org/wiki/TutorialDirectory其他的诸如手册和教程,也可以在开始菜单的Lua下面找到 Hello World print("Hello World") 分号可有可无 注释 --[[多行注释]...…

继续阅读
更早的文章

Hello World - Vno

写在前面该博客用Jekyll,和onevcat的vno主题搭建而成。希望能持续更新吧。。就这样,还有就是我要让懵逼的背景图动起来。动次大次。再次感谢onvcat同学提供的主题,让伸手速成党有了福利。( •̀ ω •́ )y 但是markdown一点也不好玩,需要一点时间熟悉,2017新的一年,加油吧。What’s thisVno Jekyll is a theme for Jekyll. It is a port of my Ghost theme vno, which is origin...…

继续阅读