C# (Empty) Method Stripping

When we develop games (or any apps really), we typically have at least two configurations: DEBUG and RELEASE. Furthermore, we usually add a bunch of debug code that should only be visible in DEBUG mode, but not in RELEASE (FINAL) mode.

I don’t know about you, but I am spoiled by C++ thinking that when we switch on the optimization, the compiler should strip empty method. However, having been in the game industry for a while, I have the mentality that we can’t really assume anything until we confirm it. So, I made a test creating an empty function and function with conditional attribute.

using System;
using System.Diagnostics;

namespace OptTest
{
    static class Debug
    {
        public static void EmptyFunction() { }

        [Conditional("DEBUG")]
        public static void DebugFunction()
        {
            Console.WriteLine("Debug Here");
        }
    }

    static class Program
    {
        static void Main(string[] args)
        {
            Debug.EmptyFunction();

            Debug.DebugFunction();
        }
    }
}

This is how the disassembly look like in DEBUG mode:

        static void Main(string[] args)
        {
00000000  mov         qword ptr [rsp+8],rcx
00000005  sub         rsp,28h
00000009  nop              
0000000a  mov         rax,7FF001B1DF8h
00000014  mov         eax,dword ptr [rax]
00000016  test        eax,eax
00000018  je          000000000000001F
0000001a  call        FFFFFFFFF9B384F0
0000001f  nop              
            Debug.EmptyFunction();
00000020  call        FFFFFFFFFFEC9A70
00000025  nop              

            Debug.DebugFunction();
00000026  call        FFFFFFFFFFEC9A78
0000002b  nop              
        }

Ok, everything works as expected in DEBUG mode, i.e. empty function and function with DEBUG conditional is not stripped. Let’s take a look the RELEASE mode assembly:

        static void Main(string[] args)
        {
            Debug.EmptyFunction();
00000000  mov         qword ptr [rsp+8],rcx
00000005  sub         rsp,28h
00000009  nop              
0000000a  mov         rax,7FF001C1DF8h
00000014  mov         eax,dword ptr [rax]
00000016  test        eax,eax
00000018  je          000000000000001F
0000001a  call        FFFFFFFFF9B28280
0000001f  call        FFFFFFFFFFEC9800   // Why is this still here???

            Debug.DebugFunction();
        }

To my surprise, C# compiler DOES NOT strip empty method!! Luckily, function with DEBUG conditional is stripped as expected. After further investigation, I found out that there are actually 2 compilers at work here: C# and JIT (Just-In-Time) compiler. C# compiler turns C# code to IL at compile time and JIT takes the IL and generates the native machine code at runtime. In addition, if you start your application from Visual Studio with the debugger attached (F5) then all the JIT optimizations will be disabled even if optimization is enabled.

In Writing High-Performance Managed Applications : A Primer, it explains how we can view the optimized JIT version of the code. After stepping through the code, it’s relieving to know that actually JIT strips out the empty method.

Jan17

Leave a Reply