21.寄存器模式

21.性能相关-寄存器模式


21.1 知识点

什么是寄存器模式

寄存器模式是 ILRuntime2.0 版引入的专用于优化大规模数值计算的执行模式。该模式通过ILRuntime自己的编译器以及指令集(JIT Compiler)将原始DLL的微软中间语言(MSIL)指令集转换成一个自定义的基于寄存器的指令集,再进行解译执行。

在ILRuntime中使用寄存器模式可以有效提高性能,主要有以下特点:

  1. 数值计算性能会大幅提升,包括for循环等需要数值计算的控制流。
  2. 属性的调用开销,for循环里调用其他热更内方法的性能会有所提升。

注意:如果一个方法既没有数值计算,也没有频繁调用热更内的方法或者访问属性,只是调用系统或Unity API,那么不会产生任何优化,甚至一些情况下性能还会低于传统模式。

开启寄存器模式

开启寄存器模式概述

全局开启

在初始化appdomain时,调用其有参构造new AppDomain(ILRuntimeJITFlags.JITOnDemand)

  • None:不启用寄存器模式。
  • JITOnDemand:按需即时编译(JIT)模式,使用该模式在默认的情况下会按照原始的方式运行,当该方法被反复执行时,会被标记为需要被JIT,并在后台线程完成JIT编译后切换到寄存器模式运行。
  • JITImmediately:立即JIT模式,使用该模式时,当方法被调用的瞬间即会被执行JIT编译,在第一次执行时即使用寄存器模式运行。JIT会在当前线程发生,因此如果方法过于复杂在第一次执行时可能会有较大的初始化时间。
  • NoJIT:禁用JIT模式,该方法在执行时会始终以传统方式执行。
  • ForceInline:强制内联模式,该模式只对方法的Attribute生效,标注该模式的方法在被调用时将会无视方法体内容大小,强制被内联。
指定开启

在指定的类和方法前加上[ILRuntimeJIT(寄存器模式)]特性,自己指定哪些类或方法将使用哪种寄存器模式。

注意:在热更工程中使用该特性,需要引用ILRuntime库,可以在项目工程文件中找到 Library\ScriptAssemblies\ILRuntime.dll 进行引用(将复制本地设置为false,避免生成时导出)。

全局开启:在ILRuntimeManager的启动函数中开启按需即时编译(JIT)模式

/// <summary>
/// 用于启动ILRuntime初始化方法
/// </summary>
public void StartILRuntime(UnityAction callBack = null)
{
    if (!isStart)
    {
        isStart = true;
        
        // 如果全局开启寄存器模式 一般都开启的是JITOnDemand
        appDomain = new AppDomain(ILRuntimeJITFlags.JITOnDemand);
        
        
        StartCoroutine(LoadHotUpdateInfo(callBack));
    }
}

指定开启:在热更工程创建测试类

using ILRuntime.Runtime;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace HotFix_Project
{
    [ILRuntimeJIT(ILRuntimeJITFlags.JITImmediately)]
    class Lesson21_Test
    {
        [ILRuntimeJIT(ILRuntimeJITFlags.JITOnDemand)]
        public void Test1()
        {

        }

        /// <summary>
        /// 该方法中没有相关的数值计算 只有api调用 那么这种时候 就不需要开启寄存器模式
        /// </summary>
        [ILRuntimeJIT(ILRuntimeJITFlags.NoJIT)]
        public void Test2()
        {

        }
    }
}

寄存器模式使用建议

推荐使用两种模式:

  1. 在初始化AppDomain时,将寄存器模式指定为JITOnDemand(按需即时编译(JIT)模式)。ILRuntime会自己判断运行模式,在大多数情况下能达到不错的性能平衡。

  2. 初始化AppDomain时,不指定寄存器模式,而是通过自己利用[ILRuntimeJIT(寄存器模式)]特性来指定精准控制想要优化处。

总结

寄存器模式可以帮助我们进行性能优化,主要是用于优化大量数值计算逻辑。我们可以选择全局开启或者精准控制来达到优化目的。

为了使用更方便,建议大家使用全局JITOnDemand模式或者通过特性自定义局部开启。


21.2 知识点代码

ILRuntimeManager

using ILRuntime.Mono.Cecil.Pdb;
using ILRuntime.Runtime.Enviorment;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Threading;
using ILRuntime.CLR.Method;
using ILRuntime.CLR.Utils;
using ILRuntime.Runtime;
using ILRuntime.Runtime.Intepreter;
using ILRuntime.Runtime.Stack;
using Lesson12_FatherNameSpace;
using Lesson13_FatherNameSpace;
using Lesson13_InheritAllNameSpace;
using Lesson13_InterfaceNameSpace;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Networking;

namespace BaseFramework
{
    public class ILRuntimeManager : BaseSingletonInMonoBehaviour<ILRuntimeManager>
    {
        public AppDomain appDomain;

        //用于存储加载出来的 两个文件的内存流对象
        private MemoryStream dllStream;
        private MemoryStream pdbStream;

        private bool isStart = false;


        private bool isDebug = true;
        
        /// <summary>
        /// 用于启动ILRuntime初始化方法
        /// </summary>
        public void StartILRuntime(UnityAction callBack = null)
        {
            if (!isStart)
            {
                isStart = true;
                
                // 如果全局开启寄存器模式 一般都开启的是JITOnDemand
                appDomain = new AppDomain(ILRuntimeJITFlags.JITOnDemand);
                
                
                StartCoroutine(LoadHotUpdateInfo(callBack));
            }
        }

        public void StopILRuntime()
        {
            //1.释放流
            if (dllStream != null)
                dllStream.Close();
            if (pdbStream != null)
                pdbStream.Close();
            dllStream = null;
            pdbStream = null;
            //2.清空appDomain
            appDomain = null;

            isStart = false;
        }

        /// <summary>
        /// 初始化ILRuntime相关的内容
        /// </summary>
        unsafe private void InitILRuntime()
        {
            //其他初始化
            
            //无参无返回值不需要委托注册
            //MyUnityDel1因为转成的是无参无返回值的Action,Action在热更新里可以直接使用,所以不需要进行委托的注册
            //委托转换器注册(要把自定义委托无参无返回值MyUnityDel1委托转换为Action,其他自定义委托可以看语法也转成Action或Func)
            appDomain.DelegateManager.RegisterDelegateConvertor<MyUnityDel1>((act) =>
            {
                return new MyUnityDel1(() =>
                {
                    ((System.Action)act)();
                });
            });

            //有参有返回值委托注册 因为是自己定的规则要自己手动进行委托注册
            appDomain.DelegateManager.RegisterFunctionDelegate<System.Int32, System.Int32, System.Int32>();
            
            //委托转换器注册(要把自定义委托有参有返回值MyUnityDel2委托转换为Func,其他自定义委托可以看语法也转成Action或Func)
            appDomain.DelegateManager.RegisterDelegateConvertor<MyUnityDel2>((act) =>
            {
                return new MyUnityDel2((i, j) =>
                {
                    return ((System.Func<System.Int32, System.Int32, System.Int32>)act)(i, j);
                });
            });

            
            //注册跨域继承的父类适配器
            appDomain.RegisterCrossBindingAdaptor((new Lesson12_FatherAdapter()));
            appDomain.RegisterCrossBindingAdaptor((new Lesson13_FatherAdapter()));
            appDomain.RegisterCrossBindingAdaptor((new Lesson13_InterfaceAdapter()));
            appDomain.RegisterCrossBindingAdaptor((new Lesson13_InheritAllAdapter()));
            
            
            //注册协程跨域适配器
            appDomain.RegisterCrossBindingAdaptor(new CoroutineAdapter());
            //注册异步跨域适配器
            appDomain.RegisterCrossBindingAdaptor(new IAsyncStateMachineClassInheritanceAdaptor());
            
            // 值类型绑定注册
            appDomain.RegisterValueTypeBinder(typeof(Vector3), new Vector3Binder());
            appDomain.RegisterValueTypeBinder(typeof(Vector2), new Vector2Binder());
            appDomain.RegisterValueTypeBinder(typeof(Quaternion), new QuaternionBinder());
            
            
            
            
            //CLR重定向内容 要写到CLR绑定之前
            //得到想要重定向类的Type
            System.Type debugType = typeof(Debug);
            //得到想要重定向方法的方法信息
            MethodInfo methodInfo = debugType.GetMethod("Log", new System.Type[] { typeof(object) });
            //进行CLR重定向
            appDomain.RegisterCLRMethodRedirection(methodInfo, MyLog);
            
            
            //注册Litjson
            LitJson.JsonMapper.RegisterILRuntimeCLRRedirection(appDomain);
            
            //注册CLR绑定信息
            ILRuntime.Runtime.Generated.CLRBindings.Initialize(appDomain);
            
            
            
            //初始化ILRuntime相关信息(目前只需要告诉ILRuntime主线程的线程ID,主要目的是能够在Unity的Profiler剖析器窗口中分析问题)
            appDomain.UnityMainThreadID = Thread.CurrentThread.ManagedThreadId;

            if (isDebug)
            {
                //启动调试服务
                appDomain.DebugService.StartDebugService(56000);
            }

        }

        
        
        
        // MyLog函数用于记录日志,并输出调用栈信息
        // 参数:
        //   __intp: ILRuntime解释器实例
        //   __esp: 栈指针
        //   __mStack: 托管堆栈
        //   __method: 当前方法
        //   isNewObj: 是否为新对象
        unsafe StackObject* MyLog(ILIntepreter __intp, StackObject* __esp, List<object> __mStack, CLRMethod __method, bool isNewObj)
        {
            // 获取当前应用程序域
            ILRuntime.Runtime.Enviorment.AppDomain __domain = __intp.AppDomain;
            // 指针的声明
            StackObject* ptr_of_this_method;
            // 为了方便减法运算,计算栈顶指针位置
            StackObject* __ret = ILIntepreter.Minus(__esp, 1);

            // 获取栈顶参数的指针
            ptr_of_this_method = ILIntepreter.Minus(__esp, 1);
            // 将栈顶参数转换为System.Object类型
            System.Object @message = (System.Object)typeof(System.Object).CheckCLRTypes(StackObject.ToObject(ptr_of_this_method, __domain, __mStack), (ILRuntime.CLR.Utils.Extensions.TypeFlags)0);
            // 释放栈顶参数的指针
            __intp.Free(ptr_of_this_method);

            // 获取调用栈信息 是我们自己添加的
            var stackTrace = __domain.DebugService.GetStackTrace(__intp);

            // 输出日志信息和调用栈信息
            UnityEngine.Debug.Log(@message + "\n" + stackTrace);

            return __ret;
        }

        
        
        
        
        
        
        
        
        /// <summary>
        /// 启动完毕并且初始化完毕后 想要执行的热更新的逻辑
        /// </summary>
        private void ILRuntimeLoadOverDoSomthing()
        {
        }

        /// <summary>
        /// 去异步加载我们的热更相关的dll和pdb文件
        /// </summary>
        /// <returns></returns>
        IEnumerator LoadHotUpdateInfo(UnityAction callBack)
        {
            //这部分知识点在 Unity网络开发基础当中有讲解
            //加载本地的DLL文件
#if UNITY_ANDROID
        UnityWebRequest reqDll = UnityWebRequest.Get(Application.streamingAssetsPath + "/HotFix_Project.dll");
#else
            UnityWebRequest reqDll =
                UnityWebRequest.Get("file:///" + Application.streamingAssetsPath + "/HotFix_Project.dll");
#endif
            yield return reqDll.SendWebRequest();
            //如果失败了
            if (reqDll.result != UnityWebRequest.Result.Success)
                print("加载DLL文件失败" + reqDll.responseCode + reqDll.result);
            //读取加载的DLL数据
            byte[] dll = reqDll.downloadHandler.data;
            reqDll.Dispose();

#if UNITY_ANDROID
        UnityWebRequest reqPdb = UnityWebRequest.Get(Application.streamingAssetsPath + "/HotFix_Project.pdb");
#else
            UnityWebRequest reqPdb =
                UnityWebRequest.Get("file:///" + Application.streamingAssetsPath + "/HotFix_Project.pdb");
#endif
            yield return reqPdb.SendWebRequest();
            //如果失败了
            if (reqPdb.result != UnityWebRequest.Result.Success)
                print("加载Pdb文件失败" + reqPdb.responseCode + reqPdb.result);
            //读取加载的DLL数据
            byte[] pdb = reqPdb.downloadHandler.data;
            reqPdb.Dispose();

            //3.将加载的数据以流的形式(文件流或者内存流对象)传递给AppDomain对象中的LoadAssembly方法
            //注意 这里使用流 不要用完就关 一定要等到热更相关内容不使用了 再关闭
            dllStream = new MemoryStream(dll);
            pdbStream = new MemoryStream(pdb);
            //将我们两个文件的内存流用于初始化 appDomain 我们之后就可以通过该对象来执行我们对应的热更代码了
            appDomain.LoadAssembly(dllStream, pdbStream, new PdbReaderProvider());

            InitILRuntime();

            if (isDebug)
            {
                StartCoroutine(WaitDebugger(callBack));
            }
            else
            {
                ILRuntimeLoadOverDoSomthing();

                //当ILRuntime启动完毕 想要在外部执行的内容
                callBack?.Invoke();
            }

        }

        IEnumerator WaitDebugger(UnityAction callBack)
        {
            print("等到调试器接入");
            //不停判断是否有调试器接入 如果false一直等待
            //直到为true 代表有调试器接入
            while (!appDomain.DebugService.IsDebuggerAttached)
            {
                yield return null;
            }
            
            print("调试器接入成功");
            yield return new WaitForSeconds(1f);
            
            ILRuntimeLoadOverDoSomthing();

            //当ILRuntime启动完毕 想要在外部执行的内容
            callBack?.Invoke();
        }
    }
}

Lesson21_Test

using ILRuntime.Runtime;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace HotFix_Project
{
    [ILRuntimeJIT(ILRuntimeJITFlags.JITImmediately)]
    class Lesson21_Test
    {
        [ILRuntimeJIT(ILRuntimeJITFlags.JITOnDemand)]
        public void Test1()
        {

        }

        /// <summary>
        /// 该方法中没有相关的数值计算 只有api调用 那么这种时候 就不需要开启寄存器模式
        /// </summary>
        [ILRuntimeJIT(ILRuntimeJITFlags.NoJIT)]
        public void Test2()
        {

        }
    }
}

21.3 练习题

寄存器模式对于我们来说有什么作用?我们应该如何开启寄存器模式?

  • 作用:
    1. 优化大规模数值计算的性能
    2. 优化属性调用开销、for循环开销、热更内方法相互调用性能开销
  • 开启方式:
    1. 全局开启
      appDomain = new AppDomain(ILRuntimeJITFlags.JITOnDemand);
      
    2. 指定开启
      在类或方法钱添加特性 [ILRuntimeJIT(ILRuntimeJITFlags.模式)]


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏