TheDeveloperBlog.com


C# JIT Method Test

JIT. Does the JIT compiler eliminate empty methods? We look at the code that could optimized. We then verify that this code is optimized. We benchmark the compiled C# code to show it is faster.

Questions: Are unused methods optimized out of RELEASE code? Do they incur a performance penalty?


Example. Many resources will tell you that the JIT optimizer can remove code that doesn't need to execute—that does nothing. Here I examine ref parameters, return values that are not used, instance methods, static methods and nested methods.

RefStatic Method
C# program that tests empty methods

using System;
using System.Diagnostics;

class Program
{
    static void Main()
    {
	// Example class
	Test t = new Test();

	// Loops
	const int m = 10000000;
	Stopwatch s1 = Stopwatch.StartNew();
	for (int i = 0; i < m; i++)
	{
	    // A
	    // Test static methods.
	    Test.Nothing();
	    Test.Nothing();
	    Test.Nothing();
	    Test.Nothing();
	    Test.Nothing();
	    Test.Nothing();
	    Test.Nothing();
	    Test.Nothing();
	    Test.Nothing();
	    Test.Nothing();
	    // B
	    // Test instance methods.
	    t.NothingElse();
	    t.NothingElse();
	    t.NothingElse();
	    t.NothingElse();
	    t.NothingElse();
	    t.NothingElse();
	    t.NothingElse();
	    t.NothingElse();
	    t.NothingElse();
	    t.NothingElse();
	    // C
	    // Test param
	    Test.NothingParam(i);
	    Test.NothingParam(i);
	    Test.NothingParam(i);
	    Test.NothingParam(i);
	    Test.NothingParam(i);
	    Test.NothingParam(i);
	    Test.NothingParam(i);
	    Test.NothingParam(i);
	    Test.NothingParam(i);
	    Test.NothingParam(i);
	    // D
	    // Test empty return
	    Test.NothingReturn();
	    Test.NothingReturn();
	    Test.NothingReturn();
	    Test.NothingReturn();
	    Test.NothingReturn();
	    Test.NothingReturn();
	    Test.NothingReturn();
	    Test.NothingReturn();
	    Test.NothingReturn();
	    Test.NothingReturn();
	    // E
	    // Test param ref
	    Test.NothingRef(ref i);
	    Test.NothingRef(ref i);
	    Test.NothingRef(ref i);
	    Test.NothingRef(ref i);
	    Test.NothingRef(ref i);
	    Test.NothingRef(ref i);
	    Test.NothingRef(ref i);
	    Test.NothingRef(ref i);
	    Test.NothingRef(ref i);
	    Test.NothingRef(ref i);
	    // F
	    // Test nested empty methods
	    Test.NothingNest();
	    Test.NothingNest();
	    Test.NothingNest();
	    Test.NothingNest();
	    Test.NothingNest();
	    Test.NothingNest();
	    Test.NothingNest();
	    Test.NothingNest();
	    Test.NothingNest();
	    Test.NothingNest();
	}
	s1.Stop();
	Stopwatch s2 = Stopwatch.StartNew();
	for (int i = 0; i < m; i++)
	{
	    // G
	    // EMPTY loop
	}
	s2.Stop();
	Console.WriteLine("{0},{1}",
	    s1.ElapsedMilliseconds,
	    s2.ElapsedMilliseconds);
	Console.Read();
    }
}

class Test
{
    /// <summary>
    /// Void static method
    /// </summary>
    public static void Nothing()
    {
    }

    /// <summary>
    /// Instance void method
    /// </summary>
    public void NothingElse()
    {
    }

    /// <summary>
    /// Empty param method
    /// </summary>
    public static void NothingParam(int x)
    {
    }

    /// <summary>
    /// Empty return method
    /// </summary>
    public static int NothingReturn()
    {
	return 0;
    }

    /// <summary>
    /// Empty ref param method
    /// </summary>
    public static void NothingRef(ref int x)
    {
    }

    /// <summary>
    /// Empty nest method 1
    /// </summary>
    public static void NothingNest()
    {
	NothingNest2();
    }

    /// <summary>
    /// Empty nest method 2
    /// </summary>
    static void NothingNest2()
    {
	NothingNest3();
    }

    /// <summary>
    /// Empty nest method 3
    /// </summary>
    static void NothingNest3()
    {
	NothingNest4();
    }

    /// <summary>
    /// Empty nest method 4
    /// </summary>
    static void NothingNest4()
    {
	NothingNest5();
    }

    /// <summary>
    /// Empty nest method 5
    /// </summary>
    static void NothingNest5()
    {
	NothingNest6();
    }

    /// <summary>
    /// Empty nest method 6
    /// </summary>
    static void NothingNest6()
    {
    }
}

Results

DEBUG, has methods:    5367 ms
DEBUG, control loop:     23 ms

RELEASE, has methods:     3 ms
RELEASE, control loop:    3 ms

I found the C# compiler does not remove empty methods such as these. In your compiled release MSIL, your empty methods will appear. Therefore, in your deployed application, the empty methods will exist.

However: In my tests the JIT compiler eliminated these method calls entirely during runtime.

And: This makes the Release executable perform equally well as if there were no methods. No performance was lost.

In the benchmark, there are two loops, the first containing 60 method calls, some of which call other methods. All of these methods are empty, but have parameters, ref parameters, return values, and are instance or static methods.

Return

So: I compiled and ran the benchmark in both DEBUG and RELEASE modes. I used 32-bit Windows Vista.

Result: In DEBUG, it required 5 seconds. In RELEASE, that time is reduced to 3 ms—about one two-thousandth.


IL. If you use IL Disassembler to look inside the release executable of this program, you will see that all of the Nothing methods are inside of it. The JIT compiler, not the C# compiler, eliminates them.

IL

Summary. The JIT compiler will remove empty methods, including ones with ref parameters. It handles instance and static methods, nested methods, and methods that return an unused value. It is effective in these cases.