如何獲取C#中方法的執(zhí)行時間以及其代碼注入詳解
前言
在優(yōu)化C#代碼或?qū)Ρ饶承〢PI的效率時,通常需要測試某個方法的運行時間,可以通過DateTime來統(tǒng)計指定方法的執(zhí)行時間,也可以使用命名空間System.Diagnostics中封裝了高精度計時器QueryPerformanceCounter方法的Stopwatch類來統(tǒng)計指定方法的執(zhí)行時間:
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()方法的運行毫秒數(shù) //重置計時器 stopwatch.Restart(); //此處可以使用stopwatch.Reset(); stopwatch.Start();組合代替 MyFunc(); stopwatch.Stop(); Console.WriteLine(stopwatch.ElapsedMilliseconds); //本次MyFunc()方法的運行毫秒數(shù)
以上兩種辦法都可以達到獲取方法執(zhí)行時間的目的,但是在需要對整個項目中的方法都進行監(jiān)測用時時,除了使用性能分析工具,我們還可以通過代碼注入的方式給程序集中每一個方法加入計時器;
通過命名空間System.Reflection.Emit中的類可以動態(tài)的創(chuàng)建程序集、類型和成員,通常類庫Mono.Cecil可以動態(tài)讀取并修改已經(jīng)生成的IL文件,這種在不修改源代碼的情況下給程序集動態(tài)添加功能的技術(shù)稱為面向切面編程(AOP);
這里給出了一個注入使用Stopwatch來檢測方法執(zhí)行時間的代碼,這里的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命令即可編譯出可執(zhí)行程序,將目標(biāo)程序集文件拖入到該應(yīng)用程序即可在程序集目錄導(dǎo)出注入代碼后的程序集文件,經(jīng)過測試,包括方法擁有返回值和方法的參數(shù)列表中包含out和ref參數(shù)等情況都不會對運行結(jié)果產(chǎn)生影響;
示例:
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代碼:

代碼注入后運行結(jié)果:

總結(jié):
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
相關(guān)文章
C#操作EXCEL DataTable轉(zhuǎn)換的實例代碼
C#操作EXCEL DataTable轉(zhuǎn)換的實例代碼,需要的朋友可以參考一下2013-04-04

