1. 程式人生 > >獲取C#中方法的執行時間及其程式碼注入

獲取C#中方法的執行時間及其程式碼注入

  在優化C#程式碼或對比某些API的效率時,通常需要測試某個方法的執行時間,可以通過DateTime來統計指定方法的執行時間,也可以使用名稱空間System.Diagnostics中封裝了高精度計時器QueryPerformanceCounter方法的Stopwatch類來統計指定方法的執行時間:

  1.使用DateTime方法:

DateTime dateTime = DateTime.Now;
MyFunc();
Console.WriteLine((DateTime.Now
- dateTime).TotalMilliseconds);

  2.使用Stopwatch方式:

Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
MyFunc();
stopwatch.Stop();
Console.WriteLine(stopwatch.ElapsedMilliseconds);
//本次MyFunc()方法的執行毫秒數
//重置計時器 stopwatch.Restart(); //此處可以使用stopwatch.Reset(); stopwatch.Start();組合代替
MyFunc();
stopwatch.Stop(); Console.WriteLine(stopwatch.ElapsedMilliseconds);
//本次MyFunc()方法的執行毫秒數

 

  以上兩種辦法都可以達到獲取方法執行時間的目的,但是在需要對整個專案中的方法都進行監測用時時,除了使用效能分析工具,我們還可以通過程式碼注入的方式給程式集中每一個方法加入計時器;

  通過名稱空間System.Reflection.Emit中的類可以動態的建立程式集、型別和成員,通常類庫Mono.Cecil可以動態讀取並修改已經生成的IL檔案,這種在不修改原始碼的情況下給程式集動態新增功能的技術稱為面向切面程式設計(AOP);

  這裡給出了一個注入使用Stopwatch來檢測方法執行時間的程式碼,這裡的Mono.Cecil類庫可以通過nuget進行安裝:

using System;
using System.IO;
using System.Linq;
using System.Diagnostics;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Collections.Generic;
static void Main(string[] args)
    {
        for (int i = 0; i < args.Length; i++)
        {
            FileStream fileStream = new FileStream(args[i], FileMode.Open);
            if (fileStream != null)
            {
                AssemblyDefinition assemblyDefinition = AssemblyDefinition.ReadAssembly(fileStream);
                ModuleDefinition moduleDefinition = assemblyDefinition.MainModule;
                Collection<TypeDefinition> typeDefinition = moduleDefinition.Types;
                foreach (TypeDefinition type in typeDefinition)
                {
                    if (type.IsClass)
                    {
                        foreach (MethodDefinition method in type.Methods)
                        {
                            if (method.IsPublic && !method.IsConstructor)
                            {
                                ILProcessor il = method.Body.GetILProcessor();
                                TypeReference stopWatchType = moduleDefinition.ImportReference(typeof(Stopwatch));
                                VariableDefinition variableDefinition = new VariableDefinition(stopWatchType);
                                method.Body.Variables.Add(variableDefinition);
                                Instruction first = method.Body.Instructions.First();
                                il.InsertBefore(first, il.Create(OpCodes.Newobj, moduleDefinition.ImportReference(typeof(Stopwatch).GetConstructor(new Type[] { }))));
                                il.InsertBefore(first, il.Create(OpCodes.Stloc_S, variableDefinition));
                                il.InsertBefore(first, il.Create(OpCodes.Ldloc_S, variableDefinition));
                                il.InsertBefore(first, il.Create(OpCodes.Callvirt, moduleDefinition.ImportReference(typeof(Stopwatch).GetMethod("Start"))));

                                Instruction @return = method.Body.Instructions.Last();
                                il.InsertBefore(@return, il.Create(OpCodes.Ldloc_S, variableDefinition));
                                il.InsertBefore(@return, il.Create(OpCodes.Callvirt, moduleDefinition.ImportReference(typeof(Stopwatch).GetMethod("Stop"))));

                                il.InsertBefore(@return, il.Create(OpCodes.Ldstr, $"{method.FullName} run time: "));
                                il.InsertBefore(@return, il.Create(OpCodes.Ldloc_S, variableDefinition));
                                il.InsertBefore(@return, il.Create(OpCodes.Callvirt, moduleDefinition.ImportReference(typeof(Stopwatch).GetMethod("get_ElapsedMilliseconds"))));
                                il.InsertBefore(@return, il.Create(OpCodes.Box, moduleDefinition.ImportReference(typeof(long))));
                                il.InsertBefore(@return, il.Create(OpCodes.Call, moduleDefinition.ImportReference(typeof(string).GetMethod("Concat", new Type[] { typeof(object), typeof(object) }))));
                                il.InsertBefore(@return, il.Create(OpCodes.Call, moduleDefinition.ImportReference(typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }))));
                            }
                        }
                    }
                }
                FileInfo fileInfo = new FileInfo(args[i]);
                string fileName = fileInfo.Name;
                int pointIndex = fileName.LastIndexOf('.');
                string frontName = fileName.Substring(0, pointIndex);
                string backName = fileName.Substring(pointIndex, fileName.Length - pointIndex);
                string writeFilePath = Path.Combine(fileInfo.Directory.FullName, frontName + "_inject" + backName);
                assemblyDefinition.Write(writeFilePath);
                Console.WriteLine($"Success! Output path: {writeFilePath}");
                fileStream.Dispose();
            }
        }
        Console.Read();
    }

 

  完整的專案傳到了Github上=>InjectionStopwatchCode

 


 如果您覺得閱讀本文對您有幫助,請點一下“推薦”按鈕,您的認可是我寫作的最大動力!

作者:Minotauros
出處:https://www.cnblogs.com/minotauros/

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連線,否則保留追究法律責任的權利。