How performant is StackFrame?

前端 未结 7 1219
鱼传尺愫
鱼传尺愫 2020-12-01 02:18

I am considering using something like StackFrame stackFrame = new StackFrame(1) to log the executing method, but I don\'t know about its performance implication

相关标签:
7条回答
  • 2020-12-01 02:32

    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.

    0 讨论(0)
  • 2020-12-01 02:33

    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 = "")
        {
    
        }
    
    0 讨论(0)
  • 2020-12-01 02:33

    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.

    0 讨论(0)
  • 2020-12-01 02:36

    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);
                }
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-01 02:37

    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);
    
    0 讨论(0)
  • 2020-12-01 02:39

    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
    
    0 讨论(0)
提交回复
热议问题