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

  在優化C#代碼或對比某些API的效率時,一般須要測試某個方法的運行時間,能夠經過DateTime來統計指定方法的執行時間,也可使用命名空間System.Diagnostics中封裝了高精度計時器QueryPerformanceCounter方法的Stopwatch類來統計指定方法的執行時間:git

  1.使用DateTime方法:github

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參數等狀況都不會對運行結果產生影響;優化

  示例:ui

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代碼:spa

  代碼注入後IL代碼:pwa

  代碼注入後運行結果:

 

 


 若是您以爲閱讀本文對您有幫助,請點一下「推薦」按鈕,您的承認是我寫做的最大動力!

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

本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,不然保留追究法律責任的權利。

相關文章
相關標籤/搜索