我正在考虑使用StackFrame stackFrame = new StackFrame(1)
之类的东西来记录执行方法,但我不知道它的性能影响。堆栈是否跟踪了每个方法调用时构建的内容,因此性能不应该是一个问题,还是仅在被要求时才构建?您是否建议在性能非常重要的应用程序中反对它?如果是这样,这是否意味着我应该在发布时禁用它?
答案 0 :(得分:31)
编辑一些背景
我们有一个类似的功能,99%的时间被禁用;我们使用的方法如下:
public void DoSomething()
{
TraceCall(MethodBase.GetCurrentMethod().Name);
// Do Something
}
public void TraceCall(string methodName)
{
if (!loggingEnabled) { return; }
// Log...
}
TraceCall(MethodBase.GetCurrentMethod().Name)
这很简单,但无论是否启用了跟踪,我们都会遇到使用Reflection查找方法名称的性能损失。
我们的选择是要么在每个方法中需要更多代码(并且存在简单的错误或拒绝风险),要么在启用日志记录时切换到使用StackFrame
来确定调用方法 。
选项A:
public void DoSomething()
{
if (loggingEnabled)
{
TraceCall(MethodBase.GetCurrentMethod().Name);
}
// Do Something
}
public void TraceCall(string methodName)
{
if (!loggingEnabled) { return; }
// Log...
}
选项B:
public void DoSomething()
{
TraceCall();
// Do Something
}
public void TraceCall()
{
if (!loggingEnabled) { return; }
StackFrame stackFrame = new StackFrame(1);
// Log...
}
我们选择了选项B.当禁用日志记录时,它比选项A 提供了显着的性能改进,99%的时间 ,并且实现起来非常简单。
这是对迈克尔代码的更改,以显示此方法的成本/收益
using System;
using System.Diagnostics;
using System.Reflection;
namespace ConsoleApplication
{
class Program
{
static bool traceCalls;
static void Main(string[] args)
{
Stopwatch sw;
// warm up
for (int i = 0; i < 100000; i++)
{
TraceCall();
}
// call 100K times, tracing *disabled*, passing method name
sw = Stopwatch.StartNew();
traceCalls = false;
for (int i = 0; i < 100000; i++)
{
TraceCall(MethodBase.GetCurrentMethod());
}
sw.Stop();
Console.WriteLine("Tracing Disabled, passing Method Name: {0}ms"
, sw.ElapsedMilliseconds);
// call 100K times, tracing *enabled*, passing method name
sw = Stopwatch.StartNew();
traceCalls = true;
for (int i = 0; i < 100000; i++)
{
TraceCall(MethodBase.GetCurrentMethod());
}
sw.Stop();
Console.WriteLine("Tracing Enabled, passing Method Name: {0}ms"
, sw.ElapsedMilliseconds);
// call 100K times, tracing *disabled*, determining method name
sw = Stopwatch.StartNew();
traceCalls = false;
for (int i = 0; i < 100000; i++)
{
TraceCall();
}
Console.WriteLine("Tracing Disabled, looking up Method Name: {0}ms"
, sw.ElapsedMilliseconds);
// call 100K times, tracing *enabled*, determining method name
sw = Stopwatch.StartNew();
traceCalls = true;
for (int i = 0; i < 100000; i++)
{
TraceCall();
}
Console.WriteLine("Tracing Enabled, looking up Method Name: {0}ms"
, sw.ElapsedMilliseconds);
Console.ReadKey();
}
private static void TraceCall()
{
if (traceCalls)
{
StackFrame stackFrame = new StackFrame(1);
TraceCall(stackFrame.GetMethod().Name);
}
}
private static void TraceCall(MethodBase method)
{
if (traceCalls)
{
TraceCall(method.Name);
}
}
private static void TraceCall(string methodName)
{
// Write to log
}
}
}
结果:
Tracing Disabled, passing Method Name: 294ms
Tracing Enabled, passing Method Name: 298ms
Tracing Disabled, looking up Method Name: 0ms
Tracing Enabled, looking up Method Name: 1230ms
答案 1 :(得分:18)
快速而天真的测试表明,对于对性能敏感的代码,是的,您需要注意这一点:
不要生成100K帧:3ms
生成100K帧:1805ms
在我的机器上每个生成的帧约20微秒。不是很多,而是在大量迭代中存在可测量的差异。
请谈谈您以后的问题(“我应该在我的应用程序中禁用StackFrame生成吗?”),我建议您分析一下您的应用程序,执行我在此处完成的性能测试,并查看性能差异金额任何与你的工作量有关的事情。
using System;
using System.Diagnostics;
namespace ConsoleApplication
{
class Program
{
static bool generateFrame;
static void Main(string[] args)
{
Stopwatch sw;
// warm up
for (int i = 0; i < 100000; i++)
{
CallA();
}
// call 100K times; no stackframes
sw = Stopwatch.StartNew();
for (int i = 0; i < 100000; i++)
{
CallA();
}
sw.Stop();
Console.WriteLine("Don't generate 100K frames: {0}ms"
, sw.ElapsedMilliseconds);
// call 100K times; generate stackframes
generateFrame = true;
sw = Stopwatch.StartNew();
for (int i = 0; i < 100000; i++)
{
CallA();
}
Console.WriteLine("Generate 100K frames: {0}ms"
, sw.ElapsedMilliseconds);
Console.ReadKey();
}
private static void CallA()
{
CallB();
}
private static void CallB()
{
CallC();
}
private static void CallC()
{
if (generateFrame)
{
StackFrame stackFrame = new StackFrame(1);
}
}
}
}
答案 2 :(得分:17)
我知道这是一篇旧帖子,但万一有人遇到它,如果你的目标是.Net 4.5还有另一种选择
您可以使用CallerMemberName属性来标识调用方法名称。它比反射或StackFrame快得多。以下是迭代一百万次的快速测试结果。与反射相比,StackFrame非常慢,而新属性使它们看起来都像是静止不动。这是在IDE中运行的。
反思结果: 00:00:01.4098808
StackFrame结果 00:00:06.2002501
CallerMemberName属性结果: 00:00:00.0042708
完成
以下内容来自编译的exe: 反思结果: 00:00:01.2136738 StackFrame结果 00:00:03.6343924 CallerMemberName属性结果: 的 00:00:00.0000947 强> 完成
static void Main(string[] args)
{
Stopwatch sw = new Stopwatch();
sw.Stop();
Console.WriteLine("Reflection Result:");
sw.Start();
for (int i = 0; i < 1000000; i++)
{
//Using reflection to get the current method name.
PassedName(MethodBase.GetCurrentMethod().Name);
}
Console.WriteLine(sw.Elapsed);
Console.WriteLine("StackFrame Result");
sw.Restart();
for (int i = 0; i < 1000000; i++)
{
UsingStackFrame();
}
Console.WriteLine(sw.Elapsed);
Console.WriteLine("CallerMemberName attribute Result:");
sw.Restart();
for (int i = 0; i < 1000000; i++)
{
UsingCallerAttribute();
}
Console.WriteLine(sw.Elapsed);
sw.Stop();
Console.WriteLine("Done");
Console.Read();
}
static void PassedName(string name)
{
}
static void UsingStackFrame()
{
string name = new StackFrame(1).GetMethod().Name;
}
static void UsingCallerAttribute([CallerMemberName] string memberName = "")
{
}
答案 3 :(得分:5)
从MSDN文档中,似乎一直在创建StackFrame:
创建并推送StackFrame 每个函数调用的调用堆栈 在执行一个线程时做的。 堆栈帧始终包括 MethodBase信息,也可以选择 包括文件名,行号和 列号信息。
您调用的构造函数new StackFrame(1)
会执行此操作:
private void BuildStackFrame(int skipFrames, bool fNeedFileInfo)
{
StackFrameHelper sfh = new StackFrameHelper(fNeedFileInfo, null);
StackTrace.GetStackFramesInternal(sfh, 0, null);
int numberOfFrames = sfh.GetNumberOfFrames();
skipFrames += StackTrace.CalculateFramesToSkip(sfh, numberOfFrames);
if ((numberOfFrames - skipFrames) > 0)
{
this.method = sfh.GetMethodBase(skipFrames);
this.offset = sfh.GetOffset(skipFrames);
this.ILOffset = sfh.GetILOffset(skipFrames);
if (fNeedFileInfo)
{
this.strFileName = sfh.GetFilename(skipFrames);
this.iLineNumber = sfh.GetLineNumber(skipFrames);
this.iColumnNumber = sfh.GetColumnNumber(skipFrames);
}
}
}
GetStackFramesInternal
是一种外部方法。 CalculateFramesToSkip
有一个只运行一次的循环,因为你只指定了1帧。其他一切看起来都很快。
您是否尝试过测量创建100万个片段需要多长时间?
答案 4 :(得分:4)
我正在考虑使用类似StackFrame的StackFrame = new StackFrame(1)来记录执行方法
出于兴趣:为什么?如果您只想要当前的方法,那么
string methodName = System.Reflection.MethodBase.GetCurrentMethod().Name;
似乎更好。也许不是更高性能(我没有比较,但是Reflection显示GetCurrentMethod()不是简单地创建一个StackFrame而是做一些“魔术”),但是它的意图更清晰。
答案 5 :(得分:3)
我认为保罗·威廉姆斯的工作正在进行中。如果你深入研究StackFrameHelper
,你会发现fNeedFileInfo
实际上是性能杀手 - 特别是在调试模式下。如果性能很重要,请尝试将其设置为 false 。无论如何,你不会在发布模式中获得更多有用的信息。
如果您在此处传递 false ,那么在执行ToString()
时仍然会获得方法名称而不输出任何信息,只需移动堆栈指针,它就会非常快。
答案 6 :(得分:2)
我知道你的意思,但这个例子的结果是超调。即使关闭日志记录也执行GetCurrentMethod是一种浪费。 它应该是这样的:
if (loggingEnabled) TraceCall(MethodBase.GetCurrentMethod());
或者,如果您希望TraceCall始终执行:
TraceCall(loggingEnabled ? MethodBase.GetCurrentMethod() : null);