C# (Empty) Method Stripping
Filed under Optimization
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.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:
{
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:
{
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








