I am considering using something like StackFrame stackFrame = new StackFrame(1)
to log the executing method, but I don't know about its performance implications. Is the stack trace something that is build anyway with each method call so performance should not be a concern or is it something that is only build when asked for it? Do you recommend against it in an application where performance is very important? If so, does that mean I should disable it for the release?
-
Will this logging always be enabled? What's the alternative approach? Michael's results are interesting, but the real question is "how does this approach stack up to X?" – STW Aug 28 '09 at 19:03
-
@Yoooder: good questions. I think the "X" we're comparing this to is logging without method information, or with statically-generated method information. "Should I disable it?": test in your application, and see if the performance differences are measurable or noticable with your workload. – Michael Petrotta Aug 28 '09 at 19:24
-
I asked this question to answer for myself whether I could have logging always enabled. The alternative would be to disable it. Yes, what other approaches are there? All I know of is: StackTrace, StackFrame, MethodBase.GetCurrentMethod and passing a method as a delegate. – Aug 28 '09 at 19:27
-
Statically-generated method information would be ideal. Michael, do you know of a way to do that in .NET? – Aug 28 '09 at 19:28
-
2Other than just writing the method name out manually in every log statement, you might look into compile-time aspect-oriented-programming libraries, and hook into a compile-time event to capture the method name, and a run-time aspect to log it. PostSharp is one such library. Might be overkill, though. – Michael Petrotta Aug 28 '09 at 19:32
-
A code file preprocessor is another option, of course. – Michael Petrotta Aug 28 '09 at 19:33
-
2Another option might be to use eXtensible C# to write a custom compiler attribute to statically generate the logging calls – STW Aug 28 '09 at 19:42
-
4Sorry, but, I have to say it... "performant" is not a word. *whew*, feel better :-) – Ed S. Aug 28 '09 at 19:43
-
Sorry Ed, you're right "performant" is a German word and I somehow assumed it would be the same. Interesting though that no one had problems understanding it. – Aug 28 '09 at 19:49
-
8@Hermann - "performant" is a word used primarily in technical circles, and you've used it correctly, in that context. Lots of English-speakers don't like the word, I'm guessing because it feels awkward or too "managerial". It's not in most dictionaries. There are lots of similar words ("actionable", "learnings"), but I like "performant". It fills a gap. – Michael Petrotta Aug 28 '09 at 20:00
-
@Hermann - I use performant all the time, even out of technical circles...But I use English with caution. @-user65199 I'm of the line of thinking that if it improves communication then do it! For a word to exist in a largely arbitrarily-picked, monetarily-influenced "official" dictionary, does not validate its usage! Communication is about portraying a message, the dictionary merely defines the most popular usages. I can give many reasons why it's not always good to be in line with certain popular trains of thought. :P – Michael Socha Mar 06 '15 at 21:58
-
1If performant is not a word why is it listed in the OED ????? – David V. Corbin Jun 02 '21 at 12:58
7 Answers
edit: Some background
We have a similar feature which is disabled 99% of the time; we were using an approach like:
public void DoSomething()
{
TraceCall(MethodBase.GetCurrentMethod().Name);
// Do Something
}
public void TraceCall(string methodName)
{
if (!loggingEnabled) { return; }
// Log...
}
TraceCall(MethodBase.GetCurrentMethod().Name)
It was simple, but regardless of whether or not tracing was enabled we were incurring the performance hit of using Reflection to lookup the method name.
Our options were to either require more code in every method (and risk simple mistakes or refusal) or to switch to using StackFrame
to determine the calling method only when logging was enabled.
Option A:
public void DoSomething()
{
if (loggingEnabled)
{
TraceCall(MethodBase.GetCurrentMethod().Name);
}
// Do Something
}
public void TraceCall(string methodName)
{
if (!loggingEnabled) { return; }
// Log...
}
Option B:
public void DoSomething()
{
TraceCall();
// Do Something
}
public void TraceCall()
{
if (!loggingEnabled) { return; }
StackFrame stackFrame = new StackFrame(1);
// Log...
}
We opted for Option B. It offers significant performance improvements over Option A when logging is disabled, 99% of the time and is very simple to implement.
Here's an alteration of Michael's code, to display the cost / benefit of this approach
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
}
}
}
The Results:
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

- 44,917
- 17
- 105
- 161
-
Excellent point: often, you can step back and find another way to solve the problem. new StackFrame() isn't expensive if you never need to call it. – Michael Petrotta Aug 28 '09 at 19:44
-
@Michael: exactly! it's very clean to implement, easy to unit-test, and the only time we take the performance hit is when we're essentially debugging and performance isn't a concern anyways. – STW Aug 28 '09 at 19:46
-
I guess it depends on how often you tracing will be enabled then since option B is still 10x slower than option A if tracing is enabled. But now everyone can decide for themselves based on facts. Very insightful. Thanks! – Aug 28 '09 at 20:14
-
1I just tried to launch the application without the debug mode and the numbers have changed significantly concerning the use of stackframe. It took 1117ms debugging in Visual Studio, 729ms running the assembly from debug compliation and 569ms running the assembly from release complitation. The other numbers did not changed. Finally using stackframe is only 3 times slower in release mode which is meant to be the final product. – Ucodia Jul 29 '10 at 08:05
-
Well... It assumes that you will trace in a sub function and that you will look at the trace flag there. It is a common scenario but if you don't do a sub call (look at the trace flag before calling getcurrentmethod) then getcurrentmethod will get the same benefit of not being called and will be faster than StackFrame. It makes only sense for tracing, if you need the thing, getcurrentmethod is faster and simpler. – Yepeekai Nov 16 '18 at 19:55
I know this is an old post, but just in case anyone comes across it there is another alternative if you are targeting .Net 4.5
You can use the CallerMemberName attribute to identify the calling method name. It is far faster than reflection or StackFrame. Here are the results from a quick test iterating a million times. StackFrame is extremely slow compared to reflection, and the new attribute makes both look like they are standing still. This was ran in the IDE.
Reflection Result: 00:00:01.4098808
StackFrame Result 00:00:06.2002501
CallerMemberName attribute Result: 00:00:00.0042708
Done
The following is from the compiled exe: Reflection Result: 00:00:01.2136738 StackFrame Result 00:00:03.6343924 CallerMemberName attribute Result: 00:00:00.0000947 Done
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 = "")
{
}

- 261
- 3
- 2
-
4The reason this attribute is faster is because the compiler actually burns this into the resulting image directly. In all places where a method that uses the compiler services attributes are called, the values are replaced with whatever the compiler is supposed to provide (CallerMemberName, CallerFileName, CallerLineNumber). It's basically binary re-writing. This can be confirmed using the ILDASM tool. – PSGuy Apr 30 '17 at 22:35
-
This doesnt get the caller's type either, which is apparently only available via a stack frame, and an essential part of finding out where something came from. – StingyJack Oct 08 '17 at 15:23
-
You dont necessarily need the callers type as you can get effectively the same (if not more) information from the `[CallerFilePath]` and `[CallerLineNumber]` attributes. The most annoying part of this is that it requires a optional parameter with a default value, so if your logging function takes a `String` and `params object[]` to pass to `String.Format`, you cant use them. – Matt Jun 21 '18 at 18:15
A quick and naive test indicates that for performance-sensitive code, yes, you want to pay attention to this:
Don't generate 100K frames: 3ms
Generate 100K frames: 1805ms
About 20 microseconds per generated frame, on my machine. Not a lot, but a measurable difference over a large number of iterations.
Speaking to your later questions ("Should I disable StackFrame generation in my application?"), I'd suggest you analyze your application, do performance tests like the one I've done here, and see if the performance difference amounts to anything with your workload.
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);
}
}
}
}

- 1
- 1

- 59,888
- 27
- 145
- 179
-
Thanks Michael! I like the way you tested this since I was concerned about the .NET framework somehow caching the method, but your test seems to show that this is not the case. – Aug 28 '09 at 18:57
-
Yes, I'm not sure how to square the article Paul references with my experience and tests. I've also been walking through the log4net documentation, which calls explicitly calls out how slow generating stackframes (for inclusion in logs) is. – Michael Petrotta Aug 28 '09 at 19:01
-
I repeated this test applying [MethodImpl(MethodImplOptions.NoInlining)] to each method just in case and I also get about 20µs for each StackFrame with 100k iterations and about 15µs for each StackFrame with 1 million iterations. 15-20 microseconds does not seem too bad. 50 StackFrame creations take only 1 millisecond after all. – Aug 28 '09 at 19:31
-
I don't know, either. It may be the framework calls new StackFrame(), which calls BuildStackFrame(0, false). Or maybe the documentation is misleading. /shrug +1 for actually testing it and reporting the results! – Paul Williams Aug 28 '09 at 19:33
From the MSDN documentation, it seems like StackFrames are being created all the time:
A StackFrame is created and pushed on the call stack for every function call made during the execution of a thread. The stack frame always includes MethodBase information, and optionally includes file name, line number, and column number information.
The constructor new StackFrame(1)
you'd call would do this:
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
is an external method. CalculateFramesToSkip
has a loop that operates exactly once, since you specified only 1 frame. Everything else looks pretty quick.
Have you tried measuring how long it would take to create, say, 1 million of them?

- 16,585
- 5
- 47
- 82
-
Thanks Paul. I read the same paragraph in MSDN and tried to track down how it works in Reflector, but didn't really understand it. I was thinking about timing it, but it could be misleading if it is somehow cached after the first call that's why I was hoping someone knew how it worked internally. – Aug 28 '09 at 18:54
-
3Michael's test suggests that the stack frames are not "created and pushed on the call stack for every function call made during the execution of a thread" because it does take longer to ask for it. – Aug 28 '09 at 19:00
-
4Or they may be creating them differently, or it may be a native construct until requested, or the docs may be wrong. I assume the CLR needs to keep track of the managed stack, so there must be *something* in native memory setup as the stack grows. – Paul Williams Aug 28 '09 at 19:39
-
I am considering using something like StackFrame stackFrame = new StackFrame(1) to log the executing method
Out of interest: Why? If you only want the current method, then
string methodName = System.Reflection.MethodBase.GetCurrentMethod().Name;
seems better. Maybe not more performant (I didn't compare, but Reflection shows that GetCurrentMethod() does not simply create a StackFrame but does some "magic"), but clearer in it's intent.

- 177,530
- 117
- 400
- 535
-
1I tried Michael's test with MethodBase.GetCurrentMethod() and you're right, it is actually faster. Takes about 1 microsecond per call on my machine. – Aug 28 '09 at 20:01
I think Paul Williams hit the nail on the head with the work being done. If you dig deeper into StackFrameHelper
you'll find that fNeedFileInfo
is actually the performance killer - especially in debug mode. Try setting it false if performance is important. You won't get much useful information in release mode anyway.
If you pass false in here you'll still get method names when doing a ToString()
and without outputting any information, just moving stack pointers around, it's very fast.
I know what you mean, but this example result is overshoot. Executing the GetCurrentMethod even when logging is turned off is a waste. It should be something like:
if (loggingEnabled) TraceCall(MethodBase.GetCurrentMethod());
Or if you want the TraceCall always executed:
TraceCall(loggingEnabled ? MethodBase.GetCurrentMethod() : null);

- 449
- 4
- 2