Unity3D研究院自动注入代码统计每个函数的执行效率以及内存分配(九十八)

栏目: 后端 · 发布时间: 6年前

内容简介:以前用UWA和WeTest的工具来查看代码效率,他们都能提供我们代码每个函数的统计效率,然而并不需要我们做什么,所以我就在想如何实现一个类似他们的功能?Unity提供了Profiler.BeginSample();Profiler.EndSample();方法来统计代码效率,但是有两个问题1.只能统计单帧的效率

以前用UWA和WeTest的 工具 来查看代码效率,他们都能提供我们代码每个函数的统计效率,然而并不需要我们做什么,所以我就在想如何实现一个类似他们的功能?

Unity提供了Profiler.BeginSample();Profiler.EndSample();方法来统计代码效率,但是有两个问题

1.只能统计单帧的效率

2.需要手动给每个方法加上这两个方法

然而我想要的是统计每个函数一段时间的所有执行效率的统计,比如玩上游戏大概15分钟,将15分钟内每个方法调用的耗时效率做一个总和出一份报表,(这也是我看UWA和WeTest都有的功能)通过代码注入自动给每个函数的首行和尾行添加两个方法就可以了。

首先在 https://github.com/jbevain/cecil 将mono.cecil取出来,我使用的版本是0.9.6版本,因为我觉得旧版本会稳定一点。另外,我使用的是源代码,没有直接用mono.cecil的DLL。原因是我们项目别的地方使用到了cecil修改的一个版本,为了避免冲突所以使用源代码可以修改命名空间保证两个mono.cecil不会被相互影响。

如下图所示,将mono.cecil导入unity工程即可。

Unity3D研究院自动注入代码统计每个函数的执行效率以及内存分配(九十八)

接着我们就需要写入注入代码了,注入代码测试也可以分为两种。

1.编辑模式下测试,也就是不需要打包。

2.打包测试,这样需要在打包的时候自动将代码注入进去。

先来看看注入的代码

using Mono.Cecil;
using Mono.Cecil.Cil;
using System;
using System.IO;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEditor.Callbacks;
 
public class HookEditor 
{
    static List<string> assemblyPathss = new List<string>()
    {
        Application.dataPath+"/../Library/ScriptAssemblies/Assembly-CSharp.dll",
        Application.dataPath+"/../Library/ScriptAssemblies/Assembly-CSharp-firstpass.dll",         
 
    };
 
 
    [MenuItem("Hook/主动注入代码")]
    static void ReCompile()
    {
        AssemblyPostProcessorRun();
    }
 
 
    [MenuItem("Hook/输出结果")]
    static void HookUtilsMessage()
    {
        HookUtils.ToMessage();
    }
 
    [PostProcessScene]//打包的时候会自动调用下面方法注入代码
    static void AssemblyPostProcessorRun()
    {
        try
        {
            Debug.Log("AssemblyPostProcessor running");
            EditorApplication.LockReloadAssemblies();
            DefaultAssemblyResolver assemblyResolver = new DefaultAssemblyResolver();
 
            foreach (System.Reflection.Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                assemblyResolver.AddSearchDirectory(Path.GetDirectoryName(assembly.Location));
            }
 
            assemblyResolver.AddSearchDirectory(Path.GetDirectoryName(EditorApplication.applicationPath) + "/Data/Managed");
 
            ReaderParameters readerParameters = new ReaderParameters();
            readerParameters.AssemblyResolver = assemblyResolver;
 
            WriterParameters writerParameters = new WriterParameters();
 
 
            foreach (String assemblyPath in assemblyPathss)
            {
                readerParameters.ReadSymbols = true;
                readerParameters.SymbolReaderProvider = new Mono.Cecil.Mdb.MdbReaderProvider();
                writerParameters.WriteSymbols = true;
                writerParameters.SymbolWriterProvider = new Mono.Cecil.Mdb.MdbWriterProvider();
              
 
                AssemblyDefinition assemblyDefinition = AssemblyDefinition.ReadAssembly(assemblyPath, readerParameters);
 
                Debug.Log("Processing " + Path.GetFileName(assemblyPath));
                if (HookEditor.ProcessAssembly(assemblyDefinition))
                {
                    Debug.Log("Writing to " + assemblyPath);
                    assemblyDefinition.Write(assemblyPath, writerParameters);
                    Debug.Log("Done writing");
                }
                else
                {
                    Debug.Log(Path.GetFileName(assemblyPath) + " didn't need to be processed");
                }
            }
        }
        catch (Exception e)
        {
            Debug.LogWarning(e);
        }
        EditorApplication.UnlockReloadAssemblies();
    }
 
    private static bool ProcessAssembly(AssemblyDefinition assemblyDefinition)
    {
        bool wasProcessed = false;
 
        foreach (ModuleDefinition moduleDefinition in assemblyDefinition.Modules)
        {
            foreach (TypeDefinition typeDefinition in moduleDefinition.Types)
            {
                if (typeDefinition.Name == typeof(HookUtils).Name) continue;
                //过滤抽象类
                if (typeDefinition.IsAbstract) continue;
                //过滤抽象方法
                if (typeDefinition.IsInterface) continue;
                foreach (MethodDefinition methodDefinition in typeDefinition.Methods)
                {
                    //过滤构造函数
                    if(methodDefinition.Name == ".ctor")continue;
                    if (methodDefinition.Name == ".cctor") continue;
                    //过滤抽象方法、虚函数、get set 方法
                    if (methodDefinition.IsAbstract) continue;
                    if (methodDefinition.IsVirtual) continue;
                    if (methodDefinition.IsGetter) continue;
                    if (methodDefinition.IsSetter) continue;
                    //如果注入代码失败,可以打开下面的输出看看卡在了那个方法上。
                    //Debug.Log(methodDefinition.Name + "======= " + typeDefinition.Name + "======= " +typeDefinition.BaseType.GenericParameters +" ===== "+ moduleDefinition.Name);
                    MethodReference logMethodReference = moduleDefinition.Import(typeof(HookUtils).GetMethod("Begin", new Type[] { typeof(string) }));
                    MethodReference logMethodReference1 = moduleDefinition.Import(typeof(HookUtils).GetMethod("End", new Type[] { typeof(string) }));
 
                    ILProcessor ilProcessor = methodDefinition.Body.GetILProcessor();
 
                    Instruction first = methodDefinition.Body.Instructions[0];
                    ilProcessor.InsertBefore(first, Instruction.Create(OpCodes.Ldstr, typeDefinition.FullName + "." + methodDefinition.Name));
                    ilProcessor.InsertBefore(first, Instruction.Create(OpCodes.Call, logMethodReference));
 
                    Instruction last = methodDefinition.Body.Instructions[methodDefinition.Body.Instructions.Count - 1];
                    ilProcessor.InsertBefore(last, Instruction.Create(OpCodes.Ldstr,typeDefinition.FullName + "." + methodDefinition.Name));
                    ilProcessor.InsertBefore(last, Instruction.Create(OpCodes.Call, logMethodReference1));
                    wasProcessed = true;
                }
            }
        }
 
        return wasProcessed;
    }
}

注意这个标签,[PostProcessScene] 如果是正式打包会自动进入该标签下的方法,这样每次打包代码就可以自动注入进去了。

前面我们也提到了注入代码就是在每个方法的首部和尾部自动注入两个函数。如下图所示,注入代码后反编译DLL能看到首行和尾行的代码已经注入进去了。

Unity3D研究院自动注入代码统计每个函数的执行效率以及内存分配(九十八)

代码注入成功以后就可以统计效率了。Begin()的时候取当前的Time.realtimeSinceStartup时间和Profiler.GetTotalAllocatedMemoryLong()内存,然后在End()的时候在取当前的Time.realtimeSinceStartup时间和Profiler.GetTotalAllocatedMemoryLong()内存减去Begin()中之前就记录的值就能统计到每个函数的执行效率和内存分配了,最后将数据生成报表就可以方便查看了。

这个方法不仅编辑器下可以用,同样可以支持真机IL2CPP和mono我已经测试通过,并且已经用此法优化项目效率啦。最后欢迎大家一起讨论,如有过意见或者建议欢迎在下面给我留言。

参考:http://www.codersblock.org/blog//2014/06/integrating-monocecil-with-unity.html

雨松MOMO提醒您:亲,如果您觉得本文不错,快快将这篇文章分享出去吧 。另外请点击网站顶部彩色广告或者捐赠支持本站发展,谢谢!

最后编辑:

作者:雨松MOMO

专注移动互联网,Unity3D游戏开发

站内专栏 QQ交谈 腾讯微博 新浪微博

捐 赠 如果您愿意花20块钱请我喝一杯咖啡的话,请用手机扫描二维码即可通过支付宝直接向我捐款哦。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

人类2.0

人类2.0

皮埃罗∙斯加鲁菲(Piero Scaruffi) / 闫景立、牛金霞 / 中信出版集团股份有限公司 / 2017-2-1 / CNY 68.00

《人类2.0:在硅谷探索科技未来》从在众多新技术中选择了他认为最有潜力塑造科技乃至人类未来的新技术进行详述,其中涉及大数据、物联网、人工智能、纳米科技、虚拟现实、生物技术、社交媒体、区块链、太空探索和3D打印。皮埃罗用一名硅谷工程师的严谨和一名历史文化学者的哲学视角,不仅在书中勾勒出这些新技术的未来演变方向和面貌,还对它们对社会和人性的影响进行了深入思考。 为了补充和佐证其观点,《人类2.0......一起来看看 《人类2.0》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具