{"id":5754,"date":"2020-05-24T07:00:02","date_gmt":"2020-05-24T14:00:02","guid":{"rendered":"http:\/\/writeasync.net\/?p=5754"},"modified":"2020-05-23T13:27:06","modified_gmt":"2020-05-23T20:27:06","slug":"null-operator-performance","status":"publish","type":"post","link":"https:\/\/writeasync.net\/?p=5754","title":{"rendered":"Null operator performance"},"content":{"rendered":"<p>The <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/csharp\/language-reference\/operators\/member-access-operators#null-conditional-operators--and-\">null-conditional<\/a> (AKA <a href=\"https:\/\/csharp.christiannagel.com\/2016\/06\/17\/nullconditionaloperator\/\">&#8220;Elvis&#8221;<\/a>) operator and <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/csharp\/language-reference\/operators\/null-coalescing-operator\">null-coalescing<\/a> operators were introduced in <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/csharp\/whats-new\/csharp-6#null-conditional-operators\">C# 6.0<\/a> and <a href=\"https:\/\/en.wikipedia.org\/wiki\/C_Sharp_2.0#Null-coalescing_operator\">C# 2.0<\/a> respectively. They&#8217;ve been with us for years and help make our code more concise. But have you ever wondered about their effect on performance, especially when dealing with value types?<\/p>\n<p>In reality, a value type (struct) cannot be null. We need to either <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/csharp\/programming-guide\/types\/boxing-and-unboxing\">box it<\/a> or wrap it in a <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/system.nullable-1?view=netcore-3.1\">Nullable<\/a> to achieve this. In the worst case, it sounds like trying to use one of these null operators would require heap allocation or at least incur a bit of bookkeeping overhead for tracking &#8220;<a href=\"https:\/\/mathworld.wolfram.com\/Nullity.html\">nullity<\/a>.&#8221; However, these are just hypotheses. We need to look <a href=\"https:\/\/channel9.msdn.com\/shows\/Behind+The+Code\/\">behind the code<\/a> to find out the truth.<\/p>\n<p>We&#8217;ll consider this example:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n    public struct MyValue\r\n    {\r\n        private readonly byte&#x5B;] bytes;\r\n\r\n        public MyValue(byte&#x5B;] bytes)\r\n        {\r\n            this.bytes = bytes;\r\n        }\r\n\r\n        public int SizeN =&gt; this.bytes?.Length ?? 0;\r\n\r\n        public int SizeT =&gt; (this.bytes != null) ? this.bytes.Length : 0;\r\n    }\r\n<\/pre>\n<p>The SizeN property uses null operators to achieve the ultimate <a href=\"https:\/\/twitter.com\/marick\/status\/1114623560616751104\">scannable<\/a> one-liner. The SizeT property uses the old-fashioned ternary operator &#8212; not as terse but readable enough. They each have the same result, but which is better performance-wise? We could start by looking at the <a href=\"https:\/\/github.com\/icsharpcode\/ILSpy\">IL disassembly<\/a>:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n.method public hidebysig specialname \r\n    instance int32 get_SizeN () cil managed \r\n{\r\n    \/\/ Method begins at RVA 0x2059\r\n    \/\/ Code size 15 (0xf)\r\n    .maxstack 8\r\n\r\n    \/\/ byte&#x5B;] array = bytes;\r\n    IL_0000: ldarg.0\r\n    IL_0001: ldfld uint8&#x5B;] ConsoleApp2.MyValue::bytes\r\n    \/\/ if (array == null)\r\n    IL_0006: dup\r\n    \/\/ (no C# code)\r\n    IL_0007: brtrue.s IL_000c\r\n\r\n    IL_0009: pop\r\n    \/\/ return 0;\r\n    IL_000a: ldc.i4.0\r\n    \/\/ (no C# code)\r\n    IL_000b: ret\r\n\r\n    \/\/ return array.Length;\r\n    IL_000c: ldlen\r\n    IL_000d: conv.i4\r\n    \/\/ (no C# code)\r\n    IL_000e: ret\r\n} \/\/ end of method MyValue::get_SizeN\r\n\r\n.method public hidebysig specialname \r\n    instance int32 get_SizeT () cil managed \r\n{\r\n    \/\/ Method begins at RVA 0x2069\r\n    \/\/ Code size 19 (0x13)\r\n    .maxstack 8\r\n\r\n    \/\/ if (bytes == null)\r\n    IL_0000: ldarg.0\r\n    IL_0001: ldfld uint8&#x5B;] ConsoleApp2.MyValue::bytes\r\n    \/\/ (no C# code)\r\n    IL_0006: brtrue.s IL_000a\r\n\r\n    \/\/ return 0;\r\n    IL_0008: ldc.i4.0\r\n    \/\/ (no C# code)\r\n    IL_0009: ret\r\n\r\n    \/\/ return bytes.Length;\r\n    IL_000a: ldarg.0\r\n    IL_000b: ldfld uint8&#x5B;] ConsoleApp2.MyValue::bytes\r\n    IL_0010: ldlen\r\n    IL_0011: conv.i4\r\n    \/\/ (no C# code)\r\n    IL_0012: ret\r\n} \/\/ end of method MyValue::get_SizeT\r\n<\/pre>\n<p>Already we can make two observations:<\/p>\n<ol>\n<li>The null operator code has no overhead! It compiles down to a simple null check and early return.<\/li>\n<li>The code size for the ternary operator is larger! It seems that the culprit is multiple field access; we touch <code>this.bytes<\/code> twice (first to compare it and second to invoke it) but the null operator version can implicitly avoid this.<\/li>\n<\/ol>\n<p>Ultimately, we need to see a benchmark to determine if we have a real difference in execution time, and to rule out any hidden heap allocations (I didn&#8217;t see any in the IL but <a href=\"https:\/\/musingstudio.com\/2013\/02\/01\/alexandrescu-video-three-optimization-tips-for-c\/\">always measure<\/a>!):<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n    &#x5B;SimpleJob(RuntimeMoniker.NetCoreApp31)]\r\n    &#x5B;MemoryDiagnoser]\r\n    public class OperatorBenchmark\r\n    {\r\n        private MyValue nullVal;\r\n        private MyValue hasVal;\r\n\r\n        &#x5B;GlobalSetup]\r\n        public void Setup()\r\n        {\r\n            this.nullVal = default;\r\n            this.hasVal = new MyValue(new byte&#x5B;] { 0xA, 0xB, 0xC });\r\n        }\r\n\r\n        &#x5B;Benchmark]\r\n        public int NullN() =&gt; this.nullVal.SizeN;\r\n\r\n        &#x5B;Benchmark]\r\n        public int NullT() =&gt; this.nullVal.SizeT;\r\n\r\n        &#x5B;Benchmark]\r\n        public int HasN() =&gt; this.hasVal.SizeN;\r\n\r\n        &#x5B;Benchmark]\r\n        public int HasT() =&gt; this.hasVal.SizeT;\r\n    }\r\n<\/pre>\n<p>Using <a href=\"https:\/\/benchmarkdotnet.org\/\">BenchmarkDotNet<\/a>, we&#8217;ll compare four situations, the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Cartesian_product\">Cartesian product<\/a> of { null value, has value } x { null operator, ternary operator }. Here are the results on my machine:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n| Method |      Mean |     Error |    StdDev |    Median | Gen 0 | Gen 1 | Gen 2 | Allocated |\r\n|------- |----------:|----------:|----------:|----------:|------:|------:|------:|----------:|\r\n|  NullN | 0.5501 ns | 0.0175 ns | 0.0146 ns | 0.5463 ns |     - |     - |     - |         - |\r\n|  NullT | 0.5400 ns | 0.0092 ns | 0.0076 ns | 0.5399 ns |     - |     - |     - |         - |\r\n|   HasN | 0.5842 ns | 0.0483 ns | 0.0693 ns | 0.5421 ns |     - |     - |     - |         - |\r\n|   HasT | 0.5401 ns | 0.0085 ns | 0.0066 ns | 0.5381 ns |     - |     - |     - |         - |\r\n<\/pre>\n<p>Given the extremely small running times, the error range, and the median values, it&#8217;s safe to call these more or less identical in terms of speed. And look, no heap allocation!<\/p>\n<p>On balance, I say use the null operators. The code style is <a href=\"https:\/\/stackoverflow.com\/questions\/84102\/what-is-idiomatic-code\">idiomatic<\/a>, the IL code size is smaller, and the runtime performance is nearly equivalent as the next most terse ternary option.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The null-conditional (AKA &#8220;Elvis&#8221;) operator and null-coalescing operators were introduced in C# 6.0 and C# 2.0 respectively. They&#8217;ve been with us for years and help make our code more concise. But have you ever wondered about their effect on performance,&hellip; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[104],"tags":[],"class_list":["post-5754","post","type-post","status-publish","format-standard","hentry","category-performance"],"_links":{"self":[{"href":"https:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5754","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=5754"}],"version-history":[{"count":3,"href":"https:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5754\/revisions"}],"predecessor-version":[{"id":5757,"href":"https:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5754\/revisions\/5757"}],"wp:attachment":[{"href":"https:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=5754"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=5754"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=5754"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}