{"id":414405,"date":"2024-06-29T23:44:15","date_gmt":"2024-06-29T23:44:15","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=414405"},"modified":"-0001-11-30T00:00:00","modified_gmt":"2024-06-29T23:44:15","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=414405","title":{"rendered":"<span>Making \u00abforeach\u00bb loop as fast as \u00abfor\u00bb loop<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>Hi. I want to share some experience on creating a enumerator for <code>foreach<\/code> loop so that it would be as performant as if it was a <code>for<\/code> loop.<\/p>\n<h2>Problem statement<\/h2>\n<p>As we know, <code>System.Range<\/code> has a nice syntax sugar:<\/p>\n<pre><code class=\"cs\">var a = 1..2; \/\/ same as new Range(1, 2)<\/code><\/pre>\n<p>That&#8217;s why I decided to abuse it as a replacement for a <code>for<\/code> loop:<\/p>\n<pre><code class=\"cs\">foreach (var i in 1..5)     Console.Write(i);<\/code><\/pre>\n<p>(prints &#171;12345&#187;)<\/p>\n<p>When you iterate over something with <code>foreach<\/code>, Roslyn requires that &#171;something&#187; to have a <code>GetEnumerator<\/code> method, which should return something, which has <code>bool MoveNext()<\/code> and <code>T Current<\/code>. We can add it as an extension method:<\/p>\n<pre><code class=\"cs\">public static class Extensions {     public ... GetEnumerator(this System.Range range) => ... }<\/code><\/pre>\n<h2>Baseline (our goal)<\/h2>\n<p>Theoretically, <code>for<\/code> is more functional than just going over a sequence of natural numbers. But because so often we see<\/p>\n<pre><code class=\"cs\">for (int i = 0; i &lt; n; i++)<\/code><\/pre>\n<p>that it deserves its own use case of <code>for<\/code>. We are going to compare against a <code>for<\/code> loop with <code>i += inc<\/code> as a step:<\/p>\n<pre><code class=\"cs\">for (int i = 0; i &lt; n; i += inc)<\/code><\/pre>\n<p>In terms of performance, it&#8217;s identical to the one with <code>i++<\/code>:<\/p>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p>Method<\/p>\n<\/th>\n<th>\n<p>Iterations<\/p>\n<\/th>\n<th>\n<p>Mean<\/p>\n<\/th>\n<th>\n<p>Error<\/p>\n<\/th>\n<th>\n<p>StdDev<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p>i++<\/p>\n<\/td>\n<td>\n<p>10000<\/p>\n<\/td>\n<td>\n<p>3.380 us<\/p>\n<\/td>\n<td>\n<p>0.0534 us<\/p>\n<\/td>\n<td>\n<p>0.0446 us<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>i += inc<\/p>\n<\/td>\n<td>\n<p>10000<\/p>\n<\/td>\n<td>\n<p>3.385 us<\/p>\n<\/td>\n<td>\n<p>0.0575 us<\/p>\n<\/td>\n<td>\n<p>0.0685 us<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>The code for baseline <code>for<\/code>:<\/p>\n<pre><code class=\"cs\">[Benchmark] public int ForWithCustomIncrement() {     var iters = Iterations;     var a = 0;     var inc = Inc;     for (int i = 0; i &lt; iters; i += inc)         a += i;     return a; }<\/code><\/pre>\n<p>(we loaded all fields\/properties onto local variables to avoid repeatitive reading from far RAM)<\/p>\n<p><a href=\"https:\/\/sharplab.io\/#v2:EYLgxg9gTgpgtADwGwBYA0AXEBDAzgWwB8ABAJgEYBYAKBuIGYACMxgIRgDswALfbKANa4aAbxqMJjcZIaMAlhwyMAkhhhRsGORA65GIxgHMYGANyNcJ8wF8a0iQAcocgG6aY8xSq76jVi\/7WjAC8jOTmAPQRjMAAnoyw2AAmCoaMAGZQEPgZcjAANkkRThAO6hixaIwY3B4AUsoAKoyQ+A5y+eqMAO46AORKpVr4cgBeHnJKGBCeYJ64GFAArmBaOnbUklKbMkwKSgBi0ADqk9zKXLD4nBisseQAFACU9vqvW25Q8mpQeqGq6k02l0pnekk+jGwIUYAAZQTsthJ0tBGA99vJoXCMQAeb7qXDmOQAaiJLwRiK2UKJoTk8IpEmIAHZIXTJLZaOTXrJ0UcoKcagBhJYLbIXMBXG7PV5ickffh437QgEaNYgsESCFQ0Jw9WMCEKOb\/LisinIr5orxyTGExi4yb4m3U2Zk+mIqk0k2Ipks17s6xAA===\" rel=\"noopener noreferrer nofollow\">Codegen <\/a>for it:<\/p>\n<pre><code>    L000f: add edx, r8d     L0012: add r8d, ecx     L0015: cmp r8d, eax     L0018: jl short L000f<\/code><\/pre>\n<blockquote>\n<p>We only care about codegen of <em>iteration<\/em> of the loop, ignoring the initial overhead and all other operations around.<\/p>\n<\/blockquote>\n<h2>Experiment 1. Enumerable.Range<\/h2>\n<p>It might make sense to start with trying the already existing API from BCL: Enumerable.Range, whose source code we can find <a href=\"https:\/\/source.dot.net\/#System.Linq\/System\/Linq\/Range.cs,11\" rel=\"noopener noreferrer nofollow\">here<\/a>.<\/p>\n<p>Here&#8217;s what our benchmark looks like:<\/p>\n<pre><code class=\"cs\">[Benchmark] public int ForeachWithEnumerableRange() {     var a = 0;     foreach (var i in Enumerable.Range(0, Iterations))         a += i;     return a; }<\/code><\/pre>\n<p>This code is <a href=\"https:\/\/sharplab.io\/#v2:EYLgxg9gTgpgtADwGwBYA+ABATARgLABQGADAAQY4B0AMgJYB2AjgNyGEYDM5WpAQjPTAALALYBDKAGsAzoQDehUktKLlnUgwAupAJKaYUMZtoR600nNIBzGJualpt+wF82BZaQAOUWgDcjMBr02jqCFtZODpHOpAC8pDj2APRJpMAAnqSwYgAmDFakAGZQECJFtDAANjlJ3hCeBprpADSkmkKBAFI6ACqkkCKetJUGpADupgDk2vXGIrQAXoG02poQQWBB0ppQAK5gxqZuHqpK6lqkAGLQMGLCAOorQgCi9LsiBmLAIwBKYvQ2AAUAEpThYwR5\/FBSGI4qRiKx3B5lIUbnchKRAVCNEFSK93p9vjBKH8ATBAcRWnpPoczMDQUjkcjYQBqeK0RFMs4AdhhnOUrgIziAA\" rel=\"noopener noreferrer nofollow\">interpreted by roslyn<\/a> as<\/p>\n<pre><code class=\"cs\">public int ForeachWithEnumerableRange() {     int num = 0;     IEnumerator&lt;int> enumerator = Enumerable.Range(0, Iterations).GetEnumerator();     try     {         while (enumerator.MoveNext())         {             int current = enumerator.Current;             num += current;         }         return num;     }     finally     {         if (enumerator != null)         {             enumerator.Dispose();         }     } }<\/code><\/pre>\n<p>No need to study the asm codegen, as long as the benchmarks are pretty illustrative:<\/p>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p>Method<\/p>\n<\/th>\n<th>\n<p>Iterations<\/p>\n<\/th>\n<th>\n<p>Mean<\/p>\n<\/th>\n<th>\n<p>Error<\/p>\n<\/th>\n<th>\n<p>StdDev<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p>ForWithCustomIncrement<\/p>\n<\/td>\n<td>\n<p>10000<\/p>\n<\/td>\n<td>\n<p>3.448 us<\/p>\n<\/td>\n<td>\n<p>0.0689 us<\/p>\n<\/td>\n<td>\n<p>0.0896 us<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>ForeachWithEnumerableRange<\/p>\n<\/td>\n<td>\n<p>10000<\/p>\n<\/td>\n<td>\n<p>43.946 us<\/p>\n<\/td>\n<td>\n<p>0.8685 us<\/p>\n<\/td>\n<td>\n<p>1.2456 us<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<h2>Experiment 2. yield return<\/h2>\n<p>The second easiest way is to create a custom generator:<\/p>\n<pre><code class=\"cs\">private static IEnumerable&lt;int> MyRange(int from, int to, int inc) {     for (int i = from; i &lt;= to; i += inc)         yield return i; }  [Benchmark] public int ForeachWithYieldReturn() {     var a = 0;     foreach (var i in MyRange(0, Iterations - 1, 1))         a += i;     return a; }<\/code><\/pre>\n<p>Nothing completely new, our enumerator is still a big reference type with a dozen of fields. The performance is even worse than that with Enumerable.Range:<\/p>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p>Method<\/p>\n<\/th>\n<th>\n<p>Iterations<\/p>\n<\/th>\n<th>\n<p>Mean<\/p>\n<\/th>\n<th>\n<p>Error<\/p>\n<\/th>\n<th>\n<p>StdDev<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p>ForWithCustomIncrement<\/p>\n<\/td>\n<td>\n<p>10000<\/p>\n<\/td>\n<td>\n<p>3.548 us<\/p>\n<\/td>\n<td>\n<p>0.0661 us<\/p>\n<\/td>\n<td>\n<p>0.1320 us<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>ForeachWithEnumerableRange<\/p>\n<\/td>\n<td>\n<p>10000<\/p>\n<\/td>\n<td>\n<p>51.663 us<\/p>\n<\/td>\n<td>\n<p>0.9103 us<\/p>\n<\/td>\n<td>\n<p>1.2460 us<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>ForeachWithYieldReturn<\/p>\n<\/td>\n<td>\n<p>10000<\/p>\n<\/td>\n<td>\n<p>56.580 us<\/p>\n<\/td>\n<td>\n<p>0.3561 us<\/p>\n<\/td>\n<td>\n<p>0.2780 us<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<h2>Experiment 3. Custom enumerator<\/h2>\n<p>Now we are going to write our own enumerator. First of all, it will be a <code>struct<\/code>. To make it work with <code>foreach<\/code>, we will need a method <code>bool MoveNext()<\/code> and a property <code>int Current<\/code>.<\/p>\n<pre><code class=\"cs\">public struct RangeEnumerator {     private readonly int to;     private readonly int step;      private int curr;      public RangeEnumerator(int @from, int to, int step)     {         this.to = to + step;         this.step = step;         this.curr = @from - step;     }      public bool MoveNext()     {         curr += step;         return curr != to;     }          public int Current => curr; }<\/code><\/pre>\n<p>To make <code>System.Range<\/code> iterable with <code>foreach<\/code>, we are making an extension method:<\/p>\n<pre><code class=\"cs\">public static class Extensions {     public static RangeEnumerator GetEnumerator(this Range @this)         => (@this.Start, @this.End) switch         {             ({ IsFromEnd: true, Value: 0 }, { IsFromEnd: true, Value: 0 }) => new RangeEnumerator(0, int.MaxValue, 1),             ({ IsFromEnd: true, Value: 0 }, { IsFromEnd: false, Value: var to }) => new RangeEnumerator(0, to + 1, 1),             ({ IsFromEnd: false, Value: var from }, { IsFromEnd: true, Value: 0 }) => new RangeEnumerator(from, int.MaxValue, 1),             ({ IsFromEnd: false, Value: var from }, { IsFromEnd: false, Value: var to })                 => (from &lt; to) switch                 {                     true => new RangeEnumerator(from, to, 1),                     false => new RangeEnumerator(from, to, -1),                 },             _ => throw new InvalidOperationException(\"Invalid range\")         }; }<\/code><\/pre>\n<p>The code of the benching method:<\/p>\n<pre><code class=\"cs\">[Benchmark] public int ForeachWithRangeEnumerator() {     var a = 0;     foreach (var i in 0..(Iterations - 1))         a += i;     return a; }<\/code><\/pre>\n<p>and is interpreted by Roslyn as:<\/p>\n<pre><code class=\"cs\">public int ForeachWithRangeEnumerator() {     int num = 0;     RangeEnumerator enumerator = Extensions.GetEnumerator(new Range(0, Iterations - 1));     while (enumerator.MoveNext())     {         int current = enumerator.Current;         num += current;     }     return num; }<\/code><\/pre>\n<p>This time it&#8217;s not a reference type. Also, because our struct does not implement <code>IDisposable<\/code>, Roslyn does not wrap it with try-finally block. Let&#8217;s have a look at the benchmark results:<\/p>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p>Method<\/p>\n<\/th>\n<th>\n<p>Iterations<\/p>\n<\/th>\n<th>\n<p>Mean<\/p>\n<\/th>\n<th>\n<p>Error<\/p>\n<\/th>\n<th>\n<p>StdDev<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p>ForWithCustomIncrement<\/p>\n<\/td>\n<td>\n<p>10000<\/p>\n<\/td>\n<td>\n<p>3.477 us<\/p>\n<\/td>\n<td>\n<p>0.0690 us<\/p>\n<\/td>\n<td>\n<p>0.0989 us<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>ForeachWithEnumerableRange<\/p>\n<\/td>\n<td>\n<p>10000<\/p>\n<\/td>\n<td>\n<p>50.501 us<\/p>\n<\/td>\n<td>\n<p>0.9984 us<\/p>\n<\/td>\n<td>\n<p>1.2627 us<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>ForeachWithYieldReturn<\/p>\n<\/td>\n<td>\n<p>10000<\/p>\n<\/td>\n<td>\n<p>53.782 us<\/p>\n<\/td>\n<td>\n<p>1.0583 us<\/p>\n<\/td>\n<td>\n<p>0.9900 us<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>ForeachWithRangeEnumerator<\/p>\n<\/td>\n<td>\n<p>10000<\/p>\n<\/td>\n<td>\n<p>18.061 us<\/p>\n<\/td>\n<td>\n<p>0.1731 us<\/p>\n<\/td>\n<td>\n<p>0.1619 us<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>18 microseconds per 10k operations. That&#8217;s much better than the previous attempts, but still worse than <code>for<\/code>. Let&#8217;s check the codegen:<\/p>\n<p>One loop iteration <a href=\"https:\/\/sharplab.io\/#v2:EYLgxg9gTgpgtADwGwBYA0AXEBDAzgWwB8ABAJgEYBYAKBuIGYACMxgIRgDswALfbKANa4aAbxqMJzJgEsOGRgEkMMKNgzSIHXIxGMA5jAwBuRrkMmAvjXGSADlGkA3NTEaz5Crjv3nTvi4wAvIzkJgD0YYzAAJ6MsNgAJrJ6jABmUBD4adIwADYJYfYQtioY0WiMGNyuAFIKACqMkPi20rkqjADumgDk8sXq+NIAXq7S8hgQbl6yuBhQAK5g6prW1JKMaxsM0\/IAYtAw2DwA6uPcAErYHAYAohwL+Cpq0AAUAJQ2EmLrG5LOUEY2CCjAADEYvn9UodjtxGK8AW5pmCAHQo15KZ4rLSMOAhd6fX5\/P7AgDUwWkEKJf2IAHYgVSNlZqMydsRyEhmKRGLcEMotBotJCfsSJGyOYwrjcYPdHljoIwAOKGWVPVSTKCvKrSbRSgzMYja3CE0V\/QIAPnhxEN3B1KIAyhh+BgKtajSj7gl3qZOuMeJDTSLTaLXroFLg9hl8J6QJVFjAKgA1bC5BYwWOgxgWCphiNRmNxtNJlNpjNZ70WxgcGCdSXXO4PNUvTWgiruFEAWWwCGTqYT+LQAeDElDijzmQL8yLjF7pbBWZzY8jE44CVjqRTZmLfdjiMm5aClurtb1Msb8pbFX3pJCFXI70H1OHI9zy+jq\/Xm\/7s\/TjER6UyBdvHDN9J3jbc50zCwKyPGs62lVUL1eAD8DbORO27H87wfIdh1HED8w\/NIvwg39\/yjIDX0ItdiNyLcZxLMj+EqKZoNw58JErZCKIAHhY71cF9DB\/SfDjJCDMTgynVxK2PeCGzldU3hQq8IGwx9JOHDc6Jk2CT3rM9FObbjMlUio4HvDTNKZKzJIAfUPSpuAyWs5M8ZxcmkBIAHkSnVQVeTAGBbGxV4ACJ3JTLy4gMsKTWfCxGUkZktkkHY5kWZZ5MMpsNWFdj7CcFw4iOBJNFyWJ3BYpLiUK5xlBKxJysquRTGUWwqQKhx6rGVqwAWKAoE60SJHcFQOBTZgUEYAARGBIC0KdlleCAFnkKrrRUxhVvW1rJgqHbdja4L4uHLjNqjMzjtsCt4X6war1tXAUX2py7TmE7hvYsaoAm3JssQpTNQ24gtqq16qo+m72IkqSnpeqZgmva6arh972pBKHUdNd17sBYILsAvEsfYlLaBGqQoggCB\/o7CBHBgAA5GA+Q+GH2I2PHGHJFGObS+kuYAQiRiBseS1LiR2KqAGEBtgVrKzx1GLCAA==\" rel=\"noopener noreferrer nofollow\">looks like<\/a>:<\/p>\n<pre><code>    L003e: add esi, eax     L0040: mov eax, [rsp+0x38]     L0044: add eax, [rsp+0x34]     L0048: mov [rsp+0x38], eax     L004c: mov eax, [rsp+0x38]     L0050: cmp eax, [rsp+0x30]     L0054: jne short L003a<\/code><\/pre>\n<p>Despite the enumerator being a struct, and hence, on stack, it&#8217;s still read from RAM. Let&#8217;s see what else we can do!<\/p>\n<h2>Experiment 4. Separating the enumerator into a local function<\/h2>\n<p>Why&#8230;? Because what we <em>want<\/em> is in fact to put the enumerator itself onto <em>registers<\/em> instead of reading\/writing from\/to RAM all the time. But probably because of too many local variables, the JIT compiler decided not to put our struct onto registers, so let us help it a bit.<\/p>\n<p>From experiment 3 we have<\/p>\n<pre><code class=\"cs\">[Benchmark] public int ForeachWithRangeEnumeratorRaw() {     var a = 0;     var enumerator = (0..(Iterations - 1)).GetEnumerator();     while (enumerator.MoveNext())         a += enumerator.Current;     return a; }<\/code><\/pre>\n<p>Note, that I replaced <code>foreach<\/code> with the equivalent code, and added <code>Raw<\/code> as postfix. In Experiment 3 it&#8217;s shown how <code>foreach<\/code> works under the hood &#8212; the same as I wrote manually.<\/p>\n<p>To reduce the number of local variables, we move the loop to a local function:<\/p>\n<pre><code class=\"cs\">[Benchmark] public int ForeachWithRangeEnumeratorRawWithLocal() {     var enumerator = (0..(Iterations - 1)).GetEnumerator();             return EnumerateItAll(enumerator);      static int EnumerateItAll(RangeEnumerator enumerator)     {         var a = 0;         while (enumerator.MoveNext())             a += enumerator.Current;         return a;     } }<\/code><\/pre>\n<p>And run the benchmark:<\/p>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p>Method<\/p>\n<\/th>\n<th>\n<p>Iterations<\/p>\n<\/th>\n<th>\n<p>Mean<\/p>\n<\/th>\n<th>\n<p>Error<\/p>\n<\/th>\n<th>\n<p>StdDev<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p>ForWithCustomIncrement<\/p>\n<\/td>\n<td>\n<p>10000<\/p>\n<\/td>\n<td>\n<p>3.498 us<\/p>\n<\/td>\n<td>\n<p>0.0658 us<\/p>\n<\/td>\n<td>\n<p>0.1153 us<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>ForeachWithEnumerableRange<\/p>\n<\/td>\n<td>\n<p>10000<\/p>\n<\/td>\n<td>\n<p>43.313 us<\/p>\n<\/td>\n<td>\n<p>0.8207 us<\/p>\n<\/td>\n<td>\n<p>1.0079 us<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>ForeachWithYieldReturn<\/p>\n<\/td>\n<td>\n<p>10000<\/p>\n<\/td>\n<td>\n<p>56.963 us<\/p>\n<\/td>\n<td>\n<p>1.0638 us<\/p>\n<\/td>\n<td>\n<p>1.0448 us<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>ForeachWithRangeEnumerator<\/p>\n<\/td>\n<td>\n<p>10000<\/p>\n<\/td>\n<td>\n<p>17.931 us<\/p>\n<\/td>\n<td>\n<p>0.2047 us<\/p>\n<\/td>\n<td>\n<p>0.1915 us<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>ForeachWithRangeEnumeratorRaw<\/p>\n<\/td>\n<td>\n<p>10000<\/p>\n<\/td>\n<td>\n<p>17.932 us<\/p>\n<\/td>\n<td>\n<p>0.1486 us<\/p>\n<\/td>\n<td>\n<p>0.1390 us<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>ForeachWithRangeEnumeratorRawWithLocal<\/p>\n<\/td>\n<td>\n<p>10000<\/p>\n<\/td>\n<td>\n<p>3.501 us<\/p>\n<\/td>\n<td>\n<p>0.0678 us<\/p>\n<\/td>\n<td>\n<p>0.0807 us<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p><code>ForeachWithRangeEnumeratorRawWithLocal<\/code> is the last method we wrote &#8212; and it&#8217;s almost <strong>6 times<\/strong> faster, than the one with the loop inlined into the body! What happened?<\/p>\n<p>Let&#8217;s look at the <a href=\"https:\/\/sharplab.io\/#v2:EYLgxg9gTgpgtADwGwBYA0AXEBDAzgWwB8ABAJgEYBYAKBuIGYACMxgIRgDswALfbKANa4aAbxqMJzJgEsOGRgEkMMKNgzSIHXIxGMA5jAwBuRrkMmAvjXGSADlGkA3NTEaz5Crjv3nTvi4wAvIzkJgD0YYzAAJ6MsNgAJrJ6jABmUBD4adIwADYJYfYQtioY0WiMGNyuAFIKACqMkPi20rkqjADumgDk8sXq+NIAXq7S8hgQbl6yuBhQAK5g6prW1JKMaxsM0\/IAYtAw2DwA6uPcAErYHAYAohwL+Cpq0FedZ1UAMhBg2LkAFABKGwSMTrDaSZxQRicR7PSbQ4L\/AAMADpUf8lPCNFpGHAQoDAaiAOKGe5w1QIoEmCESEEQ4gAdkY5KelJgSgAgrkAbC2S8oICjFtacxyEhdiyHvzlFyef8rjcYKz4dAYdLVYL6RswaKIVDGNggoxkcLwXqJJ1uG1XP8+ZrUQBZCCOGAAORgCAwQOB5otGyNAGpgvbKdBUQBhBZQWByM3+iRMw3x0VWP1pjb0mhpnbEcXMUgsr2cXA44R+3Wi3P5xV3DVh6GkjAqhv\/KrSbS11zEYjt3C+hNBAB8jH+Pb7qIAyhh+BgKuPrbhUfcEoDTJ1xjxtaLK4OJP9dApcHsMvgVyBKosYBUAGp\/BYwC\/IxgWCqH4+n8+Xh+3++Pk0vmugQjhwMCdIwXYtgKKIVO4TrYAgd65D+BJoNu\/oHooH6ZF+8woUhD5Pi+b5YSeOEcAkF6pH8Zi\/sh\/4GpMgHDowoHgZB9bQciFRMYGIQVOQgJoX6g6YUeZFnhRVE0dejAEQx\/BpKexHeOJn5Sd+snyURFhASBYEQdcdYUtB6SZLBcjwYhf4CUJ6EWmJ2GSZRaQyXRhGMAaZlZK+qlOV+1G5LRcl\/hejFTLp9mDsBo7eYwAA8lQQGuuAbhgW4iXuoJRVlmksWxhlKlBVLeTxEC2cJuUWoFZj5QZHEmSVp5lRUcCCZVVWSK+OW0gA+ixVQZOBBWeM4uTSAkADyJSUjitwIGAMC2CsHD\/AARKNfwTXERkwGtA4JhYKYSGmIqJkwcyLMshXGTK0D0ruEL2E4LhxEcCSaLksTuElx1PQ4zjKG9iSfd9cimMothmvZz2A2M4NgNGUDQ5lEjuCoHB\/MwKCMAAIjAkBaHhyz\/BACzyD9PalYwZMU+DkwVLTkpzEtB3+jFY7ENTDMQ6zxr\/IjMY8YuqI8xOLO2EKaz2ejUCY7kN3KpxVKU1zzWSjzP0S2zEKPXqE5McEvG81DPWVCLEvGhLf36yLguIswauZHiJs2110uo1IUQQBACvOq6HpekC9l66K9uMMGrtm0m4cAIRGxAbsnWdDIyODUYxpw8gxfbf0WEAA=\" rel=\"noopener noreferrer nofollow\">codegen<\/a>.<\/p>\n<p>In our method the local function hasn&#8217;t been inlined:<\/p>\n<pre><code>Benchmarks.ForeachWithRangeEnumeratorRawWithLocal()     ...     L0044: call Benchmarks.&lt;ForeachWithRangeEnumeratorRawWithLocal>g__EnumerateItAll|8_0(RangeEnumerator)<\/code><\/pre>\n<p>And it&#8217;s good news. Let&#8217;s have a look at our local function:<\/p>\n<pre><code>    L0000: mov eax, [rcx]     L0002: mov edx, [rcx+4]     L0005: mov ecx, [rcx+8]     L0008: xor r8d, r8d     L000b: jmp short L0010 >>  L000d: add r8d, ecx          >>  L0010: add ecx, edx          >>  L0012: cmp ecx, eax          >>  L0014: jne short L000d           L0016: mov eax, r8d     L0019: ret<\/code><\/pre>\n<p>The four marked lines<\/p>\n<pre><code>    L000d: add r8d, ecx     L0010: add ecx, edx     L0012: cmp ecx, eax     L0014: jne short L000d<\/code><\/pre>\n<p>are what we were trying to do. It&#8217;s identical to the codegen for the <code>for<\/code> loop!<\/p>\n<p>The first three lines<\/p>\n<pre><code>    L0000: mov eax, [rcx]     L0002: mov edx, [rcx+4]     L0005: mov ecx, [rcx+8]<\/code><\/pre>\n<p>simply load the local variables from stack onto registers. That is why our method is so fast.<\/p>\n<h2>Windows vs Linux<\/h2>\n<p>There is in fact a huge difference between what was generated on windows vs linux. What I wrote before is how it works on Windows.<\/p>\n<p>To illustrate the difference I created a <a href=\"https:\/\/gist.github.com\/WhiteBlackGoose\/5c567281c0b6bbac816ae0d48607ccba\" rel=\"noopener noreferrer nofollow\">gist<\/a> for comparing codegens for <code>ForeachWithRangeEnumeratorRaw<\/code> and <code>ForeachWithRangeEnumeratorRawWithLocal<\/code>. Let me go over the key differences:<\/p>\n<p>In case of <code>ForeachWithRangeEnumeratorRaw<\/code> method, Linux&#8217;s JIT generates:<\/p>\n<pre><code>M00_L00:        add       ebx,esi        add       esi,edi        cmp       esi,eax        jne       short M00_L00<\/code><\/pre>\n<p>Whereas Windows&#8217; JIT yields this:<\/p>\n<pre><code>M00_L00:        add       esi,[rsp+38]        mov       eax,[rsp+38]        add       eax,[rsp+34]        mov       [rsp+38],eax        mov       eax,[rsp+38]        cmp       eax,[rsp+30]        jne       short M00_L00<\/code><\/pre>\n<p>Unlike that one, in case of <code>ForeachWithRangeEnumeratorRawWithLocal<\/code> Linux gives<\/p>\n<pre><code>jmp       near ptr Benchmarks.&lt;ForeachWithRangeEnumeratorRawWithLocal>g__EnumerateItAll|14_0(Library.RangeEnumerator)<\/code><\/pre>\n<p>in the method, and in local function it generates:<\/p>\n<pre><code>M02_L00:        mov       edi,[rbp-8]        add       eax,edi        add       edi,[rbp-0C]        mov       [rbp-8],edi        cmp       edi,esi        jne       short M02_L00<\/code><\/pre>\n<p>Meanwhile, Windows&#8217;s JIT gives call:<\/p>\n<pre><code>call      Benchmarks.&lt;ForeachWithRangeEnumeratorRawWithLocal>g__EnumerateItAll|14_0(Library.RangeEnumerator) <\/code><\/pre>\n<p>and the iteration codegen:<\/p>\n<pre><code>M02_L00:        add       r8d,ecx        add       ecx,edx        cmp       ecx,eax        jne       short M02_L00<\/code><\/pre>\n<p>(all that codegen see in the <a href=\"https:\/\/gist.github.com\/WhiteBlackGoose\/5c567281c0b6bbac816ae0d48607ccba\" rel=\"noopener noreferrer nofollow\">gist<\/a>)<\/p>\n<h2>Conclusion<\/h2>\n<p>First, although we got what we wanted, it&#8217;s definitely easier to write a <code>for<\/code> loop than caring about whether the compiler put our values onto registers.<\/p>\n<p>Second, it&#8217;s an interesting example how sometimes inlining can literally ruin the performance (in our case, on Windows it took 5-6 times more time on 10k iterations, 3.5 us vs 18 us).<\/p>\n<h2>References<\/h2>\n<p>All the code I wrote is on a <a href=\"https:\/\/github.com\/WhiteBlackGoose\/FastForeachEnumeratorArticle\" rel=\"noopener noreferrer nofollow\">github repo<\/a>.<\/p>\n<ul>\n<li>\n<p><a href=\"https:\/\/github.com\/WhiteBlackGoose\/FastForeachEnumeratorArticle\/blob\/main\/FastEnum\/Benchmarks\/Program.cs\" rel=\"noopener noreferrer nofollow\">Benchmarks<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/github.com\/WhiteBlackGoose\/FastForeachEnumeratorArticle\/blob\/main\/FastEnum\/Library\/RangeEnumerator.cs\" rel=\"noopener noreferrer nofollow\">The enumerator&#8217;s implementation<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/github.com\/WhiteBlackGoose\/FastForeachEnumeratorArticle\/blob\/main\/FastEnum\/Tests\/UnitTest1.cs\" rel=\"noopener noreferrer nofollow\">Tests<\/a> (to verify that the benched methods are correct)<\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/github.com\/dotnet\/BenchmarkDotNet\" rel=\"noopener noreferrer nofollow\">BenchmarkDotNet<\/a> &#8212; tool for accurate performance measurement<\/p>\n<\/li>\n<\/ul>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/575916\/\"> https:\/\/habr.com\/ru\/articles\/575916\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>Hi. I want to share some experience on creating a enumerator for <code>foreach<\/code> loop so that it would be as performant as if it was a <code>for<\/code> loop.<\/p>\n<h2>Problem statement<\/h2>\n<p>As we know, <code>System.Range<\/code> has a nice syntax sugar:<\/p>\n<pre><code class=\"cs\">var a = 1..2; \/\/ same as new Range(1, 2)<\/code><\/pre>\n<p>That&#8217;s why I decided to abuse it as a replacement for a <code>for<\/code> loop:<\/p>\n<pre><code class=\"cs\">foreach (var i in 1..5)     Console.Write(i);<\/code><\/pre>\n<p>(prints &#171;12345&#187;)<\/p>\n<p>When you iterate over something with <code>foreach<\/code>, Roslyn requires that &#171;something&#187; to have a <code>GetEnumerator<\/code> method, which should return something, which has <code>bool MoveNext()<\/code> and <code>T Current<\/code>. We can add it as an extension method:<\/p>\n<pre><code class=\"cs\">public static class Extensions {     public ... GetEnumerator(this System.Range range) => ... }<\/code><\/pre>\n<h2>Baseline (our goal)<\/h2>\n<p>Theoretically, <code>for<\/code> is more functional than just going over a sequence of natural numbers. But because so often we see<\/p>\n<pre><code class=\"cs\">for (int i = 0; i &lt; n; i++)<\/code><\/pre>\n<p>that it deserves its own use case of <code>for<\/code>. We are going to compare against a <code>for<\/code> loop with <code>i += inc<\/code> as a step:<\/p>\n<pre><code class=\"cs\">for (int i = 0; i &lt; n; i += inc)<\/code><\/pre>\n<p>In terms of performance, it&#8217;s identical to the one with <code>i++<\/code>:<\/p>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p>Method<\/p>\n<\/th>\n<th>\n<p>Iterations<\/p>\n<\/th>\n<th>\n<p>Mean<\/p>\n<\/th>\n<th>\n<p>Error<\/p>\n<\/th>\n<th>\n<p>StdDev<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p>i++<\/p>\n<\/td>\n<td>\n<p>10000<\/p>\n<\/td>\n<td>\n<p>3.380 us<\/p>\n<\/td>\n<td>\n<p>0.0534 us<\/p>\n<\/td>\n<td>\n<p>0.0446 us<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>i += inc<\/p>\n<\/td>\n<td>\n<p>10000<\/p>\n<\/td>\n<td>\n<p>3.385 us<\/p>\n<\/td>\n<td>\n<p>0.0575 us<\/p>\n<\/td>\n<td>\n<p>0.0685 us<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>The code for baseline <code>for<\/code>:<\/p>\n<pre><code class=\"cs\">[Benchmark] public int ForWithCustomIncrement() {     var iters = Iterations;     var a = 0;     var inc = Inc;     for (int i = 0; i &lt; iters; i += inc)         a += i;     return a; }<\/code><\/pre>\n<p>(we loaded all fields\/properties onto local variables to avoid repeatitive reading from far RAM)<\/p>\n<p><a href=\"https:\/\/sharplab.io\/#v2:EYLgxg9gTgpgtADwGwBYA0AXEBDAzgWwB8ABAJgEYBYAKBuIGYACMxgIRgDswALfbKANa4aAbxqMJjcZIaMAlhwyMAkhhhRsGORA65GIxgHMYGANyNcJ8wF8a0iQAcocgG6aY8xSq76jVi\/7WjAC8jOTmAPQRjMAAnoyw2AAmCoaMAGZQEPgZcjAANkkRThAO6hixaIwY3B4AUsoAKoyQ+A5y+eqMAO46AORKpVr4cgBeHnJKGBCeYJ64GFAArmBaOnbUklKbMkwKSgBi0ADqk9zKXLD4nBisseQAFACU9vqvW25Q8mpQeqGq6k02l0pnekk+jGwIUYAAZQTsthJ0tBGA99vJoXCMQAeb7qXDmOQAaiJLwRiK2UKJoTk8IpEmIAHZIXTJLZaOTXrJ0UcoKcagBhJYLbIXMBXG7PV5ickffh437QgEaNYgsESCFQ0Jw9WMCEKOb\/LisinIr5orxyTGExi4yb4m3U2Zk+mIqk0k2Ipks17s6xAA===\" rel=\"noopener noreferrer nofollow\">Codegen <\/a>for it:<\/p>\n<pre><code>    L000f: add edx, r8d     L0012: add r8d, ecx     L0015: cmp r8d, eax     L0018: jl short L000f<\/code><\/pre>\n<blockquote>\n<p>We only care about codegen of <em>iteration<\/em> of the loop, ignoring the initial overhead and all other operations around.<\/p>\n<\/blockquote>\n<h2>Experiment 1. Enumerable.Range<\/h2>\n<p>It might make sense to start with trying the already existing API from BCL: Enumerable.Range, whose source code we can find <a href=\"https:\/\/source.dot.net\/#System.Linq\/System\/Linq\/Range.cs,11\" rel=\"noopener noreferrer nofollow\">here<\/a>.<\/p>\n<p>Here&#8217;s what our benchmark looks like:<\/p>\n<pre><code class=\"cs\">[Benchmark] public int ForeachWithEnumerableRange() {     var a = 0;     foreach (var i in Enumerable.Range(0, Iterations))         a += i;     return a; }<\/code><\/pre>\n<p>This code is <a href=\"https:\/\/sharplab.io\/#v2:EYLgxg9gTgpgtADwGwBYA+ABATARgLABQGADAAQY4B0AMgJYB2AjgNyGEYDM5WpAQjPTAALALYBDKAGsAzoQDehUktKLlnUgwAupAJKaYUMZtoR600nNIBzGJualpt+wF82BZaQAOUWgDcjMBr02jqCFtZODpHOpAC8pDj2APRJpMAAnqSwYgAmDFakAGZQECJFtDAANjlJ3hCeBprpADSkmkKBAFI6ACqkkCKetJUGpADupgDk2vXGIrQAXoG02poQQWBB0ppQAK5gxqZuHqpK6lqkAGLQMGLCAOorQgCi9LsiBmLAIwBKYvQ2AAUAEpThYwR5\/FBSGI4qRiKx3B5lIUbnchKRAVCNEFSK93p9vjBKH8ATBAcRWnpPoczMDQUjkcjYQBqeK0RFMs4AdhhnOUrgIziAA\" rel=\"noopener noreferrer nofollow\">interpreted by roslyn<\/a> as<\/p>\n<pre><code class=\"cs\">public int ForeachWithEnumerableRange() {     int num = 0;     IEnumerator&lt;int> enumerator = Enumerable.Range(0, Iterations).GetEnumerator();     try     {         while (enumerator.MoveNext())         {             int current = enumerator.Current;             num += current;         }         return num;     }     finally     {         if (enumerator != null)         {             enumerator.Dispose();         }     } }<\/code><\/pre>\n<p>No need to study the asm codegen, as long as the benchmarks are pretty illustrative:<\/p>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p>Method<\/p>\n<\/th>\n<th>\n<p>Iterations<\/p>\n<\/th>\n<th>\n<p>Mean<\/p>\n<\/th>\n<th>\n<p>Error<\/p>\n<\/th>\n<th>\n<p>StdDev<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p>ForWithCustomIncrement<\/p>\n<\/td>\n<td>\n<p>10000<\/p>\n<\/td>\n<td>\n<p>3.448 us<\/p>\n<\/td>\n<td>\n<p>0.0689 us<\/p>\n<\/td>\n<td>\n<p>0.0896 us<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>ForeachWithEnumerableRange<\/p>\n<\/td>\n<td>\n<p>10000<\/p>\n<\/td>\n<td>\n<p>43.946 us<\/p>\n<\/td>\n<td>\n<p>0.8685 us<\/p>\n<\/td>\n<td>\n<p>1.2456 us<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<h2>Experiment 2. yield return<\/h2>\n<p>The second easiest way is to create a custom generator:<\/p>\n<pre><code class=\"cs\">private static IEnumerable&lt;int> MyRange(int from, int to, int inc) {     for (int i = from; i &lt;= to; i += inc)         yield return i; }  [Benchmark] public int ForeachWithYieldReturn() {     var a = 0;     foreach (var i in MyRange(0, Iterations - 1, 1))         a += i;     return a; }<\/code><\/pre>\n<p>Nothing completely new, our enumerator is still a big reference type with a dozen of fields. The performance is even worse than that with Enumerable.Range:<\/p>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p>Method<\/p>\n<\/th>\n<th>\n<p>Iterations<\/p>\n<\/th>\n<th>\n<p>Mean<\/p>\n<\/th>\n<th>\n<p>Error<\/p>\n<\/th>\n<th>\n<p>StdDev<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p>ForWithCustomIncrement<\/p>\n<\/td>\n<td>\n<p>10000<\/p>\n<\/td>\n<td>\n<p>3.548 us<\/p>\n<\/td>\n<td>\n<p>0.0661 us<\/p>\n<\/td>\n<td>\n<p>0.1320 us<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>ForeachWithEnumerableRange<\/p>\n<\/td>\n<td>\n<p>10000<\/p>\n<\/td>\n<td>\n<p>51.663 us<\/p>\n<\/td>\n<td>\n<p>0.9103 us<\/p>\n<\/td>\n<td>\n<p>1.2460 us<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>ForeachWithYieldReturn<\/p>\n<\/td>\n<td>\n<p>10000<\/p>\n<\/td>\n<td>\n<p>56.580 us<\/p>\n<\/td>\n<td>\n<p>0.3561 us<\/p>\n<\/td>\n<td>\n<p>0.2780 us<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<h2>Experiment 3. Custom enumerator<\/h2>\n<p>Now we are going to write our own enumerator. First of all, it will be a <code>struct<\/code>. To make it work with <code>foreach<\/code>, we will need a method <code>bool MoveNext()<\/code> and a property <code>int Current<\/code>.<\/p>\n<pre><code class=\"cs\">public struct RangeEnumerator {     private readonly int to;     private readonly int step;      private int curr;      public RangeEnumerator(int @from, int to, int step)     {         this.to = to + step;         this.step = step;         this.curr = @from - step;     }      public bool MoveNext()     {         curr += step;         return curr != to;     }          public int Current => curr; }<\/code><\/pre>\n<p>To make <code>System.Range<\/code> iterable with <code>foreach<\/code>, we are making an extension method:<\/p>\n<pre><code class=\"cs\">public static class Extensions {     public static RangeEnumerator GetEnumerator(this Range @this)         => (@this.Start, @this.End) switch         {             ({ IsFromEnd: true, Value: 0 }, { IsFromEnd: true, Value: 0 }) => new RangeEnumerator(0, int.MaxValue, 1),             ({ IsFromEnd: true, Value: 0 }, { IsFromEnd: false, Value: var to }) => new RangeEnumerator(0, to + 1, 1),             ({ IsFromEnd: false, Value: var from }, { IsFromEnd: true, Value: 0 }) => new RangeEnumerator(from, int.MaxValue, 1),             ({ IsFromEnd: false, Value: var from }, { IsFromEnd: false, Value: var to })                 => (from &lt; to) switch                 {                     true => new RangeEnumerator(from, to, 1),                     false => new RangeEnumerator(from, to, -1),                 },             _ => throw new InvalidOperationException(\"Invalid range\")         }; }<\/code><\/pre>\n<p>The code of the benching method:<\/p>\n<pre><code class=\"cs\">[Benchmark] public int ForeachWithRangeEnumerator() {     var a = 0;     foreach (var i in 0..(Iterations - 1))         a += i;     return a; }<\/code><\/pre>\n<p>and is interpreted by Roslyn as:<\/p>\n<pre><code class=\"cs\">public int ForeachWithRangeEnumerator() {     int num = 0;     RangeEnumerator enumerator = Extensions.GetEnumerator(new Range(0, Iterations - 1));     while (enumerator.MoveNext())     {         int current = enumerator.Current;         num += current;     }     return num; }<\/code><\/pre>\n<p>This time it&#8217;s not a reference type. Also, because our struct does not implement <code>IDisposable<\/code>, Roslyn does not wrap it with try-finally block. Let&#8217;s have a look at the benchmark results:<\/p>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p>Method<\/p>\n<\/th>\n<th>\n<p>Iterations<\/p>\n<\/th>\n<th>\n<p>Mean<\/p>\n<\/th>\n<th>\n<p>Error<\/p>\n<\/th>\n<th>\n<p>StdDev<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p>ForWithCustomIncrement<\/p>\n<\/td>\n<td>\n<p>10000<\/p>\n<\/td>\n<td>\n<p>3.477 us<\/p>\n<\/td>\n<td>\n<p>0.0690 us<\/p>\n<\/td>\n<td>\n<p>0.0989 us<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>ForeachWithEnumerableRange<\/p>\n<\/td>\n<td>\n<p>10000<\/p>\n<\/td>\n<td>\n<p>50.501 us<\/p>\n<\/td>\n<td>\n<p>0.9984 us<\/p>\n<\/td>\n<td>\n<p>1.2627 us<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>ForeachWithYieldReturn<\/p>\n<\/td>\n<td>\n<p>10000<\/p>\n<\/td>\n<td>\n<p>53.782 us<\/p>\n<\/td>\n<td>\n<p>1.0583 us<\/p>\n<\/td>\n<td>\n<p>0.9900 us<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>ForeachWithRangeEnumerator<\/p>\n<\/td>\n<td>\n<p>10000<\/p>\n<\/td>\n<td>\n<p>18.061 us<\/p>\n<\/td>\n<td>\n<p>0.1731 us<\/p>\n<\/td>\n<td>\n<p>0.1619 us<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>18 microseconds per 10k operations. That&#8217;s much better than the previous attempts, but still worse than <code>for<\/code>. Let&#8217;s check the codegen:<\/p>\n<p>One loop iteration <a href=\"https:\/\/sharplab.io\/#v2:EYLgxg9gTgpgtADwGwBYA0AXEBDAzgWwB8ABAJgEYBYAKBuIGYACMxgIRgDswALfbKANa4aAbxqMJzJgEsOGRgEkMMKNgzSIHXIxGMA5jAwBuRrkMmAvjXGSADlGkA3NTEaz5Crjv3nTvi4wAvIzkJgD0YYzAAJ6MsNgAJrJ6jABmUBD4adIwADYJYfYQtioY0WiMGNyuAFIKACqMkPi20rkqjADumgDk8sXq+NIAXq7S8hgQbl6yuBhQAK5g6prW1JKMaxsM0\/IAYtAw2DwA6uPcAErYHAYAohwL+Cpq0AAUAJQ2EmLrG5LOUEY2CCjAADEYvn9UodjtxGK8AW5pmCAHQo15KZ4rLSMOAhd6fX5\/P7AgDUwWkEKJf2IAHYgVSNlZqMydsRyEhmKRGLcEMotBotJCfsSJGyOYwrjcYPdHljoIwAOKGWVPVSTKCvKrSbRSgzMYja3CE0V\/QIAPnhxEN3B1KIAyhh+BgKtajSj7gl3qZOuMeJDTSLTaLXroFLg9hl8J6QJVFjAKgA1bC5BYwWOgxgWCphiNRmNxtNJlNpjNZ70WxgcGCdSXXO4PNUvTWgiruFEAWWwCGTqYT+LQAeDElDijzmQL8yLjF7pbBWZzY8jE44CVjqRTZmLfdjiMm5aClurtb1Msb8pbFX3pJCFXI70H1OHI9zy+jq\/Xm\/7s\/TjER6UyBdvHDN9J3jbc50zCwKyPGs62lVUL1eAD8DbORO27H87wfIdh1HED8w\/NIvwg39\/yjIDX0ItdiNyLcZxLMj+EqKZoNw58JErZCKIAHhY71cF9DB\/SfDjJCDMTgynVxK2PeCGzldU3hQq8IGwx9JOHDc6Jk2CT3rM9FObbjMlUio4HvDTNKZKzJIAfUPSpuAyWs5M8ZxcmkBIAHkSnVQVeTAGBbGxV4ACJ3JTLy4gMsKTWfCxGUkZktkkHY5kWZZ5MMpsNWFdj7CcFw4iOBJNFyWJ3BYpLiUK5xlBKxJysquRTGUWwqQKhx6rGVqwAWKAoE60SJHcFQOBTZgUEYAARGBIC0KdlleCAFnkKrrRUxhVvW1rJgqHbdja4L4uHLjNqjMzjtsCt4X6war1tXAUX2py7TmE7hvYsaoAm3JssQpTNQ24gtqq16qo+m72IkqSnpeqZgmva6arh972pBKHUdNd17sBYILsAvEsfYlLaBGqQoggCB\/o7CBHBgAA5GA+Q+GH2I2PHGHJFGObS+kuYAQiRiBseS1LiR2KqAGEBtgVrKzx1GLCAA==\" rel=\"noopener noreferrer nofollow\">looks like<\/a>:<\/p>\n<pre><code>    L003e: add esi, eax     L0040: mov eax, [rsp+0x38]     L0044: add eax, [rsp+0x34]     L0048: mov [rsp+0x38], eax     L004c: mov eax, [rsp+0x38]     L0050: cmp eax, [rsp+0x30]     L0054: jne short L003a<\/code><\/pre>\n<p>Despite the enumerator being a struct, and hence, on stack, it&#8217;s still read from RAM. Let&#8217;s see what else we can do!<\/p>\n<h2>Experiment 4. Separating the enumerator into a local function<\/h2>\n<p>Why&#8230;? Because what we <em>want<\/em> is in fact to put the enumerator itself onto <em>registers<\/em> instead of reading\/writing from\/to RAM all the time. But probably because of too many local variables, the JIT compiler decided not to put our struct onto registers, so let us help it a bit.<\/p>\n<p>From experiment 3 we have<\/p>\n<pre><code class=\"cs\">[Benchmark] public int ForeachWithRangeEnumeratorRaw() {     var a = 0;     var enumerator = (0..(Iterations - 1)).GetEnumerator();     while (enumerator.MoveNext())         a += enumerator.Current;     return a; }<\/code><\/pre>\n<p>Note, that I replaced <code>foreach<\/code> with the equivalent code, and added <code>Raw<\/code> as postfix. In Experiment 3 it&#8217;s shown how <code>foreach<\/code> works under the hood &#8212; the same as I wrote manually.<\/p>\n<p>To reduce the number of local variables, we move the loop to a local function:<\/p>\n<pre><code class=\"cs\">[Benchmark] public int ForeachWithRangeEnumeratorRawWithLocal() {     var enumerator = (0..(Iterations - 1)).GetEnumerator();             return EnumerateItAll(enumerator);      static int EnumerateItAll(RangeEnumerator enumerator)     {         var a = 0;         while (enumerator.MoveNext())             a += enumerator.Current;         return a;     } }<\/code><\/pre>\n<p>And run the benchmark:<\/p>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p>Method<\/p>\n<\/th>\n<th>\n<p>Iterations<\/p>\n<\/th>\n<th>\n<p>Mean<\/p>\n<\/th>\n<th>\n<p>Error<\/p>\n<\/th>\n<th>\n<p>StdDev<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p>ForWithCustomIncrement<\/p>\n<\/td>\n<td>\n<p>10000<\/p>\n<\/td>\n<td>\n<p>3.498 us<\/p>\n<\/td>\n<td>\n<p>0.0658 us<\/p>\n<\/td>\n<td>\n<p>0.1153 us<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>ForeachWithEnumerableRange<\/p>\n<\/td>\n<td>\n<p>10000<\/p>\n<\/td>\n<td>\n<p>43.313 us<\/p>\n<\/td>\n<td>\n<p>0.8207 us<\/p>\n<\/td>\n<td>\n<p>1.0079 us<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>ForeachWithYieldReturn<\/p>\n<\/td>\n<td>\n<p>10000<\/p>\n<\/td>\n<td>\n<p>56.963 us<\/p>\n<\/td>\n<td>\n<p>1.0638 us<\/p>\n<\/td>\n<td>\n<p>1.0448 us<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>ForeachWithRangeEnumerator<\/p>\n<\/td>\n<td>\n<p>10000<\/p>\n<\/td>\n<td>\n<p>17.931 us<\/p>\n<\/td>\n<td>\n<p>0.2047 us<\/p>\n<\/td>\n<td>\n<p>0.1915 us<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>ForeachWithRangeEnumeratorRaw<\/p>\n<\/td>\n<td>\n<p>10000<\/p>\n<\/td>\n<td>\n<p>17.932 us<\/p>\n<\/td>\n<td>\n<p>0.1486 us<\/p>\n<\/td>\n<td>\n<p>0.1390 us<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>ForeachWithRangeEnumeratorRawWithLocal<\/p>\n<\/td>\n<td>\n<p>10000<\/p>\n<\/td>\n<td>\n<p>3.501 us<\/p>\n<\/td>\n<td>\n<p>0.0678 us<\/p>\n<\/td>\n<td>\n<p>0.0807 us<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p><code>ForeachWithRangeEnumeratorRawWithLocal<\/code> is the last method we wrote &#8212; and it&#8217;s almost <strong>6 times<\/strong> faster, than the one with the loop inlined into the body! What happened?<\/p>\n<p>Let&#8217;s look at the <a href=\"https:\/\/sharplab.io\/#v2:EYLgxg9gTgpgtADwGwBYA0AXEBDAzgWwB8ABAJgEYBYAKBuIGYACMxgIRgDswALfbKANa4aAbxqMJzJgEsOGRgEkMMKNgzSIHXIxGMA5jAwBuRrkMmAvjXGSADlGkA3NTEaz5Crjv3nTvi4wAvIzkJgD0YYzAAJ6MsNgAJrJ6jABmUBD4adIwADYJYfYQtioY0WiMGNyuAFIKACqMkPi20rkqjADumgDk8sXq+NIAXq7S8hgQbl6yuBhQAK5g6prW1JKMaxsM0\/IAYtAw2DwA6uPcAErYHAYAohwL+Cpq0FedZ1UAMhBg2LkAFABKGwSMTrDaSZxQRicR7PSbQ4L\/AAMADpUf8lPCNFpGHAQoDAaiAOKGe5w1QIoEmCESEEQ4gAdkY5KelJgSgAgrkAbC2S8oICjFtacxyEhdiyHvzlFyef8rjcYKz4dAYdLVYL6RswaKIVDGNggoxkcLwXqJJ1uG1XP8+ZrUQBZCCOGAAORgCAwQOB5otGyNAGpgvbKdBUQBhBZQWByM3+iRMw3x0VWP1pjb0mhpnbEcXMUgsr2cXA44R+3Wi3P5xV3DVh6GkjAqhv\/KrSbS11zEYjt3C+hNBAB8jH+Pb7qIAyhh+BgKuPrbhUfcEoDTJ1xjxtaLK4OJP9dApcHsMvgVyBKosYBUAGp\/BYwC\/IxgWCqH4+n8+Xh+3++Pk0vmugQjhwMCdIwXYtgKKIVO4TrYAgd65D+BJoNu\/oHooH6ZF+8woUhD5Pi+b5YSeOEcAkF6pH8Zi\/sh\/4GpMgHDowoHgZB9bQciFRMYGIQVOQgJoX6g6YUeZFnhRVE0dejAEQx\/BpKexHeOJn5Sd+snyURFhASBYEQdcdYUtB6SZLBcjwYhf4CUJ6EWmJ2GSZRaQyXRhGMAaZlZK+qlOV+1G5LRcl\/hejFTLp9mDsBo7eYwAA8lQQGuuAbhgW4iXuoJRVlmksWxhlKlBVLeTxEC2cJuUWoFZj5QZHEmSVp5lRUcCCZVVWSK+OW0gA+ixVQZOBBWeM4uTSAkADyJSUjitwIGAMC2CsHD\/AARKNfwTXERkwGtA4JhYKYSGmIqJkwcyLMshXGTK0D0ruEL2E4LhxEcCSaLksTuElx1PQ4zjKG9iSfd9cimMothmvZz2A2M4NgNGUDQ5lEjuCoHB\/MwKCMAAIjAkBaHhyz\/BACzyD9PalYwZMU+DkwVLTkpzEtB3+jFY7ENTDMQ6zxr\/IjMY8YuqI8xOLO2EKaz2ejUCY7kN3KpxVKU1zzWSjzP0S2zEKPXqE5McEvG81DPWVCLEvGhLf36yLguIswauZHiJs2110uo1IUQQBACvOq6HpekC9l66K9uMMGrtm0m4cAIRGxAbsnWdDIyODUYxpw8gxfbf0WEAA=\" rel=\"noopener noreferrer nofollow\">codegen<\/a>.<\/p>\n<p>In our method the local function hasn&#8217;t been inlined:<\/p>\n<pre><code>Benchmarks.ForeachWithRangeEnumeratorRawWithLocal()     ...     L0044: call Benchmarks.&lt;ForeachWithRangeEnumeratorRawWithLocal>g__EnumerateItAll|8_0(RangeEnumerator)<\/code><\/pre>\n<p>And it&#8217;s good news. Let&#8217;s have a look at our local function:<\/p>\n<pre><code>    L0000: mov eax, [rcx]     L0002: mov edx, [rcx+4]     L0005: mov ecx, [rcx+8]     L0008: xor r8d, r8d     L000b: jmp short L0010 >>  L000d: add r8d, ecx          >>  L0010: add ecx, edx          >>  L0012: cmp ecx, eax          >>  L0014: jne short L000d           L0016: mov eax, r8d     L0019: ret<\/code><\/pre>\n<p>The four marked lines<\/p>\n<pre><code>    L000d: add r8d, ecx     L0010: add ecx, edx     L0012: cmp ecx, eax     L0014: jne short L000d<\/code><\/pre>\n<p>are what we were trying to do. It&#8217;s identical to the codegen for the <code>for<\/code> loop!<\/p>\n<p>The first three lines<\/p>\n<pre><code>    L0000: mov eax, [rcx]     L0002: mov edx, [rcx+4]     L0005: mov ecx, [rcx+8]<\/code><\/pre>\n<p>simply load the local variables from stack onto registers. That is why our method is so fast.<\/p>\n<h2>Windows vs Linux<\/h2>\n<p>There is in fact a huge difference between what was generated on windows vs linux. What I wrote before is how it works on Windows.<\/p>\n<p>To illustrate the difference I created a <a href=\"https:\/\/gist.github.com\/WhiteBlackGoose\/5c567281c0b6bbac816ae0d48607ccba\" rel=\"noopener noreferrer nofollow\">gist<\/a> for comparing codegens for <code>ForeachWithRangeEnumeratorRaw<\/code> and <code>ForeachWithRangeEnumeratorRawWithLocal<\/code>. Let me go over the key differences:<\/p>\n<p>In case of <code>ForeachWithRangeEnumeratorRaw<\/code> method, Linux&#8217;s JIT generates:<\/p>\n<pre><code>M00_L00:        add       ebx,esi        add       esi,edi        cmp       esi,eax        jne       short M00_L00<\/code><\/pre>\n<p>Whereas Windows&#8217; JIT yields this:<\/p>\n<pre><code>M00_L00:        add       esi,[rsp+38]        mov       eax,[rsp+38]        add       eax,[rsp+34]        mov       [rsp+38],eax        mov       eax,[rsp+38]        cmp       eax,[rsp+30]        jne       short M00_L00<\/code><\/pre>\n<p>Unlike that one, in case of <code>ForeachWithRa<\/code><\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-414405","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/414405","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=414405"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/414405\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=414405"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=414405"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=414405"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}