tabs ↹ over ␣ ␣ ␣ spaces

by Jiří {x2} Činčura

Generics, structs and nulls – the JIT is smart so you don’t have to

7 May 2019 2 mins .NET Core, JIT, RyuJIT

Few days back I learned geeky little piece about JIT. This is exactly the type of knowledge you don’t have to use, maybe ever, yet it’s crucial the JIT handles it (so you don’t have to), because every smart instruction is performance gained. Literally, every instruction or every CPU cycle counts.

Let’s start with this piece fairly normal, minus the dummy return values, code in a generic class (C# 7.x).

class MyGeneric<T>
{
	T _instance;

	public MyGeneric(T instance)
	{
		_instance = instance;
	}

	public string Do()
	{
		if (_instance == null)
		{
			return "null";
		}
		return "foobar";
	}
}

If you’re here and still remember the title of this post you can probably see where this is going. When the MyGeneric<T> would be instantiated with T being a value type, the null check completely useless and always false. Because value type can’t be null. That means the whole if condition can be skipped and also the code in the if can be skipped. Not only skipped, even not generated at all. That would help not only memory usage, but also instruction cache, branch predictor, etc.

As you undoubtedly guessed, the JIT is doing that. Trying the fully optimized build, without (initially) debugger attached, with MyGeneric<DateTime> and MyGeneric<object> gives these assembly instructions (.NET Core 2.2.4, 64bit RyuJIT).

00007FFA256115C0  movsx       rax,byte ptr [rcx]
00007FFA256115C4  mov         rax,21739033068h
00007FFA256115CE  mov         rax,qword ptr [rax]
00007FFA256115D1  ret
00007FFA256115F0  cmp         qword ptr [rcx+8],0
00007FFA256115F5  jne         00007FFA25611605
00007FFA256115F7  mov         rax,21739033070h
00007FFA25611601  mov         rax,qword ptr [rax]
00007FFA25611604  ret
00007FFA25611605  mov         rax,21739033068h
00007FFA2561160F  mov         rax,qword ptr [rax]
00007FFA25611612  ret

The second assembly clearly has the if (cmp and jne) logic compared to the first one that does not. Less instructions: ✓; less branching: ✓. All this is done conveniently for you. You don’t have to think about it (unless you’re actually part of the JIT team, of course) yet the code is “optimal”.

Plus, few of us can geek about it. Happy geeking.

Profile Picture Jiří Činčura is .NET, C# and Firebird expert. He focuses on data and business layers, language constructs, parallelism, databases and performance. For almost two decades he contributes to open-source, i.e. FirebirdClient. He works as a senior software engineer for Microsoft. Frequent speaker and blogger at www.tabsoverspaces.com.