在優化C#代碼或對比某些API的效率時,通常需要測試某個方法的運行時間,可以通過DateTime來統計指定方法的執行時間,也可以使用命名空間System.Diagnostics中封裝了高精度計時器QueryPerformanceCounter方法的Stopwatch類來統計指定方法的執行時間: 1. ...
在優化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 aD = AssemblyDefinition.ReadAssembly(fileStream); ModuleDefinition mD = aD.MainModule; Collection<TypeDefinition> typeDefinition = mD.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 stT = mD.ImportReference(typeof(Stopwatch)); VariableDefinition stV = new VariableDefinition(stT); method.Body.Variables.Add(stV); Instruction first = method.Body.Instructions.First(); il.InsertBefore(first, il.Create(OpCodes.Newobj,
mD.ImportReference(typeof(Stopwatch).GetConstructor(new Type[] { })))); il.InsertBefore(first, il.Create(OpCodes.Stloc_S, stV)); il.InsertBefore(first, il.Create(OpCodes.Ldloc_S, stV)); il.InsertBefore(first, il.Create(OpCodes.Callvirt,
mD.ImportReference(typeof(Stopwatch).GetMethod("Start")))); Instruction @return = method.Body.Instructions.Last(); il.InsertBefore(@return, il.Create(OpCodes.Ldloc_S, stV)); il.InsertBefore(@return, il.Create(OpCodes.Callvirt,
mD.ImportReference(typeof(Stopwatch).GetMethod("Stop")))); il.InsertBefore(@return, il.Create(OpCodes.Ldstr, $"{method.FullName} run time: ")); il.InsertBefore(@return, il.Create(OpCodes.Ldloc_S, stV)); il.InsertBefore(@return, il.Create(OpCodes.Callvirt,
mD.ImportReference(typeof(Stopwatch).GetMethod("get_ElapsedMilliseconds")))); il.InsertBefore(@return, il.Create(OpCodes.Box, mD.ImportReference(typeof(long)))); il.InsertBefore(@return, il.Create(OpCodes.Call,
mD.ImportReference(typeof(string).GetMethod("Concat", new Type[] { typeof(object), typeof(object) })))); il.InsertBefore(@return, il.Create(OpCodes.Call,
mD.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); aD.Write(writeFilePath); Console.WriteLine($"Success! Output path: {writeFilePath}"); fileStream.Dispose(); } } Console.Read(); }
完整的項目傳到了Github上=>InjectionStopwatchCode,下載項目後,通過dotnet build命令即可編譯出可執行程式,將目標程式集文件拖入到該應用程式即可在程式集目錄導出註入代碼後的程式集文件,經過測試,包括方法擁有返回值和方法的參數列表中包含out和ref參數等情況都不會對運行結果產生影響;
示例:
using System; public class MyClass { public void MyFunc() { int num = 1; for (int i = 0; i < int.MaxValue; i++) { num++; } } } public class Program { public static void Main(string[] args) { MyClass myObj = new MyClass(); myObj.MyFunc(); Console.Read(); } }
原始IL代碼:
代碼註入後IL代碼:
代碼註入後運行結果:
如果您覺得閱讀本文對您有幫助,請點一下“推薦”按鈕,您的認可是我寫作的最大動力!
作者:Minotauros
出處:https://www.cnblogs.com/minotauros/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。