title: Weird performance behavior of .NET dictionary insertion
body: <p>I have two dictionaries with different value types: <code>Dictionary&lt;int, string[]&gt;</code> and <code>Dictionary&lt;int, int[]&gt;</code>. Suppose we generate random arrays in a loop and insert them to the dictionaries (in C#).</p>
  <pre class="lang-cs prettyprint-override"><code>var d1 = new Dictionary&lt;int, string[]&gt;();
  var d2 = new Dictionary&lt;int, int[]&gt;();
  var sw = Stopwatch.StartNew();
  for (int i = 0; i &lt; 40000000; i++)
  {
      string[] sarr = new string[10];
      for (int j = 0; j &lt; 10; j++)
      {
          sarr[j] = j.ToString();
      }
      int[] iarr = new int[10];
      for (int j = 0; j &lt; 10; j++)
      {
          iarr[j] = j;
      }
      d1[i] = sarr; // (1)
      d2[i] = iarr; // (2)
  }
  sw.Stop();
  </code></pre>
  <p>Note the last two lines of the for loop. When I run the above code, it takes about 13.9 sec on my machine. Now, when I comment out only (1), then it takes about 13.7 sec. And if I comment out only (2), then it takes about 20 sec. In other words, by removing (2) it becomes much slower! I repeated this multiple times, and I can confirm that the behavior is consistent.</p>
  <p>Can anyone please explain how this is even possible?</p>
  <p>I did this experiment because I noticed that inserting <code>string[]</code> is slower than inserting <code>int[]</code> even though I am using the same key in the two dictionaries. I would like to know why inserting <code>string []</code> can be slower than inserting <code>int[]</code>, too.</p>
  <p>So my quetion is twofold: (1) how come removing a line from the above code can make things slower, (2) why inserting <code>string[]</code> is slower than inserting <code>int[]</code>?</p>
  <hr />
  <p>FYI, I am using the latest .NET 5 (5.0.103). I tried the code on both Windows and Linux, and the behavior was the same. I consistently see the same problem using either debug or release mode.</p>
  <hr />
  <p>When I diff ILs of commented out version vs. original, the commented out version has no call to <code>set_Item</code> function of the dictionary as expected. Other things are more or less the same.</p>
  <pre><code>IL_0079: ldloc.1      // dictionary2
  IL_007a: ldloc.3      // key
  IL_007b: ldloc.s      numArray
  IL_007d: callvirt     instance void class [System.Collections]System.Collections.Generic.Dictionary`2&lt;int32, int32[]&gt;::set_Item(!0/*int32*/, !1/*int32[]*/)
  IL_0082: nop
  </code></pre>
  <p>The above part was removed when I commented out (2) for example.</p>
  <hr />
  <p>To help repro this problem, I created a simple repo with Benchmark.NET: <a href="https://github.com/sangkilc/TestDictionary" rel="nofollow noreferrer">https://github.com/sangkilc/TestDictionary</a>. In this repo, I reduced the number of iterations (from 40M to 4M) because it takes too long.</p>
  <p>On my machine the results are:</p>
  <pre><code>.NET Core SDK=5.0.103
    [Host]     : .NET Core 5.0.3 (CoreCLR 5.0.321.7212, CoreFX 5.0.321.7212), X64 RyuJIT
    DefaultJob : .NET Core 5.0.3 (CoreCLR 5.0.321.7212, CoreFX 5.0.321.7212), X64 RyuJIT
  
  
  |   Method |    Mean |    Error |   StdDev |
  |--------- |--------:|---------:|---------:|
  | TestBoth | 1.269 s | 0.0222 s | 0.0208 s |
  |  TestOne | 1.381 s | 0.0257 s | 0.0241 s |
  </code></pre>
  <p>As per @TheodorZoulias's observation, if I modify <code>d2</code> to a 2D array, then the difference becomes more significant:</p>
  <pre><code>|   Method |    Mean |    Error |   StdDev |
  |--------- |--------:|---------:|---------:|
  | TestBoth | 1.137 s | 0.0195 s | 0.0163 s |
  |  TestOne | 1.373 s | 0.0246 s | 0.0345 s |
  </code></pre>