C# 更快的堆栈跟踪

标签 c# performance stack-trace

我需要能够获取到目前为止已调用的所有方法的列表。不幸的是我无法使用 CallerInformation属性,这样我就只剩下 StackTrace 了。

我知道 StackTrace 的性能不太好,所以我希望有其他替代方案可以做到这一点,并且性能不会那么沉重。我发现这篇文章( https://ayende.com/blog/3879/reducing-the-cost-of-getting-a-stack-trace )有一个如何加速堆栈跟踪的示例,但我不确定如何真正使用它来获取方法名称。这是我到目前为止所拥有的:

void Main()
{
   var result = getTheStackTrace.Invoke();
}

private Func<object> getTheStackTrace;

public void FastStack()
{
    var stackFrameHelperType = typeof(object).Assembly.GetType("System.Diagnostics.StackFrameHelper");
    var GetStackFramesInternal = Type.GetType("System.Diagnostics.StackTrace, mscorlib").GetMethod("GetStackFramesInternal", BindingFlags.Static | BindingFlags.NonPublic);


    var method = new DynamicMethod("GetStackTraceFast", typeof(object), new Type[0], typeof(StackTrace), true);

    var generator = method.GetILGenerator();
    generator.DeclareLocal(stackFrameHelperType);
    generator.Emit(OpCodes.Ldc_I4_0);
    generator.Emit(OpCodes.Ldnull);
    generator.Emit(OpCodes.Newobj, stackFrameHelperType.GetConstructor(new[] { typeof(bool), typeof(Thread) }));
    generator.Emit(OpCodes.Stloc_0);
    generator.Emit(OpCodes.Ldloc_0);
    generator.Emit(OpCodes.Ldc_I4_0);
    generator.Emit(OpCodes.Ldnull);
    generator.Emit(OpCodes.Call, GetStackFramesInternal);
    generator.Emit(OpCodes.Ldloc_0);
    generator.Emit(OpCodes.Ret);
    getTheStackTrace = (Func<object>)method.CreateDelegate(typeof(Func<object>));
}

如何从对 getTheStackTrace.Invoke() 的调用中获取方法的名称 我对如何获取信息有点困惑。任何帮助,将不胜感激。谢谢。

最佳答案

在现代版本的.Net(6.0 + Framework 4.7.2)上,我找不到提到的性能问题in the article 。我尝试设置 @SledgeHammer 推荐的反射并执行基准测试。我无法使用 StackTrace 检测到足够慢的性能或者对反射方法进行重大改进,以证明在现代应用程序中使用反射的合理性。

<表类=“s-表”> <标题> 方法 运行时 平均值 错误 标准偏差 <正文> 堆栈跟踪 .NET 6.0 9.064 我们 0.0567 我们 0.0443 我们 堆栈跟踪 .NET Framework 4.7.2 10.701 我们 0.1510 us 0.1339 us 反射(reflection) .NET 6.0 7.474 我们 0.0692 我们 0.0648 我们 反射(reflection) .NET Framework 4.7.2 7.947 我们 0.1514 我们 0.1744 我们
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Threading;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;

namespace MyBenchmarks
{
  [SimpleJob(RuntimeMoniker.Net472, baseline: true)]
  [SimpleJob(RuntimeMoniker.Net60)]
  public class StackTraceBenchmark
  {
    private static Func<int, IEnumerable<MethodBase>> _lambda = Create();

    public StackTraceBenchmark()
    {
    }

    [Benchmark]
    public void StackTrace() => MethodA(false);

    [Benchmark]
    public void Reflection() => MethodA(true);

    [MethodImpl(MethodImplOptions.NoInlining)]
    public void MethodA(bool newMethod)
    {
      MethodB(newMethod);
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private void MethodB(bool newMethod)
    {
      MethodC(newMethod);
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private void MethodC(bool newMethod)
    {
      MethodD(newMethod);
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private void MethodD(bool newMethod)
    {
      MethodE(newMethod);
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private void MethodE(bool newMethod)
    {
      MethodF(newMethod);
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private void MethodF(bool newMethod)
    {
      MethodG(newMethod);
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private void MethodG(bool newMethod)
    {
      MethodH(newMethod);
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private void MethodH(bool newMethod)
    {
      MethodI(newMethod);
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private void MethodI(bool newMethod)
    {
      MethodJ(newMethod);
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private void MethodJ(bool newMethod)
    {
      MethodK(newMethod);
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private void MethodK(bool newMethod)
    {
      MethodZZZ(newMethod);
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private void MethodZZZ(bool newMethod)
    {
      if (newMethod)
      {
        var result = _lambda(0).Take(5).ToList();
      }
      else
      {
        var stack = new StackTrace(false);
        var result = Enumerable.Range(0, stack.FrameCount)
          .Select(i => stack.GetFrame(i).GetMethod())
          .Take(5)
          .ToList();
      }
    }

    private static Func<int, IEnumerable<MethodBase>> Create()
    {
      var stackFrameHelperType = typeof(StackTrace).Assembly.GetType("System.Diagnostics.StackFrameHelper");
      var getStackFramesInternal = typeof(StackTrace).GetMethod("GetStackFramesInternal", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
      var rangeMethod = typeof(Enumerable).GetMethod("Range");
      var stackFrameHelperCount = stackFrameHelperType.GetMethod("GetNumberOfFrames");
      var selectMethod = typeof(Enumerable).GetMethods().Where(m => m.Name == "Select").First()
        .MakeGenericMethod(typeof(int), typeof(MethodBase));
      var getMethodBase = stackFrameHelperType.GetMethod("GetMethodBase");

      var skip = Expression.Parameter(typeof(int), "skip");
      var stackFrameHelperVar = Expression.Parameter(stackFrameHelperType, "stackFrameHelper");

      var iParam = Expression.Parameter(typeof(int), "i");
      var selectLambda = Expression.Lambda(Expression.Call(stackFrameHelperVar, getMethodBase, iParam), iParam);

      var block = Expression.Block(new[] { stackFrameHelperVar }
        , Expression.Assign(stackFrameHelperVar, Expression.New(stackFrameHelperType.GetConstructor(new[] { typeof(Thread) })
          , Expression.Constant(Thread.CurrentThread)))
        , Expression.Call(getStackFramesInternal, stackFrameHelperVar, skip, Expression.Constant(false), Expression.Constant(null, typeof(Exception)))
        , Expression.Call(selectMethod
          , Expression.Call(rangeMethod
            , skip
            , Expression.Subtract(Expression.Call(stackFrameHelperVar, stackFrameHelperCount), skip)
          )
          , selectLambda
        )
      );
      return Expression.Lambda<Func<int, IEnumerable<MethodBase>>>(block, skip).Compile();
    }
  }
}

关于C# 更快的堆栈跟踪,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46312372/

相关文章:

c# - 如何在针对任意区域加权的正方形中选择随机 float ?

c# - 将两个图像合并为一个新图像

c# - 具有 10 个元素的随机 boolean 数组,其中 10 个元素中有 3 个为真

SQL JOIN : is there a difference between USING, ON 或 WHERE?

c# - 如果为空则为空字符串

java - Java 8 中对 HashMap 哈希函数的更改

python - Python中dict.has_key和key in dict的效率差异

ruby-on-rails - 沉默 cucumber 堆栈跟踪

error-handling - Clojure 中的 Stacktrace 和错误单子(monad)

c# - 异常在 try/catch 上下文中丢失了部分堆栈跟踪