<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://richardcocks.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://richardcocks.github.io/" rel="alternate" type="text/html" /><updated>2026-07-18T21:36:39+00:00</updated><id>https://richardcocks.github.io/feed.xml</id><title type="html">Richard Cocks</title><subtitle>Posts on C# / .NET performance, benchmarking, and the occasional rabbit hole.</subtitle><author><name>Richard Cocks</name></author><entry><title type="html">Performance Pitfalls in C# / .NET - List Contains</title><link href="https://richardcocks.github.io/2025/08/09/PerformancePitfalls" rel="alternate" type="text/html" title="Performance Pitfalls in C# / .NET - List Contains" /><published>2025-08-09T00:00:00+00:00</published><updated>2025-08-09T00:00:00+00:00</updated><id>https://richardcocks.github.io/2025/08/09/PerformancePitfalls</id><content type="html" xml:base="https://richardcocks.github.io/2025/08/09/PerformancePitfalls"><![CDATA[<h1 id="performance-pitfalls-in-c--net---list-contains">Performance Pitfalls in C# / .NET - List Contains</h1>

<p>A day ago, reddit user zigs asked on the csharp subreddit:</p>

<blockquote>
  <p><a href="https://np.reddit.com/r/csharp/comments/1mkrlcc/what_is_the_lowest_effort_highest_impact_helper/">What is the lowest effort, highest impact helper method you’ve ever written?</a></p>
</blockquote>

<p>This proved a popular post, and one of the <a href="https://np.reddit.com/r/csharp/comments/1mkrlcc/what_is_the_lowest_effort_highest_impact_helper/n7kuuii/">more popular answers from user _mattmc3_</a> was:</p>

<blockquote>
  <table>
    <tbody>
      <tr>
        <td>I’ve written a lot of SQL in my years as a developer, so foo IN(1, 2, 3) is a more intuitive way to express the concept to me than foo == 1</td>
        <td> </td>
        <td>foo == 2</td>
        <td> </td>
        <td>foo == 3 or even new int[] {1,2,3}.Contains(foo). Having foo being first just makes more sense, so I have a handy IsIn() extension method so I can write foo.IsIn(1, 2, 3):</td>
      </tr>
    </tbody>
  </table>
</blockquote>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">public</span> <span class="k">static</span> <span class="kt">bool</span> <span class="n">IsIn</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="k">this</span> <span class="n">T</span> <span class="n">obj</span><span class="p">,</span> <span class="k">params</span> <span class="n">T</span><span class="p">[]</span> <span class="n">values</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">foreach</span> <span class="p">(</span><span class="n">T</span> <span class="n">val</span> <span class="k">in</span> <span class="n">values</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">val</span><span class="p">.</span><span class="nf">Equals</span><span class="p">(</span><span class="n">obj</span><span class="p">))</span> <span class="k">return</span> <span class="k">true</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="k">false</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">static</span> <span class="kt">bool</span> <span class="n">IsIn</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="k">this</span> <span class="n">T</span> <span class="n">obj</span><span class="p">,</span> <span class="n">IComparer</span> <span class="n">comparer</span><span class="p">,</span> <span class="k">params</span> <span class="n">T</span><span class="p">[]</span> <span class="n">values</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">foreach</span> <span class="p">(</span><span class="n">T</span> <span class="n">val</span> <span class="k">in</span> <span class="n">values</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">comparer</span><span class="p">.</span><span class="nf">Compare</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">val</span><span class="p">)</span> <span class="p">==</span> <span class="m">0</span><span class="p">)</span> <span class="k">return</span> <span class="k">true</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="k">false</span><span class="p">;</span>
    <span class="p">}</span>
</code></pre></div></div>

<p>There was some discussion about this, pointing out that newer versions of C# supports <code class="language-plaintext highlighter-rouge">params ReadOnlySpan&lt;T&gt;</code> to avoid allocations.</p>

<p>I decided to benchmark a simple scenario where you might use this.</p>

<h2 id="benchmark">Benchmark</h2>

<p>Let’s say we have a list of 10,000 numbers which randomly range from 0 to 63 inclusive. We want to count how many of these are 1, 3 or 7.  That sounds abstract, but it’s a fairly common real-world pattern. Although in the real world, this might actually be counting a list of fruits for bananas, strawberries and mangoes. In general this scenario comes up when you have a set of items whose properties are generally known at compile time, but whose domain is open to future expansion so it doesn’t fit into a flags enum.</p>

<p>Our setup is therefore straightforward, we create the list of numbers.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="p">[</span><span class="n">GlobalSetup</span><span class="p">]</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">Setup</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="m">10</span><span class="n">_000</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
        <span class="p">{</span>
            <span class="n">keys</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="p">=</span> <span class="n">Random</span><span class="p">.</span><span class="n">Shared</span><span class="p">.</span><span class="nf">Next</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="m">63</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
</code></pre></div></div>

<p>As our baseline, we’ll compare approaches to perhaps the most natural thing to do: create a list and use <code class="language-plaintext highlighter-rouge">Contains</code>:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="p">[</span><span class="nf">Benchmark</span><span class="p">(</span><span class="n">Baseline</span> <span class="p">=</span> <span class="k">true</span><span class="p">)]</span>
    <span class="k">public</span> <span class="kt">int</span> <span class="nf">Contains</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="n">keys</span><span class="p">.</span><span class="nf">Count</span><span class="p">(</span><span class="n">key</span> <span class="p">=&gt;</span> <span class="k">new</span> <span class="kt">int</span><span class="p">[]</span> <span class="p">{</span> <span class="m">1</span><span class="p">,</span> <span class="m">3</span><span class="p">,</span> <span class="m">7</span> <span class="p">}.</span><span class="nf">Contains</span><span class="p">(</span><span class="n">key</span><span class="p">));</span>
    <span class="p">}</span>
</code></pre></div></div>

<p>We want to compare this to the helper method suggested, as well as the suggested <code class="language-plaintext highlighter-rouge">ReadOnlySpan</code> version:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="p">[</span><span class="n">Benchmark</span><span class="p">]</span>
    <span class="k">public</span> <span class="kt">int</span> <span class="nf">IsIn</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="n">keys</span><span class="p">.</span><span class="nf">Count</span><span class="p">(</span><span class="n">key</span> <span class="p">=&gt;</span> <span class="n">key</span><span class="p">.</span><span class="nf">IsIn</span><span class="p">(</span><span class="m">1</span><span class="p">,</span> <span class="m">3</span><span class="p">,</span> <span class="m">7</span><span class="p">));</span>
    <span class="p">}</span>

    <span class="p">[</span><span class="n">Benchmark</span><span class="p">]</span>
    <span class="k">public</span> <span class="kt">int</span> <span class="nf">IsInReadOnly</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="n">keys</span><span class="p">.</span><span class="nf">Count</span><span class="p">(</span><span class="n">key</span> <span class="p">=&gt;</span> <span class="n">key</span><span class="p">.</span><span class="nf">IsInReadOnly</span><span class="p">(</span><span class="m">1</span><span class="p">,</span> <span class="m">3</span><span class="p">,</span> <span class="m">7</span><span class="p">));</span>
    <span class="p">}</span>
</code></pre></div></div>

<p>We should also compare it to the simplest approach, <code class="language-plaintext highlighter-rouge">if</code> or <code class="language-plaintext highlighter-rouge">switch case</code>:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="p">[</span><span class="n">Benchmark</span><span class="p">]</span>
    <span class="k">public</span> <span class="kt">int</span> <span class="nf">WithIf</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="n">keys</span><span class="p">.</span><span class="nf">Count</span><span class="p">(</span><span class="n">key</span> <span class="p">=&gt;</span> <span class="n">key</span> <span class="p">==</span> <span class="m">1</span> <span class="p">||</span> <span class="n">key</span> <span class="p">==</span> <span class="m">3</span> <span class="p">||</span> <span class="n">key</span> <span class="p">==</span> <span class="m">7</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="p">[</span><span class="n">Benchmark</span><span class="p">]</span>
    <span class="k">public</span> <span class="kt">int</span> <span class="nf">WithSwitch</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="n">keys</span><span class="p">.</span><span class="nf">Count</span><span class="p">(</span><span class="n">key</span> <span class="p">=&gt;</span>
        <span class="p">{</span>
            <span class="k">switch</span> <span class="p">(</span><span class="n">key</span><span class="p">)</span>
            <span class="p">{</span>
                <span class="k">case</span> <span class="m">1</span><span class="p">:</span>
                <span class="k">case</span> <span class="m">3</span><span class="p">:</span>
                <span class="k">case</span> <span class="m">7</span><span class="p">:</span>
                    <span class="k">return</span> <span class="k">true</span><span class="p">;</span>
            <span class="p">}</span>
            <span class="k">return</span> <span class="k">false</span><span class="p">;</span>
        <span class="p">});</span>
    <span class="p">}</span>
</code></pre></div></div>

<h2 id="results">Results</h2>

<table>
  <thead>
    <tr>
      <th>Method</th>
      <th>Job</th>
      <th>Runtime</th>
      <th style="text-align: right">Mean</th>
      <th style="text-align: right">Error</th>
      <th style="text-align: right">StdDev</th>
      <th style="text-align: right">Median</th>
      <th style="text-align: right">Ratio</th>
      <th style="text-align: right">RatioSD</th>
      <th style="text-align: right">Gen0</th>
      <th style="text-align: right">Allocated</th>
      <th style="text-align: right">Alloc Ratio</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>IsIn</td>
      <td>.NET 8.0</td>
      <td>.NET 8.0</td>
      <td style="text-align: right">192.533 us</td>
      <td style="text-align: right">3.8374 us</td>
      <td style="text-align: right">6.8209 us</td>
      <td style="text-align: right">193.961 us</td>
      <td style="text-align: right">1.87</td>
      <td style="text-align: right">0.08</td>
      <td style="text-align: right">132.3242</td>
      <td style="text-align: right">1107792 B</td>
      <td style="text-align: right">2.769</td>
    </tr>
    <tr>
      <td>IsIn</td>
      <td>.NET 10.0</td>
      <td>.NET 10.0</td>
      <td style="text-align: right">126.626 us</td>
      <td style="text-align: right">2.5270 us</td>
      <td style="text-align: right">4.4259 us</td>
      <td style="text-align: right">126.874 us</td>
      <td style="text-align: right">1.23</td>
      <td style="text-align: right">0.05</td>
      <td style="text-align: right">132.5684</td>
      <td style="text-align: right">1109488 B</td>
      <td style="text-align: right">2.773</td>
    </tr>
    <tr>
      <td>IsInReadOnly</td>
      <td>.NET 8.0</td>
      <td>.NET 8.0</td>
      <td style="text-align: right">146.813 us</td>
      <td style="text-align: right">7.2935 us</td>
      <td style="text-align: right">21.5051 us</td>
      <td style="text-align: right">158.514 us</td>
      <td style="text-align: right">1.43</td>
      <td style="text-align: right">0.21</td>
      <td style="text-align: right">84.7168</td>
      <td style="text-align: right">708872 B</td>
      <td style="text-align: right">1.772</td>
    </tr>
    <tr>
      <td>IsInReadOnly</td>
      <td>.NET 10.0</td>
      <td>.NET 10.0</td>
      <td style="text-align: right">105.307 us</td>
      <td style="text-align: right">5.0441 us</td>
      <td style="text-align: right">14.8725 us</td>
      <td style="text-align: right">100.645 us</td>
      <td style="text-align: right">1.02</td>
      <td style="text-align: right">0.15</td>
      <td style="text-align: right">84.7168</td>
      <td style="text-align: right">708696 B</td>
      <td style="text-align: right">1.772</td>
    </tr>
    <tr>
      <td>Contains</td>
      <td>.NET 8.0</td>
      <td>.NET 8.0</td>
      <td style="text-align: right">103.059 us</td>
      <td style="text-align: right">2.0413 us</td>
      <td style="text-align: right">2.8616 us</td>
      <td style="text-align: right">101.683 us</td>
      <td style="text-align: right">1.00</td>
      <td style="text-align: right">0.04</td>
      <td style="text-align: right">47.7295</td>
      <td style="text-align: right">400032 B</td>
      <td style="text-align: right">1.000</td>
    </tr>
    <tr>
      <td>Contains</td>
      <td>.NET 10.0</td>
      <td>.NET 10.0</td>
      <td style="text-align: right">73.218 us</td>
      <td style="text-align: right">1.4635 us</td>
      <td style="text-align: right">1.6267 us</td>
      <td style="text-align: right">72.464 us</td>
      <td style="text-align: right">0.71</td>
      <td style="text-align: right">0.02</td>
      <td style="text-align: right">47.7295</td>
      <td style="text-align: right">400000 B</td>
      <td style="text-align: right">1.000</td>
    </tr>
    <tr>
      <td>WithIf</td>
      <td>.NET 8.0</td>
      <td>.NET 8.0</td>
      <td style="text-align: right">23.081 us</td>
      <td style="text-align: right">0.1456 us</td>
      <td style="text-align: right">0.1362 us</td>
      <td style="text-align: right">23.095 us</td>
      <td style="text-align: right">0.22</td>
      <td style="text-align: right">0.01</td>
      <td style="text-align: right">-</td>
      <td style="text-align: right">32 B</td>
      <td style="text-align: right">0.000</td>
    </tr>
    <tr>
      <td>WithIf</td>
      <td>.NET 10.0</td>
      <td>.NET 10.0</td>
      <td style="text-align: right">6.826 us</td>
      <td style="text-align: right">0.0714 us</td>
      <td style="text-align: right">0.0633 us</td>
      <td style="text-align: right">6.792 us</td>
      <td style="text-align: right">0.07</td>
      <td style="text-align: right">0.00</td>
      <td style="text-align: right">-</td>
      <td style="text-align: right">-</td>
      <td style="text-align: right">0.000</td>
    </tr>
    <tr>
      <td>WithSwitch</td>
      <td>.NET 8.0</td>
      <td>.NET 8.0</td>
      <td style="text-align: right">22.848 us</td>
      <td style="text-align: right">0.4243 us</td>
      <td style="text-align: right">0.3969 us</td>
      <td style="text-align: right">22.757 us</td>
      <td style="text-align: right">0.22</td>
      <td style="text-align: right">0.01</td>
      <td style="text-align: right">-</td>
      <td style="text-align: right">32 B</td>
      <td style="text-align: right">0.000</td>
    </tr>
    <tr>
      <td>WithSwitch</td>
      <td>.NET 10.0</td>
      <td>.NET 10.0</td>
      <td style="text-align: right">9.770 us</td>
      <td style="text-align: right">0.1027 us</td>
      <td style="text-align: right">0.0960 us</td>
      <td style="text-align: right">9.756 us</td>
      <td style="text-align: right">0.09</td>
      <td style="text-align: right">0.00</td>
      <td style="text-align: right">-</td>
      <td style="text-align: right">-</td>
      <td style="text-align: right">0.000</td>
    </tr>
  </tbody>
</table>

<p><img src="/assets/img/1benchmark.png" alt="Benchmark results graph" title="Lower is better" /></p>

<h2 id="net-framework">.NET Framework</h2>

<p>I also ran a comparison on .NET Framework:</p>

<table>
  <thead>
    <tr>
      <th>Method</th>
      <th>Job</th>
      <th>Runtime</th>
      <th style="text-align: right">Mean</th>
      <th style="text-align: right">Error</th>
      <th style="text-align: right">StdDev</th>
      <th style="text-align: right">Median</th>
      <th style="text-align: right">Ratio</th>
      <th style="text-align: right">RatioSD</th>
      <th style="text-align: right">Gen0</th>
      <th style="text-align: right">Allocated</th>
      <th style="text-align: right">Alloc Ratio</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>IsIn</td>
      <td>.NET Framework 4.8.1</td>
      <td>.NET Framework 4.8.1</td>
      <td style="text-align: right">186.539 us</td>
      <td style="text-align: right">1.9634 us</td>
      <td style="text-align: right">1.5329 us</td>
      <td style="text-align: right">186.937 us</td>
      <td style="text-align: right">1.75</td>
      <td style="text-align: right">0.07</td>
      <td style="text-align: right">176.5137</td>
      <td style="text-align: right">1111075 B</td>
      <td style="text-align: right">2.777</td>
    </tr>
    <tr>
      <td>IsInReadOnly</td>
      <td>.NET Framework 4.8.1</td>
      <td>.NET Framework 4.8.1</td>
      <td style="text-align: right">241.478 us</td>
      <td style="text-align: right">4.1324 us</td>
      <td style="text-align: right">3.6633 us</td>
      <td style="text-align: right">242.215 us</td>
      <td style="text-align: right">2.27</td>
      <td style="text-align: right">0.09</td>
      <td style="text-align: right">112.7930</td>
      <td style="text-align: right">709897 B</td>
      <td style="text-align: right">1.775</td>
    </tr>
    <tr>
      <td>Contains</td>
      <td>.NET Framework 4.8.1</td>
      <td>.NET Framework 4.8.1</td>
      <td style="text-align: right">560.785 us</td>
      <td style="text-align: right">9.9977 us</td>
      <td style="text-align: right">9.3518 us</td>
      <td style="text-align: right">565.321 us</td>
      <td style="text-align: right">5.26</td>
      <td style="text-align: right">0.21</td>
      <td style="text-align: right">63.4766</td>
      <td style="text-align: right">401204 B</td>
      <td style="text-align: right">1.003</td>
    </tr>
    <tr>
      <td>WithIf</td>
      <td>.NET Framework 4.8.1</td>
      <td>.NET Framework 4.8.1</td>
      <td style="text-align: right">59.772 us</td>
      <td style="text-align: right">0.7011 us</td>
      <td style="text-align: right">0.6558 us</td>
      <td style="text-align: right">59.798 us</td>
      <td style="text-align: right">0.56</td>
      <td style="text-align: right">0.02</td>
      <td style="text-align: right">-</td>
      <td style="text-align: right">31 B</td>
      <td style="text-align: right">0.000</td>
    </tr>
    <tr>
      <td>WithSwitch</td>
      <td>.NET Framework 4.8.1</td>
      <td>.NET Framework 4.8.1</td>
      <td style="text-align: right">64.200 us</td>
      <td style="text-align: right">1.1971 us</td>
      <td style="text-align: right">1.2294 us</td>
      <td style="text-align: right">64.313 us</td>
      <td style="text-align: right">0.60</td>
      <td style="text-align: right">0.03</td>
      <td style="text-align: right">-</td>
      <td style="text-align: right">31 B</td>
      <td style="text-align: right">0.000</td>
    </tr>
  </tbody>
</table>

<p>Without native <code class="language-plaintext highlighter-rouge">ReadOnlySpan&lt;T&gt;</code> support, that version is much slower than <code class="language-plaintext highlighter-rouge">params T[]</code>, despite fewer allocations.</p>

<p>With the lack of LINQ optimisation in .NET Framework, <code class="language-plaintext highlighter-rouge">Contains</code> is by far the worst performing here.</p>

<h2 id="conclusion">Conclusion</h2>

<p>The biggest surprise for me here was actually the performance difference between .NET 8 and .NET 10 for the naive <code class="language-plaintext highlighter-rouge">if</code> statement method. A dramatic improvement between .NET 8 and .NET 10 for a simple set of <code class="language-plaintext highlighter-rouge">if</code> statements suggests JIT improvements have made a big difference.</p>

<p>The <code class="language-plaintext highlighter-rouge">ReadOnlySpan&lt;T&gt;</code> approach avoided some allocations, but it still allocated more than the <code class="language-plaintext highlighter-rouge">Contains</code> approach, which was still more than the <code class="language-plaintext highlighter-rouge">switch / case</code> version.</p>

<p>For such a popular and tempting helper method, this is a real performance trap. Overall the worst performing version was 28x slower than hard-coded <code class="language-plaintext highlighter-rouge">if</code> statements.</p>

<p>As with any micro-benchmark, it’s important to stress that this is only important if profiling your real-world use case demonstrates it.</p>

<p>Always profile your real-world application and let that guide any optimisation you do. I have seen this pattern impact performance in a real world application, so keep it in mind.</p>

<p>Always profile within the context of your application. Blindly removing the helper method and replacing with <code class="language-plaintext highlighter-rouge">Contains</code> would be a disaster if you were still on .NET Framework.</p>

<h2 id="source-code">Source Code</h2>

<p>The source code to generate these results is available at https://github.com/richardcocks/IsInList .</p>

<p>Pull requests always welcome.</p>]]></content><author><name>Richard Cocks</name></author><summary type="html"><![CDATA[Looking at the performance of a C# helper method for Contains]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://richardcocks.github.io/assets/img/1benchmark.png" /><media:content medium="image" url="https://richardcocks.github.io/assets/img/1benchmark.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">My SO question was closed so I’m writing this post on CoreWCF</title><link href="https://richardcocks.github.io/2025/05/08/CoreWCF" rel="alternate" type="text/html" title="My SO question was closed so I’m writing this post on CoreWCF" /><published>2025-05-08T00:00:00+00:00</published><updated>2025-05-08T00:00:00+00:00</updated><id>https://richardcocks.github.io/2025/05/08/CoreWCF</id><content type="html" xml:base="https://richardcocks.github.io/2025/05/08/CoreWCF"><![CDATA[<h1 id="my-stackoverflow-question-was-closed-so-heres-a-blog-post-about-corewcf">My stackoverflow question was closed, so here’s a blog post about CoreWCF</h1>

<p>I’m preparing a blog post on remote procedure calls (RPC) and Interprocess Communication between .NET Framework and dotnet 8, but while doing so I ran into an issue with a service getting stuck consuming CPU after a client has closed.</p>

<p>It’s not a good fit for asking Claude, etc., because there’s too much confusion on whether you’re talking about CoreWCF, WCF client on .Net Core (<code class="language-plaintext highlighter-rouge">System.ServiceModel</code> nuget package version ), or WCF on .NET Framework ( <code class="language-plaintext highlighter-rouge">System.ServiceModel</code> from the framework ). This is confusing enough for humans, let alone a machine that will happily reproduce calls and configuration from something that is almost identical, yet subtly different and incompatible.</p>

<p>I didn’t want to raise a github issue, because I doubt I’ve found a bug in WCF, especially since it feels like what I’m trying to do is probably a misunderstanding of how WCF streams are supposed to work.  I’m also quickly hitting maximum message lengths, and that feels like a red flag that I’m not using it as intended.</p>

<p>I took my question to StackOverflow (SO). I’m not a stranger to SO, although it’s been a while since I have contributed anything there. This turned out to be a mistake, because I rushed asking my question. I extracted the classes I thought were pertinent to the question and that I thought could reproduce the issue. I skipped over some boilerplate starting Kestrel, but I also included a link to my repo in case answerers wanted to see the full context of the servicescalled. I had forgotten that any external links are a big no-no in SO land, so my question immediately attracted 2 close votes.</p>

<p>I went back, rewrote the entire question, now with a complete minimal reproduction of the issue. Client and Server fully complete in 3 source files. It now contained all the code needed to reproduce the issue, and nothing  but that code.</p>

<p>Two days later my question got it’s third vote for closure, and remains unanswered and now closed forever.</p>

<p>In my frustration I’m writing this blog post, to briefly introduce CoreWCF and hope someone will be able to answer my question about what I’m doing wrong.</p>

<h2 id="scenario">Scenario</h2>

<p>For the purpose of testing RPC throughput, I want to stream random numbers from one process to another. We can test requesting numbers call-by-call or streamed. Ideally with sequence numbers so we can also examine the reliabilty for certain transport types.</p>

<p>In gRPC, the proto file would look something like this:</p>

<div class="language-proto highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">syntax</span> <span class="o">=</span> <span class="s">"proto3"</span><span class="p">;</span>

<span class="k">option</span> <span class="na">csharp_namespace</span> <span class="o">=</span> <span class="s">"RandomNumberGrpc"</span><span class="p">;</span>

<span class="kn">package</span> <span class="nn">randomService</span><span class="p">;</span>

<span class="kd">service</span> <span class="n">RandomProvider</span> <span class="p">{</span>
  <span class="k">rpc</span> <span class="n">NextInt</span> <span class="p">(</span><span class="n">NextIntRequest</span><span class="p">)</span> <span class="k">returns</span> <span class="p">(</span><span class="n">ValueWithSequence</span><span class="p">);</span>
  <span class="k">rpc</span> <span class="n">Stream</span> <span class="p">(</span><span class="n">NextIntStreamRequest</span><span class="p">)</span> <span class="k">returns</span> <span class="p">(</span><span class="n">stream</span> <span class="n">ValueWithSequence</span><span class="p">);</span>
<span class="p">}</span>

<span class="kd">message</span> <span class="nc">NextIntRequest</span> <span class="p">{}</span>
<span class="kd">message</span> <span class="nc">NextIntStreamRequest</span><span class="p">{}</span>

<span class="kd">message</span> <span class="nc">ValueWithSequence</span> <span class="p">{</span>
  <span class="kt">int32</span> <span class="na">sequenceNumber</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
  <span class="kt">int32</span> <span class="na">Value</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>In WCF, we define services through <code class="language-plaintext highlighter-rouge">ServiceContract</code> attributes. For this post I’ll just focus on the streaming service and ignore the per-call implementation. In WCF, <a href="https://learn.microsoft.com/en-us/dotnet/framework/wcf/samples/stream">as I understand it</a>, we can’t strongly type our stream.  For now therefore, to get streams working, I just made a contract that returns a raw byte <code class="language-plaintext highlighter-rouge">Stream</code>, and will worry about casting or marshalling that to the right structure later.</p>

<p>Let’s look at our service class.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">namespace</span> <span class="nn">RandomNumberCore</span><span class="p">;</span>

<span class="p">[</span><span class="n">ServiceContract</span><span class="p">]</span>
<span class="k">public</span> <span class="k">interface</span> <span class="nc">IStreamingService</span>
<span class="p">{</span>
    <span class="p">[</span><span class="n">OperationContract</span><span class="p">]</span>
    <span class="n">Stream</span> <span class="nf">GetRandomStream</span><span class="p">();</span>
<span class="p">}</span>
    

<span class="k">public</span> <span class="k">class</span> <span class="nc">StreamingService</span> <span class="p">:</span> <span class="n">IStreamingService</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="n">Stream</span> <span class="nf">GetRandomStream</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nf">RandomStream</span><span class="p">(</span><span class="n">Random</span><span class="p">.</span><span class="n">Shared</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Where <code class="language-plaintext highlighter-rouge">RandomStream</code> is my own class that exposes <code class="language-plaintext highlighter-rouge">Random.Shared</code> as a stream, without worrying about returning the sequence number for now:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">namespace</span> <span class="nn">RandomNumberCore</span><span class="p">;</span>

<span class="k">public</span> <span class="k">class</span> <span class="nc">RandomStream</span> <span class="p">:</span> <span class="n">Stream</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="nf">RandomStream</span><span class="p">(</span><span class="n">Random</span> <span class="n">random</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="n">_random</span> <span class="p">=</span> <span class="n">random</span><span class="p">;</span>
    <span class="p">}</span>
    
    <span class="k">private</span> <span class="kt">int</span> <span class="n">_sequence</span><span class="p">;</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="n">Random</span> <span class="n">_random</span><span class="p">;</span>
    <span class="k">public</span> <span class="k">override</span> <span class="kt">bool</span> <span class="n">CanRead</span> <span class="p">=&gt;</span> <span class="k">true</span><span class="p">;</span>

    <span class="k">public</span> <span class="k">override</span> <span class="kt">bool</span> <span class="n">CanSeek</span> <span class="p">=&gt;</span> <span class="k">false</span><span class="p">;</span>

    <span class="k">public</span> <span class="k">override</span> <span class="kt">bool</span> <span class="n">CanWrite</span> <span class="p">=&gt;</span> <span class="k">false</span><span class="p">;</span>

    <span class="k">public</span> <span class="k">override</span> <span class="kt">long</span> <span class="n">Length</span> <span class="p">=&gt;</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">NotSupportedException</span><span class="p">();</span>

    <span class="c1">// ReSharper disable once ValueParameterNotUsed</span>
    <span class="k">public</span> <span class="k">override</span> <span class="kt">long</span> <span class="n">Position</span> <span class="p">{</span> <span class="k">get</span> <span class="p">=&gt;</span> <span class="n">_sequence</span><span class="p">;</span> <span class="k">set</span> <span class="p">=&gt;</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">NotSupportedException</span><span class="p">();</span> <span class="p">}</span>

    <span class="k">public</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">Flush</span><span class="p">()</span>
    <span class="p">{}</span>
    
    <span class="k">public</span> <span class="k">override</span> <span class="kt">int</span> <span class="nf">Read</span><span class="p">(</span><span class="kt">byte</span><span class="p">[]</span> <span class="n">buffer</span><span class="p">,</span> <span class="kt">int</span> <span class="n">offset</span><span class="p">,</span> <span class="kt">int</span> <span class="n">count</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">internalBuffer</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Span</span><span class="p">&lt;</span><span class="kt">byte</span><span class="p">&gt;(</span><span class="n">buffer</span><span class="p">,</span> <span class="n">offset</span><span class="p">,</span> <span class="n">count</span><span class="p">);</span>
        <span class="n">_random</span><span class="p">.</span><span class="nf">NextBytes</span><span class="p">(</span><span class="n">internalBuffer</span><span class="p">);</span>
        <span class="n">_sequence</span><span class="p">+=</span><span class="n">count</span><span class="p">;</span>
        <span class="k">return</span> <span class="n">count</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">override</span> <span class="kt">int</span> <span class="nf">Read</span><span class="p">(</span><span class="n">Span</span><span class="p">&lt;</span><span class="kt">byte</span><span class="p">&gt;</span> <span class="n">buffer</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">_random</span><span class="p">.</span><span class="nf">NextBytes</span><span class="p">(</span><span class="n">buffer</span><span class="p">);</span>
        <span class="n">_sequence</span><span class="p">+=</span><span class="n">buffer</span><span class="p">.</span><span class="n">Length</span><span class="p">;</span>
        <span class="k">return</span> <span class="n">buffer</span><span class="p">.</span><span class="n">Length</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">override</span> <span class="kt">long</span> <span class="nf">Seek</span><span class="p">(</span><span class="kt">long</span> <span class="n">offset</span><span class="p">,</span> <span class="n">SeekOrigin</span> <span class="n">origin</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">throw</span> <span class="k">new</span> <span class="nf">NotSupportedException</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">SetLength</span><span class="p">(</span><span class="kt">long</span> <span class="k">value</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">throw</span> <span class="k">new</span> <span class="nf">NotSupportedException</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">Write</span><span class="p">(</span><span class="kt">byte</span><span class="p">[]</span> <span class="n">buffer</span><span class="p">,</span> <span class="kt">int</span> <span class="n">offset</span><span class="p">,</span> <span class="kt">int</span> <span class="n">count</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">throw</span> <span class="k">new</span> <span class="nf">NotSupportedException</span><span class="p">();</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>A read-only, non-seekable stream that returns random numbers. I’ve not extensively tested this stream, but it appears to work fine.</p>

<p>Let’s write a simple client, including the contract. In a real world application you’d likely define a contract assembly to share the contracts.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">System.ServiceModel</span><span class="p">;</span>

<span class="k">namespace</span> <span class="nn">RandomNumberConsumerNet8</span>
<span class="p">{</span>
    <span class="p">[</span><span class="n">ServiceContract</span><span class="p">]</span>
    <span class="k">public</span> <span class="k">interface</span> <span class="nc">IStreamingService</span>
    <span class="p">{</span>
        <span class="p">[</span><span class="n">OperationContract</span><span class="p">]</span>
        <span class="n">Stream</span> <span class="nf">GetRandomStream</span><span class="p">();</span>
    <span class="p">}</span>
    <span class="k">public</span> <span class="k">interface</span> <span class="nc">IStreamingServiceChannel</span> <span class="p">:</span> <span class="n">IStreamingService</span><span class="p">,</span> <span class="n">IClientChannel</span><span class="p">;</span>

    <span class="k">internal</span> <span class="k">class</span> <span class="nc">Program</span>
    <span class="p">{</span>
        <span class="k">public</span> <span class="k">static</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">Main</span><span class="p">(</span><span class="kt">string</span><span class="p">[]</span> <span class="n">args</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="kt">var</span> <span class="n">cts</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">CancellationTokenSource</span><span class="p">();</span>
            <span class="k">using</span> <span class="nn">var</span> <span class="n">channelFactory</span> <span class="p">=</span> <span class="k">new</span> <span class="n">ChannelFactory</span><span class="p">&lt;</span><span class="n">IStreamingServiceChannel</span><span class="p">&gt;(</span><span class="k">new</span> <span class="nf">BasicHttpBinding</span><span class="p">(</span><span class="n">BasicHttpSecurityMode</span><span class="p">.</span><span class="n">Transport</span><span class="p">){</span><span class="n">TransferMode</span> <span class="p">=</span> <span class="n">TransferMode</span><span class="p">.</span><span class="n">Streamed</span><span class="p">,</span> <span class="n">MaxReceivedMessageSize</span> <span class="p">=</span> <span class="m">1</span><span class="n">_000_000_000</span> <span class="p">},</span> <span class="k">new</span> <span class="nf">EndpointAddress</span><span class="p">(</span><span class="s">"https://localhost:7151/StreamingService.svc"</span><span class="p">));</span>
            <span class="k">using</span> <span class="nn">var</span> <span class="n">service</span> <span class="p">=</span> <span class="n">channelFactory</span><span class="p">.</span><span class="nf">CreateChannel</span><span class="p">();</span>
            <span class="n">service</span><span class="p">.</span><span class="nf">Open</span><span class="p">();</span>
            <span class="k">using</span> <span class="nn">var</span> <span class="n">randomStream</span> <span class="p">=</span> <span class="n">service</span><span class="p">.</span><span class="nf">GetRandomStream</span><span class="p">();</span>
            <span class="kt">byte</span><span class="p">[]</span> <span class="n">buffer</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">byte</span><span class="p">[</span><span class="m">4</span><span class="p">];</span>
            <span class="k">await</span> <span class="n">randomStream</span><span class="p">.</span><span class="nf">ReadExactlyAsync</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="n">cts</span><span class="p">.</span><span class="n">Token</span><span class="p">);</span>
            
            <span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="s">$"Received bytes </span><span class="p">{</span><span class="n">buffer</span><span class="p">[</span><span class="m">0</span><span class="p">]}</span><span class="s"> , </span><span class="p">{</span><span class="n">buffer</span><span class="p">[</span><span class="m">1</span><span class="p">]}</span><span class="s">, </span><span class="p">{</span><span class="n">buffer</span><span class="p">[</span><span class="m">2</span><span class="p">]}</span><span class="s">, </span><span class="p">{</span><span class="n">buffer</span><span class="p">[</span><span class="m">3</span><span class="p">]}</span><span class="s"> "</span><span class="p">);</span>
            <span class="n">service</span><span class="p">.</span><span class="nf">Close</span><span class="p">();</span>
            <span class="n">channelFactory</span><span class="p">.</span><span class="nf">Close</span><span class="p">();</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="results">Results</h2>

<p>Now when I run the server and client, it appears to work:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Received bytes 101 , 18, 99, 251
</code></pre></div></div>

<p>Great, we opened the stream, streamed 4 bytes and then closed service.</p>

<p>But now when I look at my CPU, it’s still chugging along. Profiling the server shows it’s still trying to write bytes to the stream, well after the client has long ago disconnected.</p>

<p><img src="/assets/img/1_WCF_Serializing.png" alt="Server trace" title="It's stuck processing the message" /></p>

<p>What is it doing? It’s trying to write to the stream, with no hint of back-pressure or sense it shouldn’t be doing so. The sending stream doesn’t have any sense that it’s not being read from.</p>

<p>I’m coming to the conclusion that WCF streaming is not suitable for this, and is only suitable for single bounded streams, not for streams of unknown length or a stream of messages.</p>

<p>But it’s not therefore clear what to do in this scenario, of wanting to transfer an unknown quantity of random numbers. Do I go back to requesting numbers via single messages? That has limited throughput. My initial testing showed ~5k messages / sec that way. I could manually increase the buffer so that each message sends a greater quantity of random numbers, but that loses the fidelity, and rather misses the point, this isn’t really about random numbers, it’s about how quickly we can pass messages between applications.</p>

<p>Do I need to use session mode and coordinate the stream externally to the RPC?</p>

<p>I’m not sure if I’ve missed the point of WCF streams or something else about WCF tuning entirely, but what I really wish is that this could have been answered on StackOverflow, so others trying something similarly misguided could have learned from my mistakes.</p>

<h3 id="footnote">Footnote</h3>

<p>Comments? Questions? Want to point out how much of an idiot I’ve been? Please reach out at <a href="https://bsky.app/profile/eterm.bsky.social">@eterm.bsky.social</a>.</p>

<p>Code behind this post is available at https://github.com/richardcocks/randomNumberStackOverflow .</p>]]></content><author><name>Richard Cocks</name></author><summary type="html"><![CDATA[A question about WCF Streams]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://richardcocks.github.io/assets/img/1_WCF_Serializing.png" /><media:content medium="image" url="https://richardcocks.github.io/assets/img/1_WCF_Serializing.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">What’s faster than Memcmp?</title><link href="https://richardcocks.github.io/2025/03/30/FasterThanMemCmp" rel="alternate" type="text/html" title="What’s faster than Memcmp?" /><published>2025-03-30T00:00:00+00:00</published><updated>2025-03-30T00:00:00+00:00</updated><id>https://richardcocks.github.io/2025/03/30/FasterThanMemCmp</id><content type="html" xml:base="https://richardcocks.github.io/2025/03/30/FasterThanMemCmp"><![CDATA[<h1 id="whats-faster-than-memcmp">What’s faster than Memcmp?</h1>

<p>In this post I look at improvements in .NET and using Span<T> for performance and portability.</T></p>

<p>I was examining portability issues in a code base that I wanted to migrate from .NET framework 4.8.1 to .NET8. I discovered use of <code class="language-plaintext highlighter-rouge">msvcrt.dll</code>. I quickly established it is a <a href="https://stackoverflow.com/a/1445405/1635976">popular stackoverflow answer</a> for a fast way to compare byte arrays in .NET</p>

<p>The answer as provided, and faithfully copied into codebases, is this:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="nf">DllImport</span><span class="p">(</span><span class="s">"msvcrt.dll"</span><span class="p">,</span> <span class="n">CallingConvention</span><span class="p">=</span><span class="n">CallingConvention</span><span class="p">.</span><span class="n">Cdecl</span><span class="p">)]</span>
<span class="k">static</span> <span class="k">extern</span> <span class="kt">int</span> <span class="nf">memcmp</span><span class="p">(</span><span class="kt">byte</span><span class="p">[]</span> <span class="n">b1</span><span class="p">,</span> <span class="kt">byte</span><span class="p">[]</span> <span class="n">b2</span><span class="p">,</span> <span class="kt">long</span> <span class="n">count</span><span class="p">);</span>

<span class="k">static</span> <span class="kt">bool</span> <span class="nf">ByteArrayCompare</span><span class="p">(</span><span class="kt">byte</span><span class="p">[]</span> <span class="n">b1</span><span class="p">,</span> <span class="kt">byte</span><span class="p">[]</span> <span class="n">b2</span><span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// Validate buffers are the same length.</span>
    <span class="c1">// This also ensures that the count does not exceed the length of either buffer.  </span>
    <span class="k">return</span> <span class="n">b1</span><span class="p">.</span><span class="n">Length</span> <span class="p">==</span> <span class="n">b2</span><span class="p">.</span><span class="n">Length</span> <span class="p">&amp;&amp;</span> <span class="nf">memcmp</span><span class="p">(</span><span class="n">b1</span><span class="p">,</span> <span class="n">b2</span><span class="p">,</span> <span class="n">b1</span><span class="p">.</span><span class="n">Length</span><span class="p">)</span> <span class="p">==</span> <span class="m">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>A big performance improvement in modern .NET is the <code class="language-plaintext highlighter-rouge">Span&lt;T&gt;</code> type. The documentation describes it as:</p>

<blockquote>
  <p>Provides a type-safe and memory-safe representation of a contiguous region of arbitrary memory.</p>
</blockquote>

<p>That’s not a super helpful description, but the summary is that it’s stack-allocated rather than heap allocated.</p>

<p><code class="language-plaintext highlighter-rouge">Span&lt;T&gt;</code> has an extension method <code class="language-plaintext highlighter-rouge">SequenceEqual&lt;T&gt;(this ReadOnlySpan&lt;T&gt; span, ReadOnlySpan&lt;T&gt; other)</code>, and we’ll see how it fares.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">public</span> <span class="k">static</span> <span class="kt">bool</span> <span class="nf">EqualsSpan</span><span class="p">(</span><span class="n">ReadOnlySpan</span><span class="p">&lt;</span><span class="kt">byte</span><span class="p">&gt;</span> <span class="n">b1</span><span class="p">,</span> <span class="n">ReadOnlySpan</span><span class="p">&lt;</span><span class="kt">byte</span><span class="p">&gt;</span> <span class="n">b2</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="n">b1</span><span class="p">.</span><span class="nf">SequenceEqual</span><span class="p">(</span><span class="n">b2</span><span class="p">);</span>
    <span class="p">}</span>
</code></pre></div></div>

<p>Let’s see how it stacks up against a couple of naive implementations, a <code class="language-plaintext highlighter-rouge">for</code> loop and using <code class="language-plaintext highlighter-rouge">Enumerable.SequenceEquals</code>:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">public</span> <span class="k">static</span> <span class="kt">bool</span> <span class="nf">EqualsLoop</span><span class="p">(</span><span class="kt">byte</span><span class="p">[]</span> <span class="n">b1</span><span class="p">,</span> <span class="kt">byte</span><span class="p">[]</span> <span class="n">b2</span><span class="p">)</span>
    <span class="p">{</span>

        <span class="k">if</span> <span class="p">(</span><span class="n">b1</span><span class="p">.</span><span class="n">Length</span> <span class="p">!=</span> <span class="n">b2</span><span class="p">.</span><span class="n">Length</span><span class="p">)</span> <span class="k">return</span> <span class="k">false</span><span class="p">;</span>
        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">b1</span><span class="p">.</span><span class="n">Length</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
        <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">b1</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="p">!=</span> <span class="n">b2</span><span class="p">[</span><span class="n">i</span><span class="p">])</span> <span class="k">return</span> <span class="k">false</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="k">return</span> <span class="k">true</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">static</span> <span class="kt">bool</span> <span class="nf">EqualsSequenceEqual</span><span class="p">(</span><span class="kt">byte</span><span class="p">[]</span> <span class="n">b1</span><span class="p">,</span> <span class="kt">byte</span><span class="p">[]</span> <span class="n">b2</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="n">b1</span><span class="p">.</span><span class="nf">SequenceEqual</span><span class="p">(</span><span class="n">b2</span><span class="p">);</span>
    <span class="p">}</span>
</code></pre></div></div>

<p>We compare using two identical arrays since this is typically the worst-case for equality checking, and we’re going to benchmark on a range of array sizes: 10 bytes, 1KB, 1MB and 1GB.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="p">[</span><span class="nf">Params</span><span class="p">(</span><span class="m">10</span><span class="p">,</span> <span class="m">1</span><span class="n">_024</span><span class="p">,</span> <span class="m">1</span><span class="n">_048_576</span><span class="p">,</span> <span class="m">1073741824</span><span class="p">)]</span>
    <span class="k">public</span> <span class="kt">int</span> <span class="n">Length</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>

    <span class="kt">byte</span><span class="p">[]</span> <span class="n">first</span><span class="p">;</span>
    <span class="kt">byte</span><span class="p">[]</span> <span class="n">second</span><span class="p">;</span>

    <span class="p">[</span><span class="n">GlobalSetup</span><span class="p">]</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">Setup</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">r</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Random</span><span class="p">(</span><span class="m">0</span><span class="p">);</span>

        <span class="n">first</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">byte</span><span class="p">[</span><span class="n">Length</span><span class="p">];</span>
        <span class="n">second</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">byte</span><span class="p">[</span><span class="n">Length</span><span class="p">];</span>

        <span class="n">r</span><span class="p">.</span><span class="nf">NextBytes</span><span class="p">(</span><span class="n">first</span><span class="p">);</span>
        <span class="n">Array</span><span class="p">.</span><span class="nf">Copy</span><span class="p">(</span><span class="n">first</span><span class="p">,</span> <span class="n">second</span><span class="p">,</span> <span class="n">Length</span><span class="p">);</span>
    <span class="p">}</span>
</code></pre></div></div>

<p>The setup is straightforward, we fill the first array with random data, and copy the data to the second array.</p>

<h2 id="results">Results</h2>

<p><img src="/assets/img/1_dotnet_framework.png" alt="Benchmark results graph" title="Lower is better" /></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>BenchmarkDotNet v0.14.0, Windows 10 (10.0.19045.5679/22H2/2022Update)
AMD Ryzen 7 3800X, 1 CPU, 16 logical and 8 physical cores
.NET SDK 10.0.100-preview.2.25164.34
  [Host]               : .NET 9.0.3 (9.0.325.11113), X64 RyuJIT AVX2
  .NET 8.0             : .NET 8.0.14 (8.0.1425.11118), X64 RyuJIT AVX2
  .NET 9.0             : .NET 9.0.3 (9.0.325.11113), X64 RyuJIT AVX2
  .NET Framework 4.8.1 : .NET Framework 4.8.1 (4.8.9290.0), X64 RyuJIT VectorSize=256

</code></pre></div></div>
<p>| Method        | Job                  | Runtime              | Length         |                   Mean |    Ratio |  RatioSD | Allocated |
|—————|———————-|———————-|—————-|———————–:|———:|———:|———-:|
| <strong>MemCmp</strong>    | <strong>.NET 8.0</strong>         | <strong>.NET 8.0</strong>         | <strong>10</strong>         |           <strong>7.957 ns</strong> | <strong>0.65</strong> | <strong>0.01</strong> |     <strong>-</strong> |
| MemCmp        | .NET 9.0             | .NET 9.0             | 10             |               7.877 ns |     0.64 |     0.01 |         - |
| MemCmp        | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 10             |              12.239 ns |     1.00 |     0.02 |         - |
|               |                      |                      |                |                        |          |          |           |
| Loop          | .NET 8.0             | .NET 8.0             | 10             |               4.390 ns |     0.88 |     0.03 |         - |
| Loop          | .NET 9.0             | .NET 9.0             | 10             |               6.439 ns |     1.29 |     0.05 |         - |
| Loop          | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 10             |               4.995 ns |     1.00 |     0.03 |         - |
|               |                      |                      |                |                        |          |          |           |
| SequenceEqual | .NET 8.0             | .NET 8.0             | 10             |              21.341 ns |     0.15 |     0.00 |         - |
| SequenceEqual | .NET 9.0             | .NET 9.0             | 10             |               7.611 ns |     0.05 |     0.00 |         - |
| SequenceEqual | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 10             |             139.476 ns |     1.00 |     0.02 |      64 B |
|               |                      |                      |                |                        |          |          |           |
| Span          | .NET 8.0             | .NET 8.0             | 10             |               2.394 ns |     0.21 |     0.00 |         - |
| Span          | .NET 9.0             | .NET 9.0             | 10             |               1.624 ns |     0.14 |     0.00 |         - |
| Span          | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 10             |              11.523 ns |     1.00 |     0.01 |         - |
|               |                      |                      |                |                        |          |          |           |
| <strong>MemCmp</strong>    | <strong>.NET 8.0</strong>         | <strong>.NET 8.0</strong>         | <strong>1024</strong>       |          <strong>36.745 ns</strong> | <strong>0.89</strong> | <strong>0.01</strong> |     <strong>-</strong> |
| MemCmp        | .NET 9.0             | .NET 9.0             | 1024           |              36.317 ns |     0.88 |     0.01 |         - |
| MemCmp        | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 1024           |              41.452 ns |     1.00 |     0.01 |         - |
|               |                      |                      |                |                        |          |          |           |
| Loop          | .NET 8.0             | .NET 8.0             | 1024           |             247.326 ns |     0.66 |     0.01 |         - |
| Loop          | .NET 9.0             | .NET 9.0             | 1024           |             246.738 ns |     0.66 |     0.01 |         - |
| Loop          | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 1024           |             372.410 ns |     1.00 |     0.01 |         - |
|               |                      |                      |                |                        |          |          |           |
| SequenceEqual | .NET 8.0             | .NET 8.0             | 1024           |              32.069 ns |    0.003 |     0.00 |         - |
| SequenceEqual | .NET 9.0             | .NET 9.0             | 1024           |              21.439 ns |    0.002 |     0.00 |         - |
| SequenceEqual | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 1024           |           9,542.293 ns |    1.000 |     0.02 |      64 B |
|               |                      |                      |                |                        |          |          |           |
| Span          | .NET 8.0             | .NET 8.0             | 1024           |              12.408 ns |     0.51 |     0.01 |         - |
| Span          | .NET 9.0             | .NET 9.0             | 1024           |              12.310 ns |     0.51 |     0.01 |         - |
| Span          | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 1024           |              24.117 ns |     1.00 |     0.01 |         - |
|               |                      |                      |                |                        |          |          |           |
| <strong>MemCmp</strong>    | <strong>.NET 8.0</strong>         | <strong>.NET 8.0</strong>         | <strong>1048576</strong>    |      <strong>31,477.776 ns</strong> | <strong>0.99</strong> | <strong>0.02</strong> |     <strong>-</strong> |
| MemCmp        | .NET 9.0             | .NET 9.0             | 1048576        |          31,790.009 ns |     1.00 |     0.02 |         - |
| MemCmp        | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 1048576        |          31,693.469 ns |     1.00 |     0.02 |         - |
|               |                      |                      |                |                        |          |          |           |
| Loop          | .NET 8.0             | .NET 8.0             | 1048576        |         247,350.116 ns |     0.65 |     0.01 |         - |
| Loop          | .NET 9.0             | .NET 9.0             | 1048576        |         251,317.223 ns |     0.66 |     0.02 |         - |
| Loop          | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 1048576        |         379,628.993 ns |     1.00 |     0.02 |         - |
|               |                      |                      |                |                        |          |          |           |
| SequenceEqual | .NET 8.0             | .NET 8.0             | 1048576        |          20,974.963 ns |    0.002 |     0.00 |         - |
| SequenceEqual | .NET 9.0             | .NET 9.0             | 1048576        |          20,615.505 ns |    0.002 |     0.00 |         - |
| SequenceEqual | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 1048576        |       9,744,674.688 ns |    1.000 |     0.01 |         - |
|               |                      |                      |                |                        |          |          |           |
| Span          | .NET 8.0             | .NET 8.0             | 1048576        |          20,955.331 ns |     0.99 |     0.02 |         - |
| Span          | .NET 9.0             | .NET 9.0             | 1048576        |          20,598.672 ns |     0.97 |     0.01 |         - |
| Span          | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 1048576        |          21,176.643 ns |     1.00 |     0.02 |         - |
|               |                      |                      |                |                        |          |          |           |
| <strong>MemCmp</strong>    | <strong>.NET 8.0</strong>         | <strong>.NET 8.0</strong>         | <strong>1073741824</strong> | <strong>111,762,734.375 ns</strong> | <strong>1.04</strong> | <strong>0.03</strong> |  <strong>80 B</strong> |
| MemCmp        | .NET 9.0             | .NET 9.0             | 1073741824     |     110,374,794.400 ns |     1.03 |     0.03 |      80 B |
| MemCmp        | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 1073741824     |     107,072,063.077 ns |     1.00 |     0.01 |         - |
|               |                      |                      |                |                        |          |          |           |
| Loop          | .NET 8.0             | .NET 8.0             | 1073741824     |     280,450,679.167 ns |     0.69 |     0.02 |     200 B |
| Loop          | .NET 9.0             | .NET 9.0             | 1073741824     |     523,091,792.857 ns |     1.29 |     0.02 |     400 B |
| Loop          | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 1073741824     |     404,927,735.714 ns |     1.00 |     0.01 |         - |
|               |                      |                      |                |                        |          |          |           |
| SequenceEqual | .NET 8.0             | .NET 8.0             | 1073741824     |      95,954,794.298 ns |    0.010 |     0.00 |      67 B |
| SequenceEqual | .NET 9.0             | .NET 9.0             | 1073741824     |      94,486,122.500 ns |    0.010 |     0.00 |      80 B |
| SequenceEqual | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 1073741824     |   9,944,911,760.000 ns |    1.000 |     0.00 |         - |
|               |                      |                      |                |                        |          |          |           |
| Span          | .NET 8.0             | .NET 8.0             | 1073741824     |      92,945,091.026 ns |     0.97 |     0.01 |      67 B |
| Span          | .NET 9.0             | .NET 9.0             | 1073741824     |      94,375,230.882 ns |     0.99 |     0.03 |      67 B |
| Span          | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 1073741824     |      95,817,247.619 ns |     1.00 |     0.02 |         - |</p>

<p>The first notable result is that for very small arrays, the overhead of calling <code class="language-plaintext highlighter-rouge">memcmp</code>  is a waste compared to a naive loop. In .NET Framework, the loop is overall fastest for 10 element arrays. This is not unexpected, but it’s important to note; don’t try to optimise if you actually have small arrays. The loop does not scale well at all however, and that advantage has completely disappeared by just 1000 elements.</p>

<p>The more biggest difference is between .NET framework and .NET 8. Even the loop is notably faster in .NET 8. There is a bizarre performance regression from .NET 8 to .NET 9 for 1GB arrays, which I will investigate separately to try to confirm that result, it may have been a glitch in the benchmark, given twice the memory allocations and twice the time taken.</p>

<p>When we look at <code class="language-plaintext highlighter-rouge">IEnumerable&lt;T&gt;.SequenceEqual</code>, it is <strong>500 times faster for our 1MB array in .NET 8 than in .NET framework</strong>. In .NET8 and .NET9, <code class="language-plaintext highlighter-rouge">IEnumerable&lt;T&gt;.SequenceEqual</code> is faster than the version of memcmp I have on my machine.</p>

<p>There wasn’t a significant difference between <code class="language-plaintext highlighter-rouge">ReadOnlySpan&lt;T&gt;.SequenceEqual</code> and <code class="language-plaintext highlighter-rouge">IEnumerable&lt;T&gt;.SequenceEqual</code>, with the difference around the margin of error.</p>

<p><code class="language-plaintext highlighter-rouge">memcmp</code> is still a little slower than <code class="language-plaintext highlighter-rouge">SequenceEqual</code> across the board. It’s still very fast, much faster than naive methods, but it’s clearly no longer necessary for achieving high performance for array comparisons. When the original stackoverflow answer was written, there was nothing available in .NET that could come close to that performance, as it was before <code class="language-plaintext highlighter-rouge">Span&lt;T&gt;</code> was added to the runtime.</p>

<p>A benefit of the <code class="language-plaintext highlighter-rouge">ReadOnlySpan&lt;T&gt;</code> implementation over using <code class="language-plaintext highlighter-rouge">IEnumerable&lt;T&gt;.SequenceEqual</code> is that we can also trust that it still perform acceptably when we target .NET Framework.</p>

<h2 id="conclusion">Conclusion</h2>

<p>If you’re using .NET 8 and don’t need to run in the .NET Framework runtime, don’t write your own utility function and just use <code class="language-plaintext highlighter-rouge">IEnumerable&lt;T&gt;.SequenceEqual</code>, it’s incredibly fast and doesn’t need any external dependencies to just work.</p>

<p>If you’re on .NET Framework, then bring in <code class="language-plaintext highlighter-rouge">System.Memory</code> and use <code class="language-plaintext highlighter-rouge">Span&lt;T&gt;.SequenceEquals</code> instead of relying on external C libraries. Make sure any calls to <code class="language-plaintext highlighter-rouge">IEnumerable&lt;T&gt;.SequenceEquals</code> are checked to make sure they aren’t operating on large arrays.</p>

<h2 id="other-considerations">Other considerations</h2>

<p>If you regularly need to compare very large arrays and they are append-only and/or are compared more often than constructed, then it may make sense to avoid the comparison entirely by using a data structure that includes and maintains an order-sensitive hash of its contents. Most negative cases can be discounted through a hash comparison before doing the more expensive array comparison. Rebuilding this hash could be expensive for arrays that shuffle or remove items however.</p>

<h2 id="source-code">Source Code</h2>

<p>The source code to generate these results is available at https://github.com/richardcocks/memcomparison/ .</p>

<p>Pull requests always welcome.</p>]]></content><author><name>Richard Cocks</name></author><summary type="html"><![CDATA[What could be faster than memcmp? How to quickly compare arrays using Span]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://richardcocks.github.io/assets/img/1_dotnet_framework.png" /><media:content medium="image" url="https://richardcocks.github.io/assets/img/1_dotnet_framework.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Fixing a password generator</title><link href="https://richardcocks.github.io/2025/03/24/PasswordGen" rel="alternate" type="text/html" title="Fixing a password generator" /><published>2025-03-24T00:00:00+00:00</published><updated>2025-03-24T00:00:00+00:00</updated><id>https://richardcocks.github.io/2025/03/24/PasswordGen</id><content type="html" xml:base="https://richardcocks.github.io/2025/03/24/PasswordGen"><![CDATA[<h1 id="fixing-a-password-generator">Fixing a Password Generator</h1>

<p>I’ve been <a href="https://xkcd.com/356/">nerd-sniped</a> by co-pilot. I don’t normally have it enabled, but was working on another machine which did. I was implementing a feature when it suggested autocompleting GeneratePassword.</p>

<p>It took me on a journey of benchmarking with BenchmarkDotNet and discovering what did and did not affect performance in microbenchmarking.</p>

<h2 id="co-pilot-output">Co-pilot output</h2>

<p>The original suggestion, straight from co-pilot was:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="kt">string</span> <span class="nf">GeneratePassword</span><span class="p">(</span><span class="kt">int</span> <span class="n">length</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">string</span> <span class="n">password</span> <span class="p">=</span> <span class="s">""</span><span class="p">;</span>
    <span class="kt">string</span> <span class="n">characters</span> <span class="p">=</span> <span class="s">"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&amp;*()_+"</span><span class="p">;</span>
    <span class="n">System</span><span class="p">.</span><span class="n">Random</span> <span class="n">random</span> <span class="p">=</span> <span class="k">new</span> <span class="n">System</span><span class="p">.</span><span class="nf">Random</span><span class="p">();</span>

    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">length</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
    <span class="p">{</span>
        <span class="n">password</span> <span class="p">+=</span> <span class="n">characters</span><span class="p">[</span><span class="n">random</span><span class="p">.</span><span class="nf">Next</span><span class="p">(</span><span class="n">characters</span><span class="p">.</span><span class="n">Length</span><span class="p">)];</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="n">password</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>It surprised me, because it’s in the perfect spot of being just about good enough to sneak through some reviews. It would certainly get a few remarks if reviewed as a standalone feature, but it could get through if bundled as part of 20 files in a wider feature. This is a benefit of small commits, but that’s a post for another day.</p>

<p>Yet it’s also bad code. There are to me, three main concerns:</p>

<ul>
  <li><strong>Performance</strong> - The use of <code class="language-plaintext highlighter-rouge">new System.Random()</code> rather than <code class="language-plaintext highlighter-rouge">System.Random.Shared</code>, and string concatenation rather than <code class="language-plaintext highlighter-rouge">StringBuilder</code>, are two things in particular that stand out as quick wins for performance.</li>
  <li><strong>Security</strong> - Using <code class="language-plaintext highlighter-rouge">System.Random</code> instead of a cryptographically secure generator such as <code class="language-plaintext highlighter-rouge">System.Security.Cryptography.RandomNumberGenerator</code> is a concern for any password generator.</li>
  <li><strong>Lack of validating output</strong> for symbols - It’s desirable for passwords to always have at least one symbol to pass most validation sets. With this password generator, you’d be fine most of the time given the input set, but then occassionally be frustrated when it generates a password without symbols. This would happen around 5% of the time for a 16 character password. Just often enough to get missed in testing then hit you later.</li>
</ul>

<h2 id="improving-security">Improving Security</h2>
<p>Let’s address security first, it’s no good having a password generator be fast if you can’t trust the output.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="kt">string</span> <span class="nf">SecureRandom</span><span class="p">(</span><span class="kt">int</span> <span class="n">length</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">string</span> <span class="n">password</span> <span class="p">=</span> <span class="s">""</span><span class="p">;</span>
    <span class="kt">string</span> <span class="n">characters</span> <span class="p">=</span> <span class="s">"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&amp;*()_+"</span><span class="p">;</span>

    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">length</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
    <span class="p">{</span>
        <span class="n">password</span> <span class="p">+=</span> <span class="n">characters</span><span class="p">[</span><span class="n">RandomNumberGenerator</span><span class="p">.</span><span class="nf">GetInt32</span><span class="p">(</span><span class="n">characters</span><span class="p">.</span><span class="n">Length</span><span class="p">)];</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="n">password</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>I’ve kept the same naive implementation, but swapped <code class="language-plaintext highlighter-rouge">random.Next()</code> for the static (and thread safe) <code class="language-plaintext highlighter-rouge">RandomNumberGenerator.GetInt32</code> which is a more secure random number generator.</p>

<h2 id="benchmarking-and-performance-optimisation">Benchmarking and performance optimisation</h2>
<p>Let’s get this under the benchmark microscope to see how we can address performance.</p>

<p>Measuring baseline performance is easy with <a href="https://github.com/dotnet/BenchmarkDotNet">BenchmarkDotNet</a>, we just create a class and then annotate it.</p>

<p>Since we’re creating a class, let’s move <code class="language-plaintext highlighter-rouge">length</code> and <code class="language-plaintext highlighter-rouge">characters</code> to properties in the class.</p>

<p>This will also let us easily parameterise the length across our benchmarks. I’ve gone with 3 different lengths of passwords, so that we can see the effect of increasing password length on generation, and also used two separate categories so we can compare our optimisation efforts on both the secure and vulnerable versions of the password generator.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="nf">Benchmark</span><span class="p">()]</span>
<span class="k">public</span> <span class="kt">string</span> <span class="nf">GeneratePasswordSharedRandom</span><span class="p">()</span>
<span class="p">{</span>
    <span class="kt">string</span> <span class="n">password</span> <span class="p">=</span> <span class="s">""</span><span class="p">;</span>

    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">Length</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
    <span class="p">{</span>
        <span class="n">password</span> <span class="p">+=</span> <span class="n">characters</span><span class="p">[</span><span class="n">Random</span><span class="p">.</span><span class="n">Shared</span><span class="p">.</span><span class="nf">Next</span><span class="p">(</span><span class="n">characters</span><span class="p">.</span><span class="n">Length</span><span class="p">)];</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="n">password</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Let’s immediately look at the impact of removing <code class="language-plaintext highlighter-rouge">System.Random random = new System.Random();</code> and using the static <code class="language-plaintext highlighter-rouge">System.Random.Shared</code> for the vulnerable example.</p>

<p>For this example I’ll post the whole class, for future examples I’ll just post individual methods.</p>

<details>
<summary>Example1.cs</summary>

```csharp
using System.Security.Cryptography;
using BenchmarkDotNet.Attributes;

namespace PasswordGen
{
    [MemoryDiagnoser]
    public class Example1
    {
        [Params(14, 24, 32)]
        public int Length { get; set; }

        private const string characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&amp;*()_+";

        [Benchmark(Baseline = true)]
        public string GeneratePassword()
        {
            string password = "";
            System.Random random = new System.Random();

            for (int i = 0; i &lt; Length; i++)
            {
                password += characters[random.Next(characters.Length)];
            }
            return password;
        }

        [Benchmark()]
        public string SecureRandom(int length)
        {
            string password = "";

            for (int i = 0; i &lt; length; i++)
            {
                password += characters[RandomNumberGenerator.GetInt32(characters.Length)];
            }
            return password;
        }

        [Benchmark()]
        public string GeneratePasswordSharedRandom()
        {
            string password = "";

            for (int i = 0; i &lt; Length; i++)
            {
                password += characters[Random.Shared.Next(characters.Length)];
            }
            return password;
        }

    }
}
```

</details>

<details>
<summary>Table of Results</summary>

| Method                       | Length | Mean       | Error    | StdDev   | Ratio | RatioSD | Gen0   | Allocated | Alloc Ratio |
|----------------------------- |------- |-----------:|---------:|---------:|------:|--------:|-------:|----------:|------------:|
| **GeneratePassword**             | **14**     |   **219.0 ns** |  **4.19 ns** |  **3.92 ns** |  **1.00** |    **0.02** | **0.0753** |     **632 B** |        **1.00** |
| SecureRandom                 | 14     | 1,378.7 ns |  6.59 ns |  5.50 ns |  6.30 |    0.11 | 0.0668 |     560 B |        0.89 |
| GeneratePasswordSharedRandom | 14     |   129.3 ns |  1.85 ns |  1.54 ns |  0.59 |    0.01 | 0.0668 |     560 B |        0.89 |
|                              |        |            |          |          |       |         |        |           |             |
| **GeneratePassword**             | **24**     |   **307.6 ns** |  **4.08 ns** |  **3.41 ns** |  **1.00** |    **0.02** | **0.1516** |    **1272 B** |        **1.00** |
| SecureRandom                 | 24     | 2,379.0 ns | 11.01 ns |  9.20 ns |  7.73 |    0.09 | 0.1411 |    1200 B |        0.94 |
| GeneratePasswordSharedRandom | 24     |   227.1 ns |  1.20 ns |  0.93 ns |  0.74 |    0.01 | 0.1433 |    1200 B |        0.94 |
|                              |        |            |          |          |       |         |        |           |             |
| **GeneratePassword**             | **32**     |   **390.2 ns** |  **7.85 ns** |  **9.64 ns** |  **1.00** |    **0.03** | **0.2303** |    **1928 B** |        **1.00** |
| SecureRandom                 | 32     | 3,181.0 ns | 18.73 ns | 15.64 ns |  8.16 |    0.20 | 0.2213 |    1856 B |        0.96 |
| GeneratePasswordSharedRandom | 32     |   314.4 ns |  6.03 ns |  6.20 ns |  0.81 |    0.02 | 0.2217 |    1856 B |        0.96 |

</details>

<p><img src="/assets/img/1random.png" alt="Benchmark results graph" title="Lower is better" /></p>

<p>We can now see that avoiding <code class="language-plaintext highlighter-rouge">new System.Random()</code> increased performance, roughly 35% faster for the 24 character example.</p>

<p>We can also see that using <code class="language-plaintext highlighter-rouge">RandomNumberGenerator.GetInt32</code> destroyed our performance, taking us into microseconds territory and taking around 8 times as long to do the same work.</p>

<p>To make comparisons in the table easier, we can add <code class="language-plaintext highlighter-rouge">[BenchmarkCategory("Secure")]</code> and <code class="language-plaintext highlighter-rouge">[BenchmarkCategory("Vulnerable")]</code> attributes to our benchmarks to mark <code class="language-plaintext highlighter-rouge">GeneratePassword</code> and <code class="language-plaintext highlighter-rouge">SecureRandom</code> as two separate baselines, so we can more easily examine the performane impact on changes made to each. We also need to mark the class with <code class="language-plaintext highlighter-rouge">[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]</code> and <code class="language-plaintext highlighter-rouge">[CategoriesColumn]</code> to get a category column in the output table.</p>

<h2 id="string-building">String Building</h2>
<p>Okay, let’s do the other straightforward and obvious improvement, to use <code class="language-plaintext highlighter-rouge">StringBuilder</code>, so that our non-secure version now looks like this:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="nf">BenchmarkCategory</span><span class="p">(</span><span class="s">"Vulnerable"</span><span class="p">),</span> <span class="nf">Benchmark</span><span class="p">()]</span>
<span class="k">public</span> <span class="kt">string</span> <span class="nf">StringBuilder</span><span class="p">()</span>
<span class="p">{</span>

    <span class="n">StringBuilder</span> <span class="n">password</span> <span class="p">=</span> <span class="k">new</span><span class="p">(</span><span class="n">Length</span><span class="p">);</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">Length</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
    <span class="p">{</span>
        <span class="n">password</span><span class="p">.</span><span class="nf">Append</span><span class="p">(</span><span class="n">characters</span><span class="p">[</span><span class="n">Random</span><span class="p">.</span><span class="n">Shared</span><span class="p">.</span><span class="nf">Next</span><span class="p">(</span><span class="n">characters</span><span class="p">.</span><span class="n">Length</span><span class="p">)]);</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="n">password</span><span class="p">.</span><span class="nf">ToString</span><span class="p">();</span>
<span class="p">}</span>

<span class="c1">// With an equivalent Secure version not shown here.</span>

</code></pre></div></div>
<p>Since we know the size of the string, we are able to intialize our string builder with that capacity. However, given this knowledge, we can also allocate a <code class="language-plaintext highlighter-rouge">char[]</code> and fill it. Let’s also try that at the same time:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="nf">BenchmarkCategory</span><span class="p">(</span><span class="s">"Secure"</span><span class="p">),</span> <span class="nf">Benchmark</span><span class="p">()]</span>
<span class="k">public</span> <span class="kt">string</span> <span class="nf">CharArraySecure</span><span class="p">()</span>
<span class="p">{</span>
    <span class="kt">char</span><span class="p">[]</span> <span class="n">buffer</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">char</span><span class="p">[</span><span class="n">Length</span><span class="p">];</span>

    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">Length</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
    <span class="p">{</span>
        <span class="n">buffer</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="p">=</span> <span class="n">characters</span><span class="p">[</span><span class="n">RandomNumberGenerator</span><span class="p">.</span><span class="nf">GetInt32</span><span class="p">(</span><span class="n">characters</span><span class="p">.</span><span class="n">Length</span><span class="p">)];</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="k">new</span> <span class="kt">string</span><span class="p">(</span><span class="n">buffer</span><span class="p">);</span>
<span class="p">}</span>

<span class="c1">// With an equivalent Vulnerable version not shown here.</span>
</code></pre></div></div>

<details>
<summary>Table of Results for String Builder, Char[], and original function with string concatenation</summary>

| Method              | Categories | Length | Mean        | Error     | StdDev    | Median      | Ratio | RatioSD | Gen0   | Allocated | Alloc Ratio |
|-------------------- |----------- |------- |------------:|----------:|----------:|------------:|------:|--------:|-------:|----------:|------------:|
| **SecureRandom**        | **Secure**     | **14**     | **1,406.36 ns** | **24.936 ns** | **22.105 ns** | **1,399.08 ns** |  **1.00** |    **0.02** | **0.0668** |     **560 B** |        **1.00** |
| StringBuilderSecure | Secure     | 14     | 1,183.25 ns | 22.336 ns | 21.937 ns | 1,175.49 ns |  0.84 |    0.02 | 0.0191 |     160 B |        0.29 |
| CharArraySecure     | Secure     | 14     | 1,170.42 ns |  4.963 ns |  4.643 ns | 1,171.53 ns |  0.83 |    0.01 | 0.0134 |     112 B |        0.20 |
|                     |            |        |             |           |           |             |       |         |        |           |             |
| **SecureRandom**        | **Secure**     | **24**     | **2,278.24 ns** | **13.348 ns** | **11.833 ns** | **2,277.78 ns** |  **1.00** |    **0.01** | **0.1411** |    **1200 B** |        **1.00** |
| StringBuilderSecure | Secure     | 24     | 2,000.61 ns |  8.895 ns |  7.428 ns | 2,002.13 ns |  0.88 |    0.01 | 0.0229 |     192 B |        0.16 |
| CharArraySecure     | Secure     | 24     | 1,983.18 ns |  8.655 ns |  7.672 ns | 1,981.64 ns |  0.87 |    0.01 | 0.0153 |     144 B |        0.12 |
|                     |            |        |             |           |           |             |       |         |        |           |             |
| **SecureRandom**        | **Secure**     | **32**     | **3,158.97 ns** | **14.740 ns** | **12.308 ns** | **3,157.65 ns** |  **1.00** |    **0.01** | **0.2213** |    **1856 B** |        **1.00** |
| StringBuilderSecure | Secure     | 32     | 2,636.48 ns | 14.115 ns | 13.203 ns | 2,638.96 ns |  0.83 |    0.01 | 0.0267 |     224 B |        0.12 |
| CharArraySecure     | Secure     | 32     | 2,629.67 ns | 11.391 ns | 10.655 ns | 2,630.99 ns |  0.83 |    0.00 | 0.0191 |     176 B |        0.09 |
|                     |            |        |             |           |           |             |       |         |        |           |             |
| **GeneratePassword**    | **Vulnerable** | **14**     |   **218.57 ns** |  **3.956 ns** |  **3.700 ns** |   **217.32 ns** |  **1.00** |    **0.02** | **0.0753** |     **632 B** |        **1.00** |
| StringBuilder       | Vulnerable | 14     |    91.63 ns |  1.880 ns |  4.540 ns |    89.94 ns |  0.42 |    0.02 | 0.0191 |     160 B |        0.25 |
| CharArray           | Vulnerable | 14     |    66.62 ns |  0.340 ns |  0.284 ns |    66.60 ns |  0.30 |    0.01 | 0.0134 |     112 B |        0.18 |
|                     |            |        |             |           |           |             |       |         |        |           |             |
| **GeneratePassword**    | **Vulnerable** | **24**     |   **309.67 ns** |  **5.906 ns** |  **5.525 ns** |   **306.79 ns** |  **1.00** |    **0.02** | **0.1516** |    **1272 B** |        **1.00** |
| StringBuilder       | Vulnerable | 24     |   158.18 ns |  3.241 ns |  8.706 ns |   157.86 ns |  0.51 |    0.03 | 0.0229 |     192 B |        0.15 |
| CharArray           | Vulnerable | 24     |   108.31 ns |  1.156 ns |  1.082 ns |   107.71 ns |  0.35 |    0.01 | 0.0172 |     144 B |        0.11 |
|                     |            |        |             |           |           |             |       |         |        |           |             |
| **GeneratePassword**    | **Vulnerable** | **32**     |   **384.21 ns** |  **4.711 ns** |  **3.678 ns** |   **385.03 ns** |  **1.00** |    **0.01** | **0.2303** |    **1928 B** |        **1.00** |
| StringBuilder       | Vulnerable | 32     |   182.13 ns |  2.545 ns |  2.125 ns |   182.57 ns |  0.47 |    0.01 | 0.0267 |     224 B |        0.12 |
| CharArray           | Vulnerable | 32     |   141.43 ns |  0.751 ns |  0.627 ns |   141.33 ns |  0.37 |    0.00 | 0.0210 |     176 B |        0.09 |
</details>

<p><img src="/assets/img/2stringBuilderWeak.png" alt="Graph for Table 2 non-secure variants" title="Lower is better" />
<img src="/assets/img/3StringBuilderSecure.png" alt="Graph for Table 2 secure variants" title="Lower is better" /></p>

<p>Okay, that’s another modest improvement. It’s hard to see with the secure version, but with the vulnerable version we’ve confirmed the <code class="language-plaintext highlighter-rouge">char[]</code> approach is better than <code class="language-plaintext highlighter-rouge">StringBuilder</code>for building short fixed-length strings up from characters.</p>

<p>The time in the secure version is dominated by the random number generation, so let’s fix that.</p>

<h2 id="faster-random-generators">Faster random generators</h2>

<p>We need to address  time spent in our secure version which is completely dominated by <code class="language-plaintext highlighter-rouge">GetInt32</code>. We can improve performance by getting all the bytes we need at once and then encoding them into characters.</p>

<p>To allow us to do this, we will need to make an important compromise, so that we don’t introduce bias.</p>

<p>Our character set is 74 characters. If we were to generate a byte and then do <code class="language-plaintext highlighter-rouge">value % 74</code>, we would be introducing a bias toward characters <code class="language-plaintext highlighter-rouge">a</code> through <code class="language-plaintext highlighter-rouge">H</code>. I won’t go into the mathematics, but this can be seen by running this code:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">byte</span><span class="p">[]</span> <span class="n">foo</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">byte</span><span class="p">[</span><span class="m">1024</span><span class="p">*</span><span class="m">1024</span><span class="p">];</span>
<span class="kt">string</span> <span class="n">characters</span> <span class="p">=</span> <span class="s">"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&amp;*()_+"</span><span class="p">;</span>

<span class="n">Random</span><span class="p">.</span><span class="n">Shared</span><span class="p">.</span><span class="nf">NextBytes</span><span class="p">(</span><span class="n">foo</span><span class="p">);</span>

<span class="kt">var</span> <span class="n">frequencies</span> <span class="p">=</span> <span class="n">foo</span><span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="n">f</span> <span class="p">=&gt;</span> <span class="n">f</span> <span class="p">%</span> <span class="m">74</span><span class="p">)</span>
<span class="p">.</span><span class="nf">GroupBy</span><span class="p">(</span><span class="n">f</span> <span class="p">=&gt;</span> <span class="n">f</span><span class="p">)</span>
<span class="p">.</span><span class="nf">OrderBy</span><span class="p">(</span><span class="n">f</span> <span class="p">=&gt;</span> <span class="n">f</span><span class="p">.</span><span class="n">Key</span><span class="p">)</span>
<span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="n">f</span> <span class="p">=&gt;</span> <span class="k">new</span> <span class="p">{</span> <span class="n">Value</span> <span class="p">=</span> <span class="n">characters</span><span class="p">[</span><span class="n">f</span><span class="p">.</span><span class="n">Key</span><span class="p">],</span> <span class="n">Frequency</span> <span class="p">=</span> <span class="n">f</span><span class="p">.</span><span class="nf">Count</span><span class="p">()</span> <span class="p">});</span>

<span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">c</span> <span class="k">in</span> <span class="n">frequencies</span><span class="p">)</span> <span class="p">{</span>
 	<span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="s">$"'</span><span class="p">{</span><span class="n">c</span><span class="p">.</span><span class="n">Value</span><span class="p">}</span><span class="s">': </span><span class="p">{</span><span class="n">c</span><span class="p">.</span><span class="n">Frequency</span><span class="p">}</span><span class="s">"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<pre><code class="language-output">'a': 16452
'b': 16693
...
'G': 16527
'H': 16543
'I': 12233
'J': 12248
'K': 12242
...
'_': 12279
'+': 12203
</code></pre>
<p>There’s a heavy bias with <code class="language-plaintext highlighter-rouge">a</code> through <code class="language-plaintext highlighter-rouge">H</code> having approximately ~16,300 appearances with <code class="language-plaintext highlighter-rouge">I</code> through <code class="language-plaintext highlighter-rouge">+</code> having roughly 12,200.</p>

<p>To eliminate this bias, we need a character set that divides evenly into 256. We can either try to pad to 128 characters, which will significantly increase the entropy of our password for a given length, or we can cut down to 64 characters, which will have the unfortunate side-effect of also reducing entropy.</p>

<p>We’ll cut down to 64. It’s difficult to think of 50 more recognisable characters and we can also take the opportunity to cut out characters that can be confused for each other in some fonts, such as I and l.</p>

<p>The reduction in entropy can be calculated. For a 16 character password, we are going from <code class="language-plaintext highlighter-rouge">74^16  ~= 100 bits</code>, to <code class="language-plaintext highlighter-rouge">64^16 = 96 bits</code>.  So we’ve lost around 4 bits from our password. If this is a concern, then we can increase our password length by one character to accomodate.</p>

<p>The actual entropy lost will be <code class="language-plaintext highlighter-rouge">log_2(64/74) = -0.2094534</code> bits per character in the password.</p>

<p>It is a weakening, but if it’s properly documented, should not be a concern.</p>

<p>Let’s define our new character set:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">const</span> <span class="kt">string</span> <span class="n">charactersShortSet</span> <span class="p">=</span> <span class="s">"abcdefghjkmnpqrstuwxyzABCDEFGHJKLMNPQRSTVWXYZ0123456789@#$%&amp;()_+"</span><span class="p">;</span>
</code></pre></div></div>

<p>Now we have a character set that won’t introduce bias, let’s add a function that gets all the bytes at once from our Random sources:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="kt">string</span> <span class="nf">Buffer</span><span class="p">()</span>
<span class="p">{</span>
    <span class="kt">byte</span><span class="p">[]</span> <span class="n">bytebuffer</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">byte</span><span class="p">[</span><span class="n">Length</span><span class="p">];</span>
    <span class="n">RandomNumberGenerator</span><span class="p">.</span><span class="nf">Fill</span><span class="p">(</span><span class="n">bytebuffer</span><span class="p">);</span>

    <span class="kt">char</span><span class="p">[]</span> <span class="n">buffer</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">char</span><span class="p">[</span><span class="n">Length</span><span class="p">];</span>

    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">Length</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
    <span class="p">{</span>
        <span class="n">buffer</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="p">=</span> <span class="n">charactersShortSet</span><span class="p">[</span><span class="n">bytebuffer</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="p">%</span> <span class="m">64</span><span class="p">];</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="k">new</span><span class="p">(</span><span class="n">buffer</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>We’ll also produce an equivalent “vulnerable” version using <code class="language-plaintext highlighter-rouge">Random.Shared.NextBytes</code>, although I’d recommend not using it.</p>

<details>
<summary>Table of results for buffering the random bytes</summary>

| Method           | Categories | Length | Mean        | Error     | StdDev    | Ratio | RatioSD | Gen0   | Allocated | Alloc Ratio |
|----------------- |----------- |------- |------------:|----------:|----------:|------:|--------:|-------:|----------:|------------:|
| **SecureRandom**     | **Secure**     | **14**     | **1,385.36 ns** |  **7.762 ns** |  **6.881 ns** |  **1.00** |    **0.01** | **0.0668** |     **560 B** |        **1.00** |
| BufferSecure     | Secure     | 14     |    86.59 ns |  0.738 ns |  0.654 ns |  0.06 |    0.00 | 0.0181 |     152 B |        0.27 |
|                  |            |        |             |           |           |       |         |        |           |             |
| **SecureRandom**     | **Secure**     | **24**     | **2,385.88 ns** | **15.122 ns** | **13.405 ns** |  **1.00** |    **0.01** | **0.1411** |    **1200 B** |        **1.00** |
| BufferSecure     | Secure     | 24     |   107.46 ns |  1.057 ns |  0.989 ns |  0.05 |    0.00 | 0.0229 |     192 B |        0.16 |
|                  |            |        |             |           |           |       |         |        |           |             |
| **SecureRandom**     | **Secure**     | **32**     | **3,182.64 ns** | **13.813 ns** | **10.785 ns** |  **1.00** |    **0.00** | **0.2213** |    **1856 B** |        **1.00** |
| BufferSecure     | Secure     | 32     |   117.98 ns |  1.035 ns |  0.917 ns |  0.04 |    0.00 | 0.0277 |     232 B |        0.12 |
|                  |            |        |             |           |           |       |         |        |           |             |
| **GeneratePassword** | **Vulnerable** | **14**     |   **218.01 ns** |  **3.323 ns** |  **3.412 ns** |  **1.00** |    **0.02** | **0.0753** |     **632 B** |        **1.00** |
| Buffer           | Vulnerable | 14     |    32.63 ns |  0.345 ns |  0.269 ns |  0.15 |    0.00 | 0.0181 |     152 B |        0.24 |
|                  |            |        |             |           |           |       |         |        |           |             |
| **GeneratePassword** | **Vulnerable** | **24**     |   **306.43 ns** |  **4.712 ns** |  **4.177 ns** |  **1.00** |    **0.02** | **0.1516** |    **1272 B** |        **1.00** |
| Buffer           | Vulnerable | 24     |    37.25 ns |  0.270 ns |  0.225 ns |  0.12 |    0.00 | 0.0229 |     192 B |        0.15 |
|                  |            |        |             |           |           |       |         |        |           |             |
| **GeneratePassword** | **Vulnerable** | **32**     |   **374.71 ns** |  **4.076 ns** |  **3.812 ns** |  **1.00** |    **0.01** | **0.2303** |    **1928 B** |        **1.00** |
| Buffer           | Vulnerable | 32     |    44.37 ns |  0.522 ns |  0.488 ns |  0.12 |    0.00 | 0.0277 |     232 B |        0.12 |

</details>

<p><img src="/assets/img/4Buffer.png" alt="Graph of benchmark results for buffering the random data" title="Lower is better" /></p>

<p>Now there’s the improvement we were hoping for! Our vulnerable version is now up to 8 times faster than the original co-pilot output.</p>

<p>More importantly, our secure version is actually faster than the original vulnerable co-pilot version.</p>

<h2 id="using-the-full-symbol-set">Using the full symbol set</h2>

<p>We’ve sacrificed some symbols and characters from the output to achieve this. Can we maintain a good speed while also using our full character set? It might be possible: while looking up <code class="language-plaintext highlighter-rouge">RandomNumberGenerator.Fill</code> I spotted <code class="language-plaintext highlighter-rouge">RandomNumberGenerator.GetItems&lt;T&gt;</code>, which:</p>

<blockquote>
  <p>Creates an array populated with items chosen at random from choices</p>
</blockquote>

<p>That sounds exactly like what we’re after. There’s also an equivalent <code class="language-plaintext highlighter-rouge">Random.Shared.GetItems&lt;T&gt;</code>, let’s implement them, going back to our original 74 character set to do so. This leaves our methods as:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="nf">BenchmarkCategory</span><span class="p">(</span><span class="s">"Vulnerable"</span><span class="p">),</span> <span class="nf">Benchmark</span><span class="p">()]</span>
<span class="k">public</span> <span class="kt">string</span> <span class="nf">GetItems</span><span class="p">()</span>
<span class="p">{</span>
    <span class="kt">char</span><span class="p">[]</span> <span class="n">buffer</span> <span class="p">=</span> <span class="n">Random</span><span class="p">.</span><span class="n">Shared</span><span class="p">.</span><span class="n">GetItems</span><span class="p">&lt;</span><span class="kt">char</span><span class="p">&gt;(</span><span class="n">characters</span><span class="p">,</span> <span class="n">Length</span><span class="p">);</span>
    <span class="k">return</span> <span class="k">new</span><span class="p">(</span><span class="n">buffer</span><span class="p">);</span>
<span class="p">}</span>

<span class="p">[</span><span class="nf">BenchmarkCategory</span><span class="p">(</span><span class="s">"Secure"</span><span class="p">),</span> <span class="nf">Benchmark</span><span class="p">()]</span>
<span class="k">public</span> <span class="kt">string</span> <span class="nf">GetItemsSecure</span><span class="p">()</span>
<span class="p">{</span>
    <span class="kt">char</span><span class="p">[]</span> <span class="n">buffer</span> <span class="p">=</span> <span class="n">RandomNumberGenerator</span><span class="p">.</span><span class="n">GetItems</span><span class="p">&lt;</span><span class="kt">char</span><span class="p">&gt;(</span><span class="n">characters</span><span class="p">,</span> <span class="n">Length</span><span class="p">);</span>
    <span class="k">return</span> <span class="k">new</span><span class="p">(</span><span class="n">buffer</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>That’s definitely a lot neater code than the original, let’s see how it performs:</p>

<details>

<summary>Table of Results for GetItems methods</summary>

| Method           | Categories | Length | Mean       | Error    | StdDev   | Ratio | RatioSD | Gen0   | Allocated | Alloc Ratio |
|----------------- |----------- |------- |-----------:|---------:|---------:|------:|--------:|-------:|----------:|------------:|
| **SecureRandom**     | **Secure**     | **14**     | **1,375.0 ns** |  **8.36 ns** |  **7.41 ns** |  **1.00** |    **0.01** | **0.0668** |     **560 B** |        **1.00** |
| GetItemsSecure   | Secure     | 14     |   208.9 ns |  1.95 ns |  1.73 ns |  0.15 |    0.00 | 0.0134 |     112 B |        0.20 |
|                  |            |        |            |          |          |       |         |        |           |             |
| **SecureRandom**     | **Secure**     | **24**     | **2,405.9 ns** | **32.45 ns** | **30.35 ns** |  **1.00** |    **0.02** | **0.1411** |    **1200 B** |        **1.00** |
| GetItemsSecure   | Secure     | 24     |   302.6 ns |  1.86 ns |  1.55 ns |  0.13 |    0.00 | 0.0172 |     144 B |        0.12 |
|                  |            |        |            |          |          |       |         |        |           |             |
| **SecureRandom**     | **Secure**     | **32**     | **3,184.5 ns** | **22.23 ns** | **19.71 ns** |  **1.00** |    **0.01** | **0.2213** |    **1856 B** |        **1.00** |
| GetItemsSecure   | Secure     | 32     |   369.4 ns |  3.19 ns |  2.49 ns |  0.12 |    0.00 | 0.0210 |     176 B |        0.09 |
|                  |            |        |            |          |          |       |         |        |           |             |
| **GeneratePassword** | **Vulnerable** | **14**     |   **217.4 ns** |  **2.64 ns** |  **2.34 ns** |  **1.00** |    **0.01** | **0.0753** |     **632 B** |        **1.00** |
| GetItems         | Vulnerable | 14     |   125.0 ns |  1.46 ns |  1.30 ns |  0.57 |    0.01 | 0.0134 |     112 B |        0.18 |
|                  |            |        |            |          |          |       |         |        |           |             |
| **GeneratePassword** | **Vulnerable** | **24**     |   **309.8 ns** |  **4.09 ns** |  **3.63 ns** |  **1.00** |    **0.02** | **0.1516** |    **1272 B** |        **1.00** |
| GetItems         | Vulnerable | 24     |   187.3 ns |  1.67 ns |  1.48 ns |  0.60 |    0.01 | 0.0172 |     144 B |        0.11 |
|                  |            |        |            |          |          |       |         |        |           |             |
| **GeneratePassword** | **Vulnerable** | **32**     |   **383.6 ns** |  **5.39 ns** |  **4.50 ns** |  **1.00** |    **0.02** | **0.2303** |    **1928 B** |        **1.00** |
| GetItems         | Vulnerable | 32     |   243.7 ns |  2.80 ns |  2.62 ns |  0.64 |    0.01 | 0.0210 |     176 B |        0.09 |

</details>

<p><img src="/assets/img/5GetItems.png" alt="Graph of benchmark results for GetItems" title="Lower is better" /></p>

<p>Okay, well that’s a bit of a performance regression. We’d need to understand the trade-offs of using the full character set vs the reduced character set with better performance.</p>

<p>At this point we can drop benchmarking the “weak” version entirely. We now have  a secure version that has feature parity with the original while also being faster than the original code. I’ll be using <code class="language-plaintext highlighter-rouge">GetItemsSecure</code> as the baseline for benchmarks going forward.</p>

<p>There’s a couple of final performance tweaks we can try, but before we do, we should address the third bullet point on the list of problems with the original co-pilot code.</p>

<h2 id="fixing-the-functionality">Fixing the functionality</h2>

<p>It’s important we don’t lose sight of our goal, a working password generator that won’t frustrate us by sometimes returning a password that contains no symbols.</p>

<p>There are a few approaches to fixing this issue, some of which are fraught with the danger of re-introducing bias. One naive way is to generate a character from the symbols set (or <code class="language-plaintext highlighter-rouge">k</code> characters) and then generate <code class="language-plaintext highlighter-rouge">n-k</code> characters from the full set, then shuffle both sets together. This would however reintroduce a subtle bias.</p>

<p>The easiest way to demonstrate this bias is with a set of 3 fair coins.</p>

<p>We have 8 possible random outcomes:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>HHH
HHT
HTH
HTT
THH
THT
TTH
TTT
</code></pre></div></div>
<p>If we demand a passcode that “must have at least 1 T”, then we have 7 possible equally likely outcomes, that is all outcomes except <code class="language-plaintext highlighter-rouge">HHH</code>.</p>

<p>Let’s ee what happens if we try to generate this by picking a <code class="language-plaintext highlighter-rouge">T</code>, then randomly picking two more characters and then shuffling.</p>

<p>We have 4 intial outcomes from our two remaining coin tosses:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>T + HH
T + HT
T + TH
T + TT
</code></pre></div></div>

<p>For each initial outcome, we have 6 ways to shuffle the 3 characters.</p>

<p>So when shuffled, these become 24 equally likely outcomes:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>( From T + HH )

THH
THH
HTH
HHT
HTH
HHT

( From T + HT )
THT
TTH
HTT
HTT
THT
TTH

( From T + TH )

THT
TTH
HTT
HTT
THT
TTH

( From T + TT )
TTT
TTT
TTT
TTT
TTT
TTT
</code></pre></div></div>

<p>Yes, 6 in 24, a full quarter of all outcomes are <code class="language-plaintext highlighter-rouge">TTT</code>, yet with a fair coin there should be just 1 in 7 cases of <code class="language-plaintext highlighter-rouge">TTT</code> given there is at least one <code class="language-plaintext highlighter-rouge">T</code>.</p>

<p>So that approach is out. The fairer way to do this is to generate a password, then if it doesn’t meet the criteria, throw it out entirely and start again.</p>

<p>We’ll also parameterise the minimum number of special characters in our code, to look at requiring 0, 1 or 2 special characters.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="nf">Params</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="m">1</span><span class="p">,</span> <span class="m">2</span><span class="p">)]</span>
<span class="k">public</span> <span class="kt">int</span> <span class="n">MinmumSpecialCharacters</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</code></pre></div></div>

<p>When we’re using <code class="language-plaintext highlighter-rouge">RandomNumberGenerator.Fill</code> and looking up the result in our character set, our character set is fortunately arranged so that we can cheaply determine if there is a special character. We just have to look at the value of the byte to see if it is <code class="language-plaintext highlighter-rouge">55 (0x37)</code> or higher, since indexes <code class="language-plaintext highlighter-rouge">55</code> to <code class="language-plaintext highlighter-rouge">63</code> in our string are all special characters.</p>

<p>For the case where we are using <code class="language-plaintext highlighter-rouge">GetItems</code>, we have to count <code class="language-plaintext highlighter-rouge">char.IsAsciiLetterOrDigit</code> and take that from our password length, then compare that count against the requested minimum symbols.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="nf">BenchmarkCategory</span><span class="p">(</span><span class="s">"Secure"</span><span class="p">),</span> <span class="nf">Benchmark</span><span class="p">()]</span>
<span class="k">public</span> <span class="kt">string</span> <span class="nf">RejectionSampleSecure</span><span class="p">()</span>
<span class="p">{</span>
    <span class="kt">byte</span><span class="p">[]</span> <span class="n">bytebuffer</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">byte</span><span class="p">[</span><span class="n">Length</span><span class="p">];</span>
    <span class="kt">char</span><span class="p">[]</span> <span class="n">buffer</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">char</span><span class="p">[</span><span class="n">Length</span><span class="p">];</span>

    <span class="k">while</span> <span class="p">(</span><span class="k">true</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">RandomNumberGenerator</span><span class="p">.</span><span class="nf">Fill</span><span class="p">(</span><span class="n">bytebuffer</span><span class="p">);</span>
        <span class="kt">int</span> <span class="n">specialChars</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
        <span class="kt">bool</span> <span class="n">metMinimum</span> <span class="p">=</span> <span class="k">false</span><span class="p">;</span>

        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">Length</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
        <span class="p">{</span>
            <span class="k">if</span> <span class="p">(!</span><span class="n">metMinimum</span> <span class="p">&amp;&amp;</span> <span class="p">(</span><span class="n">bytebuffer</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="p">&gt;</span> <span class="m">54</span><span class="p">)</span> <span class="p">&amp;&amp;</span> <span class="p">(++</span><span class="n">specialChars</span> <span class="p">&gt;=</span> <span class="n">MinmumSpecialCharacters</span><span class="p">))</span>
            <span class="p">{</span>
                <span class="n">metMinimum</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
            <span class="p">}</span>

            <span class="n">buffer</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="p">=</span> <span class="n">charactersShortSet</span><span class="p">[</span><span class="n">bytebuffer</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="p">%</span> <span class="m">64</span><span class="p">];</span>
        <span class="p">}</span>

        <span class="k">if</span> <span class="p">(</span><span class="n">metMinimum</span><span class="p">)</span>
        <span class="p">{</span>

            <span class="k">return</span> <span class="k">new</span><span class="p">(</span><span class="n">buffer</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="p">[</span><span class="nf">BenchmarkCategory</span><span class="p">(</span><span class="s">"Secure"</span><span class="p">),</span> <span class="nf">Benchmark</span><span class="p">()]</span>
<span class="k">public</span> <span class="kt">string</span> <span class="nf">GetItemsWithRejectionSecure</span><span class="p">()</span>
<span class="p">{</span>
    <span class="k">while</span> <span class="p">(</span><span class="k">true</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="kt">char</span><span class="p">[]</span> <span class="n">buffer</span> <span class="p">=</span> <span class="n">RandomNumberGenerator</span><span class="p">.</span><span class="n">GetItems</span><span class="p">&lt;</span><span class="kt">char</span><span class="p">&gt;(</span><span class="n">characters</span><span class="p">,</span> <span class="n">Length</span><span class="p">);</span>

        <span class="k">if</span> <span class="p">((</span><span class="n">buffer</span><span class="p">.</span><span class="n">Length</span> <span class="p">-</span> <span class="n">buffer</span><span class="p">.</span><span class="nf">Count</span><span class="p">(</span><span class="kt">char</span><span class="p">.</span><span class="n">IsAsciiLetterOrDigit</span><span class="p">))</span> <span class="p">&gt;=</span> <span class="n">MinmumSpecialCharacters</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="k">return</span> <span class="k">new</span><span class="p">(</span><span class="n">buffer</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>For this solution we have had to enumerate the array again for the <code class="language-plaintext highlighter-rouge">GetItems</code> approach. We expect this to further worsen its performance relative to the 64 character set approach, so let’s see the results:</p>

<details>

<summary>Table of Results for passwords with Minimum Special Characters</summary>

| Method                      | MinmumSpecialCharacters | Length | Mean      | Error    | StdDev   | Ratio | RatioSD | Gen0   | Allocated | Alloc Ratio |
|---------------------------- |------------------------ |------- |----------:|---------:|---------:|------:|--------:|-------:|----------:|------------:|
| **GetItemsSecure**              | **0**                       | **14**     | **216.41 ns** | **3.943 ns** | **3.688 ns** |  **1.00** |    **0.02** | **0.0134** |     **112 B** |        **1.00** |
| RejectionSampleSecure       | 0                       | 14     |  93.84 ns | 0.779 ns | 0.651 ns |  0.43 |    0.01 | 0.0181 |     152 B |        1.36 |
| GetItemsWithRejectionSecure | 0                       | 14     | 274.44 ns | 2.315 ns | 2.165 ns |  1.27 |    0.02 | 0.0134 |     112 B |        1.00 |
|                             |                         |        |           |          |          |       |         |        |           |             |
| **GetItemsSecure**              | **0**                       | **24**     | **308.27 ns** | **4.794 ns** | **4.250 ns** |  **1.00** |    **0.02** | **0.0172** |     **144 B** |        **1.00** |
| RejectionSampleSecure       | 0                       | 24     | 125.08 ns | 2.024 ns | 1.794 ns |  0.41 |    0.01 | 0.0229 |     192 B |        1.33 |
| GetItemsWithRejectionSecure | 0                       | 24     | 410.03 ns | 3.729 ns | 3.488 ns |  1.33 |    0.02 | 0.0172 |     144 B |        1.00 |
|                             |                         |        |           |          |          |       |         |        |           |             |
| **GetItemsSecure**              | **0**                       | **32**     | **383.04 ns** | **7.545 ns** | **7.410 ns** |  **1.00** |    **0.03** | **0.0210** |     **176 B** |        **1.00** |
| RejectionSampleSecure       | 0                       | 32     | 150.98 ns | 3.058 ns | 7.149 ns |  0.39 |    0.02 | 0.0277 |     232 B |        1.32 |
| GetItemsWithRejectionSecure | 0                       | 32     | 499.03 ns | 3.942 ns | 3.495 ns |  1.30 |    0.03 | 0.0210 |     176 B |        1.00 |
|                             |                         |        |           |          |          |       |         |        |           |             |
| **GetItemsSecure**              | **1**                       | **14**     | **210.37 ns** | **3.295 ns** | **3.082 ns** |  **1.00** |    **0.02** | **0.0134** |     **112 B** |        **1.00** |
| RejectionSampleSecure       | 1                       | 14     |  99.96 ns | 1.267 ns | 1.058 ns |  0.48 |    0.01 | 0.0181 |     152 B |        1.36 |
| GetItemsWithRejectionSecure | 1                       | 14     | 312.13 ns | 3.662 ns | 3.425 ns |  1.48 |    0.03 | 0.0138 |     117 B |        1.04 |
|                             |                         |        |           |          |          |       |         |        |           |             |
| **GetItemsSecure**              | **1**                       | **24**     | **296.49 ns** | **3.779 ns** | **3.535 ns** |  **1.00** |    **0.02** | **0.0172** |     **144 B** |        **1.00** |
| RejectionSampleSecure       | 1                       | 24     | 130.04 ns | 2.424 ns | 3.628 ns |  0.44 |    0.01 | 0.0229 |     192 B |        1.33 |
| GetItemsWithRejectionSecure | 1                       | 24     | 421.19 ns | 2.212 ns | 1.847 ns |  1.42 |    0.02 | 0.0172 |     145 B |        1.01 |
|                             |                         |        |           |          |          |       |         |        |           |             |
| **GetItemsSecure**              | **1**                       | **32**     | **378.64 ns** | **4.840 ns** | **4.528 ns** |  **1.00** |    **0.02** | **0.0210** |     **176 B** |        **1.00** |
| RejectionSampleSecure       | 1                       | 32     | 149.58 ns | 2.487 ns | 2.327 ns |  0.40 |    0.01 | 0.0277 |     232 B |        1.32 |
| GetItemsWithRejectionSecure | 1                       | 32     | 516.26 ns | 5.135 ns | 4.803 ns |  1.36 |    0.02 | 0.0210 |     176 B |        1.00 |
|                             |                         |        |           |          |          |       |         |        |           |             |
| **GetItemsSecure**              | **2**                       | **14**     | **211.07 ns** | **2.997 ns** | **2.803 ns** |  **1.00** |    **0.02** | **0.0134** |     **112 B** |        **1.00** |
| RejectionSampleSecure       | 2                       | 14     | 120.75 ns | 1.360 ns | 1.205 ns |  0.57 |    0.01 | 0.0181 |     152 B |        1.36 |
| GetItemsWithRejectionSecure | 2                       | 14     | 403.71 ns | 4.163 ns | 3.691 ns |  1.91 |    0.03 | 0.0162 |     137 B |        1.22 |
|                             |                         |        |           |          |          |       |         |        |           |             |
| **GetItemsSecure**              | **2**                       | **24**     | **301.89 ns** | **3.207 ns** | **3.000 ns** |  **1.00** |    **0.01** | **0.0172** |     **144 B** |        **1.00** |
| RejectionSampleSecure       | 2                       | 24     | 133.41 ns | 1.090 ns | 0.910 ns |  0.44 |    0.01 | 0.0229 |     192 B |        1.33 |
| GetItemsWithRejectionSecure | 2                       | 24     | 442.47 ns | 3.852 ns | 3.603 ns |  1.47 |    0.02 | 0.0176 |     150 B |        1.04 |
|                             |                         |        |           |          |          |       |         |        |           |             |
| **GetItemsSecure**              | **2**                       | **32**     | **376.13 ns** | **3.315 ns** | **3.100 ns** |  **1.00** |    **0.01** | **0.0210** |     **176 B** |        **1.00** |
| RejectionSampleSecure       | 2                       | 32     | 156.98 ns | 0.922 ns | 0.818 ns |  0.42 |    0.00 | 0.0277 |     232 B |        1.32 |
| GetItemsWithRejectionSecure | 2                       | 32     | 504.26 ns | 5.433 ns | 5.082 ns |  1.34 |    0.02 | 0.0210 |     178 B |        1.01 |

</details>

<p><img src="/assets/img/6Rejection.png" alt="Graph of results for minimum special characters" title="Lower is better" /></p>

<p>As expected, this has added overhead, it leaves us with a choice between <code class="language-plaintext highlighter-rouge">RejectionSampleSecure</code> with it’s slightly reduced entropy per output character but preferable performance characteristics and <code class="language-plaintext highlighter-rouge">GetItemsWithRejectionSecure</code>, which uses the full symbol set for feature parity with the original code.</p>

<h2 id="further-performance-improvements">Further performance improvements</h2>

<p>We can try to speed up the <code class="language-plaintext highlighter-rouge">GetItems</code> based methods by using a loop to count the special characters, so we can exit early when we’ve met our target rather than counting all special characters.</p>

<p>Also, we can try to avoid a heap allocation by using <code class="language-plaintext highlighter-rouge">stackalloc</code> to allocate the span on the stack.</p>

<p>This leaves our code like the following:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="nf">Benchmark</span><span class="p">()]</span>
<span class="k">public</span> <span class="kt">string</span> <span class="nf">StackAllocSecure</span><span class="p">()</span>
<span class="p">{</span>
    <span class="n">Span</span><span class="p">&lt;</span><span class="kt">char</span><span class="p">&gt;</span> <span class="n">buffer</span> <span class="p">=</span> <span class="k">stackalloc</span> <span class="kt">char</span><span class="p">[</span><span class="n">Length</span><span class="p">];</span>

    <span class="k">while</span> <span class="p">(</span><span class="k">true</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">RandomNumberGenerator</span><span class="p">.</span><span class="n">GetItems</span><span class="p">&lt;</span><span class="kt">char</span><span class="p">&gt;(</span><span class="n">characters</span><span class="p">,</span> <span class="n">buffer</span><span class="p">);</span>

        <span class="kt">int</span> <span class="n">specialChars</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>

        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">Length</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
        <span class="p">{</span>
            <span class="k">if</span> <span class="p">(!</span><span class="kt">char</span><span class="p">.</span><span class="nf">IsAsciiLetterOrDigit</span><span class="p">(</span><span class="n">buffer</span><span class="p">[</span><span class="n">i</span><span class="p">])</span> <span class="p">&amp;&amp;</span> <span class="p">(++</span><span class="n">specialChars</span> <span class="p">&gt;=</span> <span class="n">MinmumSpecialCharacters</span><span class="p">))</span>
            <span class="p">{</span>
                <span class="k">return</span> <span class="k">new</span><span class="p">(</span><span class="n">buffer</span><span class="p">);</span>
            <span class="p">}</span>
        <span class="p">}</span>

    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<details>

<summary>Table of Results for manual Special character checking and stack allocation</summary>

| Method                      | MinmumSpecialCharacters | Length | Mean      | Error    | StdDev   | Ratio | RatioSD | Gen0   | Allocated | Alloc Ratio |
|---------------------------- |------------------------ |------- |----------:|---------:|---------:|------:|--------:|-------:|----------:|------------:|
| **GetItemsSecure**              | **0**                       | **14**     | **219.25 ns** | **3.568 ns** | **3.504 ns** |  **1.00** |    **0.02** | **0.0134** |     **112 B** |        **1.00** |
| RejectionSampleSecure       | 0                       | 14     |  96.46 ns | 1.653 ns | 1.546 ns |  0.44 |    0.01 | 0.0181 |     152 B |        1.36 |
| GetItemsWithRejectionSecure | 0                       | 14     | 281.77 ns | 4.986 ns | 4.664 ns |  1.29 |    0.03 | 0.0134 |     112 B |        1.00 |
| SpecialLoopSecure           | 0                       | 14     | 239.87 ns | 4.092 ns | 3.828 ns |  1.09 |    0.02 | 0.0134 |     112 B |        1.00 |
| StackAllocSecure            | 0                       | 14     | 255.86 ns | 4.454 ns | 7.916 ns |  1.17 |    0.04 | 0.0067 |      56 B |        0.50 |
| RejectionSampleStackAlloc   | 0                       | 14     |  97.55 ns | 1.386 ns | 1.229 ns |  0.45 |    0.01 | 0.0067 |      56 B |        0.50 |
|                             |                         |        |           |          |          |       |         |        |           |             |
| **GetItemsSecure**              | **0**                       | **24**     | **313.89 ns** | **2.916 ns** | **2.435 ns** |  **1.00** |    **0.01** | **0.0172** |     **144 B** |        **1.00** |
| RejectionSampleSecure       | 0                       | 24     | 129.64 ns | 1.929 ns | 1.805 ns |  0.41 |    0.01 | 0.0229 |     192 B |        1.33 |
| GetItemsWithRejectionSecure | 0                       | 24     | 415.99 ns | 6.103 ns | 5.709 ns |  1.33 |    0.02 | 0.0172 |     144 B |        1.00 |
| SpecialLoopSecure           | 0                       | 24     | 319.59 ns | 5.351 ns | 5.005 ns |  1.02 |    0.02 | 0.0172 |     144 B |        1.00 |
| StackAllocSecure            | 0                       | 24     | 327.40 ns | 5.817 ns | 5.442 ns |  1.04 |    0.02 | 0.0086 |      72 B |        0.50 |
| RejectionSampleStackAlloc   | 0                       | 24     | 124.45 ns | 2.023 ns | 1.892 ns |  0.40 |    0.01 | 0.0086 |      72 B |        0.50 |
|                             |                         |        |           |          |          |       |         |        |           |             |
| **GetItemsSecure**              | **0**                       | **32**     | **367.56 ns** | **5.343 ns** | **4.998 ns** |  **1.00** |    **0.02** | **0.0210** |     **176 B** |        **1.00** |
| RejectionSampleSecure       | 0                       | 32     | 145.40 ns | 2.978 ns | 3.186 ns |  0.40 |    0.01 | 0.0277 |     232 B |        1.32 |
| GetItemsWithRejectionSecure | 0                       | 32     | 524.62 ns | 7.121 ns | 5.946 ns |  1.43 |    0.02 | 0.0210 |     176 B |        1.00 |
| SpecialLoopSecure           | 0                       | 32     | 406.65 ns | 6.309 ns | 5.901 ns |  1.11 |    0.02 | 0.0210 |     176 B |        1.00 |
| StackAllocSecure            | 0                       | 32     | 398.60 ns | 7.189 ns | 6.724 ns |  1.08 |    0.02 | 0.0105 |      88 B |        0.50 |
| RejectionSampleStackAlloc   | 0                       | 32     | 138.75 ns | 2.733 ns | 2.557 ns |  0.38 |    0.01 | 0.0105 |      88 B |        0.50 |
|                             |                         |        |           |          |          |       |         |        |           |             |
| **GetItemsSecure**              | **1**                       | **14**     | **209.37 ns** | **3.375 ns** | **3.157 ns** |  **1.00** |    **0.02** | **0.0134** |     **112 B** |        **1.00** |
| RejectionSampleSecure       | 1                       | 14     | 101.67 ns | 1.786 ns | 1.670 ns |  0.49 |    0.01 | 0.0181 |     152 B |        1.36 |
| GetItemsWithRejectionSecure | 1                       | 14     | 317.30 ns | 4.885 ns | 4.569 ns |  1.52 |    0.03 | 0.0138 |     117 B |        1.04 |
| SpecialLoopSecure           | 1                       | 14     | 241.89 ns | 3.421 ns | 3.200 ns |  1.16 |    0.02 | 0.0134 |     112 B |        1.00 |
| StackAllocSecure            | 1                       | 14     | 246.68 ns | 4.584 ns | 4.288 ns |  1.18 |    0.03 | 0.0067 |      56 B |        0.50 |
| RejectionSampleStackAlloc   | 1                       | 14     |  96.41 ns | 1.684 ns | 1.575 ns |  0.46 |    0.01 | 0.0067 |      56 B |        0.50 |
|                             |                         |        |           |          |          |       |         |        |           |             |
| **GetItemsSecure**              | **1**                       | **24**     | **305.87 ns** | **4.377 ns** | **4.094 ns** |  **1.00** |    **0.02** | **0.0172** |     **144 B** |        **1.00** |
| RejectionSampleSecure       | 1                       | 24     | 129.04 ns | 2.608 ns | 2.790 ns |  0.42 |    0.01 | 0.0229 |     192 B |        1.33 |
| GetItemsWithRejectionSecure | 1                       | 24     | 425.78 ns | 5.961 ns | 5.576 ns |  1.39 |    0.03 | 0.0172 |     145 B |        1.01 |
| SpecialLoopSecure           | 1                       | 24     | 319.29 ns | 4.231 ns | 3.958 ns |  1.04 |    0.02 | 0.0172 |     144 B |        1.00 |
| StackAllocSecure            | 1                       | 24     | 318.13 ns | 4.758 ns | 4.451 ns |  1.04 |    0.02 | 0.0086 |      72 B |        0.50 |
| RejectionSampleStackAlloc   | 1                       | 24     | 118.54 ns | 2.184 ns | 2.043 ns |  0.39 |    0.01 | 0.0086 |      72 B |        0.50 |
|                             |                         |        |           |          |          |       |         |        |           |             |
| **GetItemsSecure**              | **1**                       | **32**     | **367.17 ns** | **5.206 ns** | **4.869 ns** |  **1.00** |    **0.02** | **0.0210** |     **176 B** |        **1.00** |
| RejectionSampleSecure       | 1                       | 32     | 148.29 ns | 2.290 ns | 2.030 ns |  0.40 |    0.01 | 0.0277 |     232 B |        1.32 |
| GetItemsWithRejectionSecure | 1                       | 32     | 497.77 ns | 8.330 ns | 7.792 ns |  1.36 |    0.03 | 0.0210 |     176 B |        1.00 |
| SpecialLoopSecure           | 1                       | 32     | 383.71 ns | 6.443 ns | 6.027 ns |  1.05 |    0.02 | 0.0210 |     176 B |        1.00 |
| StackAllocSecure            | 1                       | 32     | 380.26 ns | 4.160 ns | 3.891 ns |  1.04 |    0.02 | 0.0105 |      88 B |        0.50 |
| RejectionSampleStackAlloc   | 1                       | 32     | 134.83 ns | 2.179 ns | 2.038 ns |  0.37 |    0.01 | 0.0105 |      88 B |        0.50 |
|                             |                         |        |           |          |          |       |         |        |           |             |
| **GetItemsSecure**              | **2**                       | **14**     | **218.75 ns** | **1.572 ns** | **1.471 ns** |  **1.00** |    **0.01** | **0.0134** |     **112 B** |        **1.00** |
| RejectionSampleSecure       | 2                       | 14     | 106.04 ns | 1.657 ns | 1.550 ns |  0.48 |    0.01 | 0.0181 |     152 B |        1.36 |
| GetItemsWithRejectionSecure | 2                       | 14     | 410.57 ns | 6.451 ns | 6.034 ns |  1.88 |    0.03 | 0.0162 |     137 B |        1.22 |
| SpecialLoopSecure           | 2                       | 14     | 332.73 ns | 2.577 ns | 2.284 ns |  1.52 |    0.01 | 0.0134 |     112 B |        1.00 |
| StackAllocSecure            | 2                       | 14     | 340.73 ns | 4.629 ns | 4.330 ns |  1.56 |    0.02 | 0.0067 |      56 B |        0.50 |
| RejectionSampleStackAlloc   | 2                       | 14     |  98.66 ns | 1.572 ns | 1.470 ns |  0.45 |    0.01 | 0.0067 |      56 B |        0.50 |
|                             |                         |        |           |          |          |       |         |        |           |             |
| **GetItemsSecure**              | **2**                       | **24**     | **313.25 ns** | **4.255 ns** | **3.772 ns** |  **1.00** |    **0.02** | **0.0172** |     **144 B** |        **1.00** |
| RejectionSampleSecure       | 2                       | 24     | 130.08 ns | 2.566 ns | 2.852 ns |  0.42 |    0.01 | 0.0229 |     192 B |        1.33 |
| GetItemsWithRejectionSecure | 2                       | 24     | 450.29 ns | 3.358 ns | 2.977 ns |  1.44 |    0.02 | 0.0176 |     150 B |        1.04 |
| SpecialLoopSecure           | 2                       | 24     | 368.24 ns | 5.882 ns | 5.502 ns |  1.18 |    0.02 | 0.0172 |     144 B |        1.00 |
| StackAllocSecure            | 2                       | 24     | 357.85 ns | 2.917 ns | 2.729 ns |  1.14 |    0.02 | 0.0086 |      72 B |        0.50 |
| RejectionSampleStackAlloc   | 2                       | 24     | 119.55 ns | 1.124 ns | 0.997 ns |  0.38 |    0.01 | 0.0086 |      72 B |        0.50 |
|                             |                         |        |           |          |          |       |         |        |           |             |
| **GetItemsSecure**              | **2**                       | **32**     | **375.78 ns** | **7.011 ns** | **6.558 ns** |  **1.00** |    **0.02** | **0.0210** |     **176 B** |        **1.00** |
| RejectionSampleSecure       | 2                       | 32     | 155.96 ns | 1.061 ns | 0.886 ns |  0.42 |    0.01 | 0.0277 |     232 B |        1.32 |
| GetItemsWithRejectionSecure | 2                       | 32     | 536.46 ns | 7.375 ns | 6.899 ns |  1.43 |    0.03 | 0.0210 |     178 B |        1.01 |
| SpecialLoopSecure           | 2                       | 32     | 424.13 ns | 7.327 ns | 6.853 ns |  1.13 |    0.03 | 0.0210 |     176 B |        1.00 |
| StackAllocSecure            | 2                       | 32     | 411.87 ns | 6.340 ns | 5.931 ns |  1.10 |    0.02 | 0.0105 |      88 B |        0.50 |
| RejectionSampleStackAlloc   | 2                       | 32     | 136.31 ns | 2.318 ns | 2.168 ns |  0.36 |    0.01 | 0.0105 |      88 B |        0.50 |


</details>

<p><img src="/assets/img/7aStackAlloc0.png" alt="Graph of Results for Table 6 with minimum special characters: 0" /></p>

<p><img src="/assets/img/7bStackAlloc1.png" alt="Graph of Results for Table 6 with minimum special characters: 1" /></p>

<p><img src="/assets/img/7cStackAlloc2.png" alt="Graph of Results for Table 6 with minimum special characters: 2" /></p>

<p>Checking in a loop has significantly reduced the overhead of counting special characters.</p>

<p>Stack allocation has barely changed running time, but has halved allocated bytes, as can be seen in these graphs by the purple allocation line.</p>

<h2 id="double-rejection">Double-rejection</h2>

<p>We’ve seen how rejection sampling avoids bias in the outcome when dealing with passwords that don’t meet minimum characters.</p>

<p>We can try a similar approach to avoid the bias in trying to sample from 74 characters.</p>

<p>If we first create 4 copies of each character, we pad our set to 222 characters. We also take the opportunity to sort the character map so the symbols are the first 48 characters in our expanded set.</p>

<p>If we then sample bytes, skipping any results outside of <code class="language-plaintext highlighter-rouge">0-221</code>, and checking for values &lt;=48 at the same time. Once we’ve generated a password, if we have the minimum, we return the password, else we keep going through the generated bytes.</p>

<p>We’ll need to take enough bytes into our buffer so that we can reasonably expect to not exhaust the buffer.</p>

<p>The chance of fewer than K special bytes in N characters is calculatable using the Binomial distribution, but to get a rough idea we’ll suggest people might want up to 1/4 their characters be symbols. For our worst case here, that is 8 symbols in 32. The probability of achieving this is only 0.135157, or roughly 1 in 7.</p>

<p>We also have only 222 in 256 chance of being able to use each byte, so we’ll need another 15% overhead to accomodate bytes rejected initially.</p>

<p>Together, we’ll need to ask for roughly 8.5 times as much random data as previous. For 32 length passwords, that is ~273 bytes, but we’ll be generous and round up to the next power of 2, and take a 512 byte buffer.</p>

<p>Alternatively, we can have a strategy in place for getting a new buffer if we run out, and therefore we can be more conservative and target a 256 byte buffer, which could have performance benefits given it can be addressed by a single byte.</p>

<p>We can parameterise the buffer length to find an acceptable trade-off between requesting too much data, and having to re-fill the buffer.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">Benchmark</span><span class="p">]</span>
<span class="k">public</span> <span class="kt">string</span> <span class="nf">DoubleRejection</span><span class="p">()</span>
<span class="p">{</span>
    <span class="n">Span</span><span class="p">&lt;</span><span class="kt">byte</span><span class="p">&gt;</span> <span class="n">bytebuffer</span> <span class="p">=</span> <span class="k">stackalloc</span> <span class="kt">byte</span><span class="p">[</span><span class="n">RandomBufferLength</span><span class="p">];</span>
    <span class="n">Span</span><span class="p">&lt;</span><span class="kt">char</span><span class="p">&gt;</span> <span class="n">buffer</span> <span class="p">=</span> <span class="k">stackalloc</span> <span class="kt">char</span><span class="p">[</span><span class="n">Length</span><span class="p">];</span>


    <span class="k">while</span> <span class="p">(</span><span class="k">true</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
        <span class="n">RandomNumberGenerator</span><span class="p">.</span><span class="nf">Fill</span><span class="p">(</span><span class="n">bytebuffer</span><span class="p">);</span>
        <span class="kt">int</span> <span class="n">charIndex</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
        <span class="kt">bool</span> <span class="n">metMinimum</span> <span class="p">=</span> <span class="k">false</span><span class="p">;</span>

        <span class="kt">int</span> <span class="n">specialChars</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>

        <span class="k">while</span> <span class="p">(</span><span class="n">i</span> <span class="p">&lt;</span> <span class="n">RandomBufferLength</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="kt">byte</span> <span class="k">value</span> <span class="p">=</span> <span class="n">bytebuffer</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
            <span class="k">if</span> <span class="p">(</span><span class="k">value</span> <span class="p">&gt;=</span> <span class="m">222</span><span class="p">)</span>
            <span class="p">{</span>
                <span class="n">i</span><span class="p">++;</span>
                <span class="k">continue</span><span class="p">;</span>
            <span class="p">}</span>

            <span class="n">buffer</span><span class="p">[</span><span class="n">charIndex</span><span class="p">]</span> <span class="p">=</span> <span class="n">charactersLongSet</span><span class="p">[</span><span class="n">bytebuffer</span><span class="p">[</span><span class="n">i</span><span class="p">]];</span>

            <span class="k">if</span> <span class="p">(!</span><span class="n">metMinimum</span> <span class="p">&amp;&amp;</span> <span class="p">(</span><span class="k">value</span> <span class="p">&lt;</span> <span class="m">36</span><span class="p">)</span> <span class="p">&amp;&amp;</span> <span class="p">(++</span><span class="n">specialChars</span> <span class="p">&gt;=</span> <span class="n">MinmumSpecialCharacters</span><span class="p">))</span>
            <span class="p">{</span>
                <span class="n">metMinimum</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
            <span class="p">}</span>

            <span class="k">if</span> <span class="p">(++</span><span class="n">charIndex</span> <span class="p">==</span> <span class="n">Length</span><span class="p">)</span>
            <span class="p">{</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">metMinimum</span><span class="p">)</span>
                <span class="p">{</span>
                    <span class="k">return</span> <span class="k">new</span><span class="p">(</span><span class="n">buffer</span><span class="p">);</span>
                <span class="p">}</span>

                <span class="c1">// reset charIndex</span>
                <span class="n">charIndex</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
                <span class="n">specialChars</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
                <span class="n">i</span><span class="p">++;</span>
                <span class="k">continue</span><span class="p">;</span>

            <span class="p">}</span>
            <span class="k">else</span>
            <span class="p">{</span>
                <span class="n">i</span><span class="p">++;</span>
                <span class="k">continue</span><span class="p">;</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<details>

<summary> Table of different buffer lengths with 32 length password requiring 8 special characters.</summary>

| Method                    | RandomBufferLength | MinmumSpecialCharacters | Length | Mean       | Error     | StdDev    | Gen0   | Allocated |
|-------------------------- |------------------- |------------------------ |------- |-----------:|----------:|----------:|-------:|----------:|
| **RandomBufferLengthsTest** | **32**                 | **8**                       | **32**     | **125.753 μs** | **1.8742 μs** | **1.6615 μs** |      **-** |      **88 B** |
| **RandomBufferLengthsTest** | **48**                 | **8**                       | **32**     |   **1.915 μs** | **0.0186 μs** | **0.0155 μs** | **0.0076** |      **88 B** |
| **RandomBufferLengthsTest** | **64**                 | **8**                       | **32**     |   **2.369 μs** | **0.0114 μs** | **0.0095 μs** | **0.0076** |      **88 B** |
| **RandomBufferLengthsTest** | **128**                | **8**                       | **32**     |   **1.507 μs** | **0.0226 μs** | **0.0211 μs** | **0.0095** |      **88 B** |
| **RandomBufferLengthsTest** | **256**                | **8**                       | **32**     |   **1.271 μs** | **0.0124 μs** | **0.0104 μs** | **0.0095** |      **88 B** |
| **RandomBufferLengthsTest** | **512**                | **8**                       | **32**     |   **1.196 μs** | **0.0071 μs** | **0.0059 μs** | **0.0095** |      **88 B** |
| **RandomBufferLengthsTest** | **1024**               | **8**                       | **32**     |   **1.267 μs** | **0.0063 μs** | **0.0056 μs** | **0.0095** |      **88 B** |

</details>

<p>Here we can see that the fastest time was indeed a 512 byte buffer. This will be overkill for shorter password lengths and fewer required minimum special characters, but we can see from the shortest buffer lengths that the cost of having to refill the buffer is great.</p>

<details>
<summary>Table of Results for Double Rejection</summary>

| Method           | RandomBufferLength | MinmumSpecialCharacters | Length | Mean     | Error   | StdDev   | Median   | Ratio | RatioSD | Gen0   | Allocated | Alloc Ratio |
|----------------- |------------------- |------------------------ |------- |---------:|--------:|---------:|---------:|------:|--------:|-------:|----------:|------------:|
| **StackAllocSecure** | **64**                 | **0**                       | **14**     | **253.4 ns** | **4.84 ns** |  **7.82 ns** | **250.1 ns** |  **1.00** |    **0.04** | **0.0067** |      **56 B** |        **1.00** |
| DoubleRejection  | 64                 | 0                       | 14     | 195.3 ns | 2.14 ns |  1.79 ns | 195.0 ns |  0.77 |    0.02 | 0.0067 |      56 B |        1.00 |
|                  |                    |                         |        |          |         |          |          |       |         |        |           |             |
| **StackAllocSecure** | **64**                 | **0**                       | **24**     | **326.0 ns** | **2.69 ns** |  **2.99 ns** | **325.8 ns** |  **1.00** |    **0.01** | **0.0086** |      **72 B** |        **1.00** |
| DoubleRejection  | 64                 | 0                       | 24     | 219.2 ns | 2.09 ns |  1.75 ns | 218.8 ns |  0.67 |    0.01 | 0.0086 |      72 B |        1.00 |
|                  |                    |                         |        |          |         |          |          |       |         |        |           |             |
| **StackAllocSecure** | **64**                 | **0**                       | **32**     | **395.1 ns** | **3.37 ns** |  **2.81 ns** | **393.3 ns** |  **1.00** |    **0.01** | **0.0105** |      **88 B** |        **1.00** |
| DoubleRejection  | 64                 | 0                       | 32     | 245.2 ns | 4.88 ns |  7.88 ns | 242.4 ns |  0.62 |    0.02 | 0.0105 |      88 B |        1.00 |
|                  |                    |                         |        |          |         |          |          |       |         |        |           |             |
| **StackAllocSecure** | **64**                 | **1**                       | **14**     | **251.4 ns** | **5.02 ns** |  **9.18 ns** | **245.1 ns** |  **1.00** |    **0.05** | **0.0067** |      **56 B** |        **1.00** |
| DoubleRejection  | 64                 | 1                       | 14     | 205.5 ns | 4.11 ns |  6.28 ns | 204.4 ns |  0.82 |    0.04 | 0.0067 |      56 B |        1.00 |
|                  |                    |                         |        |          |         |          |          |       |         |        |           |             |
| **StackAllocSecure** | **64**                 | **1**                       | **24**     | **328.9 ns** | **6.59 ns** | **11.36 ns** | **334.6 ns** |  **1.00** |    **0.05** | **0.0086** |      **72 B** |        **1.00** |
| DoubleRejection  | 64                 | 1                       | 24     | 231.9 ns | 4.62 ns |  7.19 ns | 227.7 ns |  0.71 |    0.03 | 0.0086 |      72 B |        1.00 |
|                  |                    |                         |        |          |         |          |          |       |         |        |           |             |
| **StackAllocSecure** | **64**                 | **1**                       | **32**     | **416.2 ns** | **8.27 ns** | **13.36 ns** | **425.9 ns** |  **1.00** |    **0.04** | **0.0105** |      **88 B** |        **1.00** |
| DoubleRejection  | 64                 | 1                       | 32     | 258.2 ns | 5.18 ns |  7.60 ns | 255.2 ns |  0.62 |    0.03 | 0.0105 |      88 B |        1.00 |
|                  |                    |                         |        |          |         |          |          |       |         |        |           |             |
| **StackAllocSecure** | **64**                 | **2**                       | **14**     | **340.6 ns** | **6.80 ns** | **10.98 ns** | **334.7 ns** |  **1.00** |    **0.04** | **0.0067** |      **56 B** |        **1.00** |
| DoubleRejection  | 64                 | 2                       | 14     | 232.6 ns | 3.84 ns |  3.41 ns | 230.6 ns |  0.68 |    0.02 | 0.0067 |      56 B |        1.00 |
|                  |                    |                         |        |          |         |          |          |       |         |        |           |             |
| **StackAllocSecure** | **64**                 | **2**                       | **24**     | **351.7 ns** | **3.23 ns** |  **3.46 ns** | **350.4 ns** |  **1.00** |    **0.01** | **0.0086** |      **72 B** |        **1.00** |
| DoubleRejection  | 64                 | 2                       | 24     | 240.8 ns | 1.58 ns |  1.32 ns | 240.2 ns |  0.68 |    0.01 | 0.0086 |      72 B |        1.00 |
|                  |                    |                         |        |          |         |          |          |       |         |        |           |             |
| **StackAllocSecure** | **64**                 | **2**                       | **32**     | **401.7 ns** | **1.78 ns** |  **1.48 ns** | **402.0 ns** |  **1.00** |    **0.01** | **0.0105** |      **88 B** |        **1.00** |
| DoubleRejection  | 64                 | 2                       | 32     | 263.9 ns | 2.68 ns |  2.51 ns | 263.6 ns |  0.66 |    0.01 | 0.0105 |      88 B |        1.00 |
|                  |                    |                         |        |          |         |          |          |       |         |        |           |             |
| **StackAllocSecure** | **128**                | **0**                       | **14**     | **250.8 ns** | **1.83 ns** |  **1.71 ns** | **250.9 ns** |  **1.00** |    **0.01** | **0.0067** |      **56 B** |        **1.00** |
| DoubleRejection  | 128                | 0                       | 14     | 264.2 ns | 1.49 ns |  1.39 ns | 264.3 ns |  1.05 |    0.01 | 0.0067 |      56 B |        1.00 |
|                  |                    |                         |        |          |         |          |          |       |         |        |           |             |
| **StackAllocSecure** | **128**                | **0**                       | **24**     | **323.7 ns** | **2.31 ns** |  **2.04 ns** | **323.4 ns** |  **1.00** |    **0.01** | **0.0086** |      **72 B** |        **1.00** |
| DoubleRejection  | 128                | 0                       | 24     | 288.0 ns | 1.54 ns |  1.37 ns | 288.2 ns |  0.89 |    0.01 | 0.0086 |      72 B |        1.00 |
|                  |                    |                         |        |          |         |          |          |       |         |        |           |             |
| **StackAllocSecure** | **128**                | **0**                       | **32**     | **385.6 ns** | **3.37 ns** |  **3.15 ns** | **384.0 ns** |  **1.00** |    **0.01** | **0.0105** |      **88 B** |        **1.00** |
| DoubleRejection  | 128                | 0                       | 32     | 304.6 ns | 0.99 ns |  0.83 ns | 304.8 ns |  0.79 |    0.01 | 0.0105 |      88 B |        1.00 |
|                  |                    |                         |        |          |         |          |          |       |         |        |           |             |
| **StackAllocSecure** | **128**                | **1**                       | **14**     | **241.0 ns** | **1.54 ns** |  **1.44 ns** | **241.5 ns** |  **1.00** |    **0.01** | **0.0067** |      **56 B** |        **1.00** |
| DoubleRejection  | 128                | 1                       | 14     | 259.3 ns | 1.20 ns |  1.12 ns | 259.4 ns |  1.08 |    0.01 | 0.0067 |      56 B |        1.00 |
|                  |                    |                         |        |          |         |          |          |       |         |        |           |             |
| **StackAllocSecure** | **128**                | **1**                       | **24**     | **309.8 ns** | **1.19 ns** |  **1.06 ns** | **309.5 ns** |  **1.00** |    **0.00** | **0.0086** |      **72 B** |        **1.00** |
| DoubleRejection  | 128                | 1                       | 24     | 280.5 ns | 1.32 ns |  1.10 ns | 280.8 ns |  0.91 |    0.00 | 0.0086 |      72 B |        1.00 |
|                  |                    |                         |        |          |         |          |          |       |         |        |           |             |
| **StackAllocSecure** | **128**                | **1**                       | **32**     | **393.9 ns** | **1.29 ns** |  **1.01 ns** | **393.9 ns** |  **1.00** |    **0.00** | **0.0105** |      **88 B** |        **1.00** |
| DoubleRejection  | 128                | 1                       | 32     | 300.8 ns | 2.33 ns |  2.18 ns | 300.2 ns |  0.76 |    0.01 | 0.0105 |      88 B |        1.00 |
|                  |                    |                         |        |          |         |          |          |       |         |        |           |             |
| **StackAllocSecure** | **128**                | **2**                       | **14**     | **329.2 ns** | **2.28 ns** |  **2.02 ns** | **329.0 ns** |  **1.00** |    **0.01** | **0.0067** |      **56 B** |        **1.00** |
| DoubleRejection  | 128                | 2                       | 14     | 289.9 ns | 0.81 ns |  0.68 ns | 289.8 ns |  0.88 |    0.01 | 0.0067 |      56 B |        1.00 |
|                  |                    |                         |        |          |         |          |          |       |         |        |           |             |
| **StackAllocSecure** | **128**                | **2**                       | **24**     | **350.2 ns** | **3.57 ns** |  **3.34 ns** | **348.5 ns** |  **1.00** |    **0.01** | **0.0086** |      **72 B** |        **1.00** |
| DoubleRejection  | 128                | 2                       | 24     | 302.0 ns | 1.53 ns |  1.44 ns | 302.4 ns |  0.86 |    0.01 | 0.0086 |      72 B |        1.00 |
|                  |                    |                         |        |          |         |          |          |       |         |        |           |             |
| **StackAllocSecure** | **128**                | **2**                       | **32**     | **404.5 ns** | **1.94 ns** |  **1.51 ns** | **405.3 ns** |  **1.00** |    **0.01** | **0.0105** |      **88 B** |        **1.00** |
| DoubleRejection  | 128                | 2                       | 32     | 314.7 ns | 1.85 ns |  1.55 ns | 314.4 ns |  0.78 |    0.00 | 0.0105 |      88 B |        1.00 |
|                  |                    |                         |        |          |         |          |          |       |         |        |           |             |
| **StackAllocSecure** | **256**                | **0**                       | **14**     | **247.3 ns** | **1.13 ns** |  **0.94 ns** | **246.8 ns** |  **1.00** |    **0.01** | **0.0067** |      **56 B** |        **1.00** |
| DoubleRejection  | 256                | 0                       | 14     | 304.1 ns | 5.40 ns |  5.05 ns | 303.9 ns |  1.23 |    0.02 | 0.0067 |      56 B |        1.00 |
|                  |                    |                         |        |          |         |          |          |       |         |        |           |             |
| **StackAllocSecure** | **256**                | **0**                       | **24**     | **326.3 ns** | **2.41 ns** |  **2.26 ns** | **326.3 ns** |  **1.00** |    **0.01** | **0.0086** |      **72 B** |        **1.00** |
| DoubleRejection  | 256                | 0                       | 24     | 322.3 ns | 1.55 ns |  1.45 ns | 322.4 ns |  0.99 |    0.01 | 0.0086 |      72 B |        1.00 |
|                  |                    |                         |        |          |         |          |          |       |         |        |           |             |
| **StackAllocSecure** | **256**                | **0**                       | **32**     | **394.8 ns** | **2.43 ns** |  **2.27 ns** | **394.6 ns** |  **1.00** |    **0.01** | **0.0105** |      **88 B** |        **1.00** |
| DoubleRejection  | 256                | 0                       | 32     | 339.9 ns | 1.49 ns |  1.32 ns | 339.8 ns |  0.86 |    0.01 | 0.0105 |      88 B |        1.00 |
|                  |                    |                         |        |          |         |          |          |       |         |        |           |             |
| **StackAllocSecure** | **256**                | **1**                       | **14**     | **250.9 ns** | **2.08 ns** |  **1.95 ns** | **250.8 ns** |  **1.00** |    **0.01** | **0.0067** |      **56 B** |        **1.00** |
| DoubleRejection  | 256                | 1                       | 14     | 293.7 ns | 2.22 ns |  2.08 ns | 292.6 ns |  1.17 |    0.01 | 0.0067 |      56 B |        1.00 |
|                  |                    |                         |        |          |         |          |          |       |         |        |           |             |
| **StackAllocSecure** | **256**                | **1**                       | **24**     | **311.3 ns** | **2.73 ns** |  **2.56 ns** | **310.0 ns** |  **1.00** |    **0.01** | **0.0086** |      **72 B** |        **1.00** |
| DoubleRejection  | 256                | 1                       | 24     | 315.7 ns | 1.67 ns |  1.48 ns | 315.9 ns |  1.01 |    0.01 | 0.0086 |      72 B |        1.00 |
|                  |                    |                         |        |          |         |          |          |       |         |        |           |             |
| **StackAllocSecure** | **256**                | **1**                       | **32**     | **377.3 ns** | **2.79 ns** |  **2.47 ns** | **376.6 ns** |  **1.00** |    **0.01** | **0.0105** |      **88 B** |        **1.00** |
| DoubleRejection  | 256                | 1                       | 32     | 340.8 ns | 2.59 ns |  2.43 ns | 340.1 ns |  0.90 |    0.01 | 0.0105 |      88 B |        1.00 |
|                  |                    |                         |        |          |         |          |          |       |         |        |           |             |
| **StackAllocSecure** | **256**                | **2**                       | **14**     | **330.0 ns** | **2.27 ns** |  **2.12 ns** | **330.8 ns** |  **1.00** |    **0.01** | **0.0067** |      **56 B** |        **1.00** |
| DoubleRejection  | 256                | 2                       | 14     | 319.7 ns | 1.13 ns |  1.00 ns | 319.4 ns |  0.97 |    0.01 | 0.0067 |      56 B |        1.00 |
|                  |                    |                         |        |          |         |          |          |       |         |        |           |             |
| **StackAllocSecure** | **256**                | **2**                       | **24**     | **346.6 ns** | **0.81 ns** |  **0.63 ns** | **346.7 ns** |  **1.00** |    **0.00** | **0.0086** |      **72 B** |        **1.00** |
| DoubleRejection  | 256                | 2                       | 24     | 329.9 ns | 1.78 ns |  1.58 ns | 330.2 ns |  0.95 |    0.00 | 0.0086 |      72 B |        1.00 |
|                  |                    |                         |        |          |         |          |          |       |         |        |           |             |
| **StackAllocSecure** | **256**                | **2**                       | **32**     | **401.4 ns** | **3.71 ns** |  **3.47 ns** | **399.5 ns** |  **1.00** |    **0.01** | **0.0105** |      **88 B** |        **1.00** |
| DoubleRejection  | 256                | 2                       | 32     | 344.3 ns | 2.96 ns |  2.63 ns | 343.7 ns |  0.86 |    0.01 | 0.0105 |      88 B |        1.00 |

</details>

<p><img src="/assets/img/8DoubleRejection.png" alt="Graph for Double Rejection benchmark" title="Lower is better" /></p>

<p>That’s a great speedup with a 64 byte buffer, a modest saving with a 128 byte buffer, and a regression generating 14 character passwords with a 256 byte buffer but a modest saving generating 32 character passwords.</p>

<h2 id="conclusion">Conclusion</h2>

<p>We’ve gone from the initial co-pilot generated code with its naive use of <code class="language-plaintext highlighter-rouge">new Random()</code> and lack of features, to a couple of performant and secure alternatives. We can now configure minimum symbol guarantees in our generator and we can alter settings to fine-tune the performance for the length of password we want to generate.</p>

<p>If we want to minimise runtime, we can use a reduced character set to avoid having to reject bytes. I would recommend this approach.</p>

<h1 id="notes">Notes</h1>

<h2 id="future-improvements">Future improvements</h2>

<p>Investigate alternative Crypto-secure Random Number Generators (CSPRNG) or Key Derivation Functions (KDF) for faster generation.</p>

<h2 id="motivation">Motivation</h2>

<p>You may be thinking:</p>

<blockquote>
  <p>Password generation shouldn’t happen often enough that even 1ms, let alone 1µs should matter. All this optimization is a waste.</p>
</blockquote>

<p>You’d be right if the goal of this was to optimise password generation in production rather than a personal exercise and demonstration of the tooling with an easy to understand example.</p>

<p>I also feel that code quality, including performance, is still important. Two things happen if you let in poor quality code:</p>

<ol>
  <li>The code gets copied around, and used outside of its original context.</li>
  <li>Your team gets used to checking in bad code, and a “Looks good to me” culture.</li>
</ol>

<p>Password generation almost certainly isn’t a hot path, but who knows when next time someone might need some random data. If this code is hanging around your code-base, it can easily get copied and used in a different context where the performance concerns are more valid.</p>

<p>The second is more subjective, but it’s a matter of pride to work on a team where the first code that co-pilot generated  would get picked apart and not accepted. A culture of rubber-stamping PRs can quickly set in if standards aren’t held up and assumptions aren’t checked.</p>

<h2 id="false-optimisations">False Optimisations</h2>

<p>It’s worth noting here some approaches taken that did not improve performance. I originally wanted to include them in the bulk of the post but I felt it was already getting too long, so they got cut from the final version.</p>

<p>### Replacing the character lookup <code class="language-plaintext highlighter-rouge">string</code> with <code class="language-plaintext highlighter-rouge">char[]</code></p>

<p>This actually decreased performance. <code class="language-plaintext highlighter-rouge">string</code> in c# are immutable and as such can be treated as a <code class="language-plaintext highlighter-rouge">ReadOnlySpan&lt;char&gt;</code> wherever needed, and are fast to index against. There was no improvement to using <code class="language-plaintext highlighter-rouge">char[]</code>, just a neglible downside.</p>

<p>### Avoiding Modulo with a longer lookup</p>

<p>This one was flat. I had the idea to avoid the modulo by using a repeated string, i.e. instead of:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">string</span> <span class="n">characters</span> <span class="p">=</span> <span class="s">"abcdefghjkmnpqrstuwxyzABCDEFGHJKLMNPQRSTVWXYZ0123456789@#$%&amp;()_+"</span><span class="p">;</span>
<span class="kt">char</span> <span class="n">randomChar</span> <span class="p">=</span> <span class="n">characters</span><span class="p">[</span><span class="k">value</span> <span class="p">%</span> <span class="m">64</span><span class="p">];</span>
</code></pre></div></div>

<p>You would do:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">string</span> <span class="n">characters</span> <span class="p">=</span> <span class="s">"abcdefghjkmnpqrstuwxyzABCDEFGHJKLMNPQRSTVWXYZ0123456789@#$%&amp;()_+abcdefghjkmnpqrstuwxyzABCDEFGHJKLMNPQRSTVWXYZ0123456789@#$%&amp;()_+abcdefghjkmnpqrstuwxyzABCDEFGHJKLMNPQRSTVWXYZ0123456789@#$%&amp;()_+abcdefghjkmnpqrstuwxyzABCDEFGHJKLMNPQRSTVWXYZ0123456789@#$%&amp;()_+"</span><span class="p">;</span>
<span class="kt">char</span> <span class="n">randomChar</span> <span class="p">=</span> <span class="n">characters</span><span class="p">[</span><span class="k">value</span><span class="p">];</span>
</code></pre></div></div>

<p>I was hopeful for this one, but it had no appreciable effect on performance.</p>

<h3 id="avoiding-modulo-with-bittricks--bitshifiting">Avoiding modulo with bittricks / bitshifiting</h3>
<p>Other approaches to avoiding modulo such as bitwise <code class="language-plaintext highlighter-rouge">&amp;</code> or bitshifting were the same performance as modulo. I didn’t check whether they compiled to the same IL but I’d imagine they did. I’m not smarter than a compiler.</p>

<h3 id="stringcreate">String.Create</h3>
<p>I’d heard that rather than <code class="language-plaintext highlighter-rouge">new string(buffer)</code>, there were faster methods to allocate strings with <code class="language-plaintext highlighter-rouge">String.Create</code>. I experimented with what is an awkward method and I couldn’t get it close. That may be down to the very small string values I’m dealing with.</p>

<h3 id="randomnumbergeneratorgetbytes">RandomNumberGenerator.GetBytes</h3>

<p>Instead of <code class="language-plaintext highlighter-rouge">RandomNumberGenerator.Fill</code> you can use <code class="language-plaintext highlighter-rouge">RandomNumberGenerator.GetBytes</code> to return the array. This is cleaner syntax but it has no effect on performance. I left <code class="language-plaintext highlighter-rouge">RandomNumberGenerator.Fill</code> to make the example have syntax that matched <code class="language-plaintext highlighter-rouge">Random.Shared.NextBytes</code> more closely to make it clear they were otherwise the same.</p>

<h3 id="matching-special-characters-with-searchvalues">Matching special characters with SearchValues<T></T></h3>
<p>Instead of <code class="language-plaintext highlighter-rouge">!char.IsAsciiLetterOrDigit</code>, I tried <code class="language-plaintext highlighter-rouge">System.Buffer.SearchValues</code> with a span of special characters. This added ~20ns to the runtime. ( See <code class="language-plaintext highlighter-rouge">Searching.cs</code> for code. )</p>

<h2 id="addendum">Addendum</h2>

<h3 id="credits">Credits</h3>

<p>Graphs generated with https://chartbenchmark.net/</p>

<p>Thank you to members on the <a href="https://discord.gg/csharp">csharp discord</a> for reviewing a draft and finding mistakes, posting improvements and suggesting alternative approaches.</p>

<h3 id="code---pull-requests-welcome">Code - Pull Requests Welcome</h3>

<p>The code to generate all the results in this post is available at <a href="https://github.com/richardcocks/passwordgen">https://github.com/richardcocks/passwordgen</a></p>

<p>Pull requests are welcomed. If you have improvements or corrections, please send me a PR with the change.</p>

<h3 id="full-comparison-table">Full comparison table</h3>

<p>Here’s a benchmark run with most of the functions, with a single baseline of the original co-pilot output. This run was done before the very final versions of some of the functions, so some minor differences in naming or results may be observed. ( This full run takes well over an hour to generate. )</p>

<details>

<summary>Full results comparison</summary>

These results can be recreated by running the program in this repository.

```

BenchmarkDotNet v0.14.0, Windows 10 (10.0.19045.5608/22H2/2022Update)
AMD Ryzen 7 3800X, 1 CPU, 16 logical and 8 physical cores
.NET SDK 10.0.100-preview.2.25164.34
  [Host]     : .NET 10.0.0 (10.0.25.16302), X64 RyuJIT AVX2
  DefaultJob : .NET 10.0.0 (10.0.25.16302), X64 RyuJIT AVX2


```
| Method                       | Categories | Length | MinmumSpecialCharacters | Mean        | Error     | StdDev    | Median      | Ratio | RatioSD | Gen0   | Allocated | Alloc Ratio |
|----------------------------- |----------- |------- |------------------------ |------------:|----------:|----------:|------------:|------:|--------:|-------:|----------:|------------:|
| **GeneratePassword**             | ****           | **14**     | **0**                       |   **215.75 ns** |  **3.793 ns** |  **3.548 ns** |   **214.55 ns** |  **1.00** |    **0.02** | **0.0753** |     **632 B** |        **1.00** |
| SecureRandom                 |            | 14     | 0                       | 1,385.24 ns |  8.990 ns |  7.969 ns | 1,385.91 ns |  6.42 |    0.11 | 0.0668 |     560 B |        0.89 |
| GeneratePasswordSharedRandom |            | 14     | 0                       |   129.72 ns |  1.398 ns |  1.167 ns |   129.88 ns |  0.60 |    0.01 | 0.0668 |     560 B |        0.89 |
|                              |            |        |                         |             |           |           |             |       |         |        |           |             |
| **GeneratePassword**             | ****           | **14**     | **1**                       |   **210.58 ns** |  **2.404 ns** |  **2.007 ns** |   **210.05 ns** |  **1.00** |    **0.01** | **0.0753** |     **632 B** |        **1.00** |
| SecureRandom                 |            | 14     | 1                       | 1,340.80 ns | 23.188 ns | 30.151 ns | 1,329.52 ns |  6.37 |    0.15 | 0.0668 |     560 B |        0.89 |
| GeneratePasswordSharedRandom |            | 14     | 1                       |   128.65 ns |  1.271 ns |  0.992 ns |   128.77 ns |  0.61 |    0.01 | 0.0668 |     560 B |        0.89 |
|                              |            |        |                         |             |           |           |             |       |         |        |           |             |
| **GeneratePassword**             | ****           | **14**     | **2**                       |   **211.72 ns** |  **4.151 ns** |  **4.077 ns** |   **209.72 ns** |  **1.00** |    **0.03** | **0.0753** |     **632 B** |        **1.00** |
| SecureRandom                 |            | 14     | 2                       | 1,359.95 ns | 11.902 ns | 11.133 ns | 1,356.93 ns |  6.43 |    0.13 | 0.0668 |     560 B |        0.89 |
| GeneratePasswordSharedRandom |            | 14     | 2                       |   127.48 ns |  1.065 ns |  0.832 ns |   127.52 ns |  0.60 |    0.01 | 0.0668 |     560 B |        0.89 |
|                              |            |        |                         |             |           |           |             |       |         |        |           |             |
| **GeneratePassword**             | ****           | **24**     | **0**                       |   **305.68 ns** |  **5.666 ns** |  **5.300 ns** |   **305.30 ns** |  **1.00** |    **0.02** | **0.1516** |    **1272 B** |        **1.00** |
| SecureRandom                 |            | 24     | 0                       | 2,395.31 ns | 46.895 ns | 55.825 ns | 2,370.40 ns |  7.84 |    0.22 | 0.1411 |    1200 B |        0.94 |
| GeneratePasswordSharedRandom |            | 24     | 0                       |   229.69 ns |  3.249 ns |  2.536 ns |   229.95 ns |  0.75 |    0.01 | 0.1433 |    1200 B |        0.94 |
|                              |            |        |                         |             |           |           |             |       |         |        |           |             |
| **GeneratePassword**             | ****           | **24**     | **1**                       |   **305.93 ns** |  **3.580 ns** |  **3.174 ns** |   **306.07 ns** |  **1.00** |    **0.01** | **0.1516** |    **1272 B** |        **1.00** |
| SecureRandom                 |            | 24     | 1                       | 2,287.34 ns | 19.272 ns | 17.084 ns | 2,283.84 ns |  7.48 |    0.09 | 0.1411 |    1200 B |        0.94 |
| GeneratePasswordSharedRandom |            | 24     | 1                       |   227.92 ns |  4.142 ns |  3.459 ns |   227.26 ns |  0.75 |    0.01 | 0.1433 |    1200 B |        0.94 |
|                              |            |        |                         |             |           |           |             |       |         |        |           |             |
| **GeneratePassword**             | ****           | **24**     | **2**                       |   **301.40 ns** |  **3.863 ns** |  **3.016 ns** |   **301.27 ns** |  **1.00** |    **0.01** | **0.1516** |    **1272 B** |        **1.00** |
| SecureRandom                 |            | 24     | 2                       | 2,323.11 ns |  9.154 ns |  7.644 ns | 2,322.33 ns |  7.71 |    0.08 | 0.1411 |    1200 B |        0.94 |
| GeneratePasswordSharedRandom |            | 24     | 2                       |   226.98 ns |  4.383 ns |  3.660 ns |   226.45 ns |  0.75 |    0.01 | 0.1433 |    1200 B |        0.94 |
|                              |            |        |                         |             |           |           |             |       |         |        |           |             |
| **GeneratePassword**             | ****           | **32**     | **0**                       |   **374.21 ns** |  **4.377 ns** |  **4.095 ns** |   **372.42 ns** |  **1.00** |    **0.01** | **0.2303** |    **1928 B** |        **1.00** |
| SecureRandom                 |            | 32     | 0                       | 2,940.00 ns | 20.856 ns | 18.488 ns | 2,944.29 ns |  7.86 |    0.10 | 0.2213 |    1856 B |        0.96 |
| GeneratePasswordSharedRandom |            | 32     | 0                       |   315.04 ns |  2.574 ns |  2.009 ns |   315.06 ns |  0.84 |    0.01 | 0.2217 |    1856 B |        0.96 |
|                              |            |        |                         |             |           |           |             |       |         |        |           |             |
| **GeneratePassword**             | ****           | **32**     | **1**                       |   **378.56 ns** |  **6.198 ns** |  **5.176 ns** |   **378.47 ns** |  **1.00** |    **0.02** | **0.2303** |    **1928 B** |        **1.00** |
| SecureRandom                 |            | 32     | 1                       | 3,107.14 ns | 28.586 ns | 25.341 ns | 3,101.47 ns |  8.21 |    0.13 | 0.2213 |    1856 B |        0.96 |
| GeneratePasswordSharedRandom |            | 32     | 1                       |   304.02 ns |  1.081 ns |  0.844 ns |   304.00 ns |  0.80 |    0.01 | 0.2217 |    1856 B |        0.96 |
|                              |            |        |                         |             |           |           |             |       |         |        |           |             |
| **GeneratePassword**             | ****           | **32**     | **2**                       |   **390.50 ns** |  **6.386 ns** |  **5.974 ns** |   **388.73 ns** |  **1.00** |    **0.02** | **0.2303** |    **1928 B** |        **1.00** |
| SecureRandom                 |            | 32     | 2                       | 3,128.55 ns | 30.199 ns | 28.248 ns | 3,127.64 ns |  8.01 |    0.14 | 0.2213 |    1856 B |        0.96 |
| GeneratePasswordSharedRandom |            | 32     | 2                       |   310.85 ns |  4.786 ns |  3.996 ns |   310.55 ns |  0.80 |    0.02 | 0.2217 |    1856 B |        0.96 |
|                              |            |        |                         |             |           |           |             |       |         |        |           |             |
| **StringBuilderSecure**          | **Secure**     | **14**     | **0**                       | **1,185.09 ns** | **19.311 ns** | **18.063 ns** | **1,174.75 ns** |     **?** |       **?** | **0.0191** |     **160 B** |           **?** |
| CharArraySecure              | Secure     | 14     | 0                       | 1,158.14 ns |  4.036 ns |  3.776 ns | 1,158.91 ns |     ? |       ? | 0.0134 |     112 B |           ? |
| BufferSecure                 | Secure     | 14     | 0                       |    85.71 ns |  0.503 ns |  0.420 ns |    85.58 ns |     ? |       ? | 0.0181 |     152 B |           ? |
| GetItemsSecure               | Secure     | 14     | 0                       |   212.04 ns |  2.828 ns |  2.646 ns |   210.90 ns |     ? |       ? | 0.0134 |     112 B |           ? |
| RejectionSampleSecure        | Secure     | 14     | 0                       |    98.66 ns |  0.955 ns |  0.893 ns |    98.63 ns |     ? |       ? | 0.0181 |     152 B |           ? |
| GetItemsWithRejectionSecure  | Secure     | 14     | 0                       |   281.52 ns |  1.200 ns |  1.002 ns |   281.65 ns |     ? |       ? | 0.0134 |     112 B |           ? |
| SpecialLoopSecure            | Secure     | 14     | 0                       |   239.94 ns |  1.372 ns |  1.146 ns |   239.51 ns |     ? |       ? | 0.0134 |     112 B |           ? |
| StackAllocSecure             | Secure     | 14     | 0                       |   245.24 ns |  1.685 ns |  1.577 ns |   245.40 ns |     ? |       ? | 0.0067 |      56 B |           ? |
|                              |            |        |                         |             |           |           |             |       |         |        |           |             |
| **StringBuilderSecure**          | **Secure**     | **14**     | **1**                       | **1,188.01 ns** |  **9.011 ns** |  **7.525 ns** | **1,187.19 ns** |     **?** |       **?** | **0.0191** |     **160 B** |           **?** |
| CharArraySecure              | Secure     | 14     | 1                       | 1,161.76 ns |  5.315 ns |  4.712 ns | 1,160.91 ns |     ? |       ? | 0.0134 |     112 B |           ? |
| BufferSecure                 | Secure     | 14     | 1                       |    86.07 ns |  0.390 ns |  0.326 ns |    86.01 ns |     ? |       ? | 0.0181 |     152 B |           ? |
| GetItemsSecure               | Secure     | 14     | 1                       |   212.12 ns |  2.252 ns |  2.107 ns |   211.24 ns |     ? |       ? | 0.0134 |     112 B |           ? |
| RejectionSampleSecure        | Secure     | 14     | 1                       |   101.42 ns |  0.613 ns |  0.512 ns |   101.26 ns |     ? |       ? | 0.0181 |     152 B |           ? |
| GetItemsWithRejectionSecure  | Secure     | 14     | 1                       |   300.96 ns |  2.228 ns |  2.084 ns |   300.47 ns |     ? |       ? | 0.0138 |     117 B |           ? |
| SpecialLoopSecure            | Secure     | 14     | 1                       |   230.91 ns |  1.113 ns |  0.929 ns |   230.93 ns |     ? |       ? | 0.0134 |     112 B |           ? |
| StackAllocSecure             | Secure     | 14     | 1                       |   242.83 ns |  2.003 ns |  1.874 ns |   242.11 ns |     ? |       ? | 0.0067 |      56 B |           ? |
|                              |            |        |                         |             |           |           |             |       |         |        |           |             |
| **StringBuilderSecure**          | **Secure**     | **14**     | **2**                       | **1,182.45 ns** |  **4.747 ns** |  **4.440 ns** | **1,183.29 ns** |     **?** |       **?** | **0.0191** |     **160 B** |           **?** |
| CharArraySecure              | Secure     | 14     | 2                       | 1,170.14 ns |  6.867 ns |  6.424 ns | 1,170.44 ns |     ? |       ? | 0.0134 |     112 B |           ? |
| BufferSecure                 | Secure     | 14     | 2                       |    84.92 ns |  0.628 ns |  0.524 ns |    84.64 ns |     ? |       ? | 0.0181 |     152 B |           ? |
| GetItemsSecure               | Secure     | 14     | 2                       |   209.01 ns |  1.692 ns |  1.583 ns |   208.98 ns |     ? |       ? | 0.0134 |     112 B |           ? |
| RejectionSampleSecure        | Secure     | 14     | 2                       |   106.88 ns |  0.855 ns |  0.800 ns |   106.71 ns |     ? |       ? | 0.0181 |     152 B |           ? |
| GetItemsWithRejectionSecure  | Secure     | 14     | 2                       |   386.26 ns |  2.061 ns |  1.721 ns |   386.12 ns |     ? |       ? | 0.0162 |     137 B |           ? |
| SpecialLoopSecure            | Secure     | 14     | 2                       |   325.62 ns |  1.919 ns |  1.795 ns |   325.56 ns |     ? |       ? | 0.0134 |     112 B |           ? |
| StackAllocSecure             | Secure     | 14     | 2                       |   341.45 ns |  2.575 ns |  2.408 ns |   341.46 ns |     ? |       ? | 0.0067 |      56 B |           ? |
|                              |            |        |                         |             |           |           |             |       |         |        |           |             |
| **StringBuilderSecure**          | **Secure**     | **24**     | **0**                       | **1,986.09 ns** |  **7.470 ns** |  **6.987 ns** | **1,990.54 ns** |     **?** |       **?** | **0.0229** |     **192 B** |           **?** |
| CharArraySecure              | Secure     | 24     | 0                       | 1,973.83 ns |  7.796 ns |  7.292 ns | 1,975.28 ns |     ? |       ? | 0.0153 |     144 B |           ? |
| BufferSecure                 | Secure     | 24     | 0                       |   108.45 ns |  1.711 ns |  1.601 ns |   108.34 ns |     ? |       ? | 0.0229 |     192 B |           ? |
| GetItemsSecure               | Secure     | 24     | 0                       |   299.18 ns |  2.914 ns |  2.726 ns |   298.42 ns |     ? |       ? | 0.0172 |     144 B |           ? |
| RejectionSampleSecure        | Secure     | 24     | 0                       |   119.60 ns |  0.658 ns |  0.549 ns |   119.57 ns |     ? |       ? | 0.0229 |     192 B |           ? |
| GetItemsWithRejectionSecure  | Secure     | 24     | 0                       |   404.17 ns |  2.775 ns |  2.596 ns |   404.11 ns |     ? |       ? | 0.0172 |     144 B |           ? |
| SpecialLoopSecure            | Secure     | 24     | 0                       |   312.67 ns |  1.939 ns |  1.619 ns |   312.76 ns |     ? |       ? | 0.0172 |     144 B |           ? |
| StackAllocSecure             | Secure     | 24     | 0                       |   322.41 ns |  1.980 ns |  1.852 ns |   322.38 ns |     ? |       ? | 0.0086 |      72 B |           ? |
|                              |            |        |                         |             |           |           |             |       |         |        |           |             |
| **StringBuilderSecure**          | **Secure**     | **24**     | **1**                       | **1,989.94 ns** |  **9.423 ns** |  **8.353 ns** | **1,990.50 ns** |     **?** |       **?** | **0.0229** |     **192 B** |           **?** |
| CharArraySecure              | Secure     | 24     | 1                       | 2,159.30 ns |  8.801 ns |  7.802 ns | 2,159.61 ns |     ? |       ? | 0.0153 |     144 B |           ? |
| BufferSecure                 | Secure     | 24     | 1                       |   107.50 ns |  1.434 ns |  1.341 ns |   107.10 ns |     ? |       ? | 0.0229 |     192 B |           ? |
| GetItemsSecure               | Secure     | 24     | 1                       |   293.72 ns |  2.800 ns |  2.619 ns |   292.97 ns |     ? |       ? | 0.0172 |     144 B |           ? |
| RejectionSampleSecure        | Secure     | 24     | 1                       |   125.20 ns |  1.823 ns |  1.705 ns |   124.90 ns |     ? |       ? | 0.0229 |     192 B |           ? |
| GetItemsWithRejectionSecure  | Secure     | 24     | 1                       |   416.30 ns |  2.899 ns |  2.570 ns |   415.79 ns |     ? |       ? | 0.0172 |     145 B |           ? |
| SpecialLoopSecure            | Secure     | 24     | 1                       |   318.78 ns |  2.812 ns |  2.630 ns |   318.29 ns |     ? |       ? | 0.0172 |     144 B |           ? |
| StackAllocSecure             | Secure     | 24     | 1                       |   312.46 ns |  1.519 ns |  1.269 ns |   312.75 ns |     ? |       ? | 0.0086 |      72 B |           ? |
|                              |            |        |                         |             |           |           |             |       |         |        |           |             |
| **StringBuilderSecure**          | **Secure**     | **24**     | **2**                       | **1,990.07 ns** |  **6.972 ns** |  **6.180 ns** | **1,989.67 ns** |     **?** |       **?** | **0.0229** |     **192 B** |           **?** |
| CharArraySecure              | Secure     | 24     | 2                       | 1,985.53 ns | 11.634 ns | 10.883 ns | 1,983.66 ns |     ? |       ? | 0.0153 |     144 B |           ? |
| BufferSecure                 | Secure     | 24     | 2                       |   108.31 ns |  1.357 ns |  1.203 ns |   107.83 ns |     ? |       ? | 0.0229 |     192 B |           ? |
| GetItemsSecure               | Secure     | 24     | 2                       |   305.47 ns |  3.098 ns |  2.898 ns |   305.27 ns |     ? |       ? | 0.0172 |     144 B |           ? |
| RejectionSampleSecure        | Secure     | 24     | 2                       |   133.99 ns |  1.627 ns |  1.442 ns |   133.84 ns |     ? |       ? | 0.0229 |     192 B |           ? |
| GetItemsWithRejectionSecure  | Secure     | 24     | 2                       |   445.34 ns |  3.244 ns |  3.035 ns |   446.11 ns |     ? |       ? | 0.0176 |     150 B |           ? |
| SpecialLoopSecure            | Secure     | 24     | 2                       |   361.00 ns |  3.072 ns |  2.874 ns |   360.94 ns |     ? |       ? | 0.0172 |     144 B |           ? |
| StackAllocSecure             | Secure     | 24     | 2                       |   362.64 ns |  2.274 ns |  2.127 ns |   362.30 ns |     ? |       ? | 0.0086 |      72 B |           ? |
|                              |            |        |                         |             |           |           |             |       |         |        |           |             |
| **StringBuilderSecure**          | **Secure**     | **32**     | **0**                       | **2,663.43 ns** | **14.367 ns** | **13.439 ns** | **2,662.94 ns** |     **?** |       **?** | **0.0267** |     **224 B** |           **?** |
| CharArraySecure              | Secure     | 32     | 0                       | 2,635.60 ns | 10.638 ns |  9.951 ns | 2,634.98 ns |     ? |       ? | 0.0191 |     176 B |           ? |
| BufferSecure                 | Secure     | 32     | 0                       |   120.60 ns |  0.777 ns |  0.689 ns |   120.71 ns |     ? |       ? | 0.0277 |     232 B |           ? |
| GetItemsSecure               | Secure     | 32     | 0                       |   365.84 ns |  6.409 ns |  5.995 ns |   364.31 ns |     ? |       ? | 0.0210 |     176 B |           ? |
| RejectionSampleSecure        | Secure     | 32     | 0                       |   156.50 ns |  0.690 ns |  0.539 ns |   156.52 ns |     ? |       ? | 0.0277 |     232 B |           ? |
| GetItemsWithRejectionSecure  | Secure     | 32     | 0                       |   499.75 ns |  3.139 ns |  2.782 ns |   499.69 ns |     ? |       ? | 0.0210 |     176 B |           ? |
| SpecialLoopSecure            | Secure     | 32     | 0                       |   398.02 ns |  3.087 ns |  2.887 ns |   398.12 ns |     ? |       ? | 0.0210 |     176 B |           ? |
| StackAllocSecure             | Secure     | 32     | 0                       |   390.82 ns |  3.490 ns |  3.264 ns |   389.70 ns |     ? |       ? | 0.0105 |      88 B |           ? |
|                              |            |        |                         |             |           |           |             |       |         |        |           |             |
| **StringBuilderSecure**          | **Secure**     | **32**     | **1**                       | **2,638.28 ns** | **13.093 ns** | **12.247 ns** | **2,634.78 ns** |     **?** |       **?** | **0.0267** |     **224 B** |           **?** |
| CharArraySecure              | Secure     | 32     | 1                       | 2,623.61 ns | 10.045 ns |  9.396 ns | 2,620.10 ns |     ? |       ? | 0.0191 |     176 B |           ? |
| BufferSecure                 | Secure     | 32     | 1                       |   117.34 ns |  0.484 ns |  0.405 ns |   117.34 ns |     ? |       ? | 0.0277 |     232 B |           ? |
| GetItemsSecure               | Secure     | 32     | 1                       |   372.87 ns |  3.183 ns |  2.977 ns |   372.13 ns |     ? |       ? | 0.0210 |     176 B |           ? |
| RejectionSampleSecure        | Secure     | 32     | 1                       |   151.72 ns |  2.136 ns |  1.893 ns |   151.65 ns |     ? |       ? | 0.0277 |     232 B |           ? |
| GetItemsWithRejectionSecure  | Secure     | 32     | 1                       |   504.52 ns |  4.325 ns |  4.045 ns |   504.67 ns |     ? |       ? | 0.0210 |     176 B |           ? |
| SpecialLoopSecure            | Secure     | 32     | 1                       |   386.11 ns |  2.910 ns |  2.722 ns |   386.58 ns |     ? |       ? | 0.0210 |     176 B |           ? |
| StackAllocSecure             | Secure     | 32     | 1                       |   376.84 ns |  2.314 ns |  2.165 ns |   376.26 ns |     ? |       ? | 0.0105 |      88 B |           ? |
|                              |            |        |                         |             |           |           |             |       |         |        |           |             |
| **StringBuilderSecure**          | **Secure**     | **32**     | **2**                       | **2,635.03 ns** |  **8.654 ns** |  **8.095 ns** | **2,636.25 ns** |     **?** |       **?** | **0.0267** |     **224 B** |           **?** |
| CharArraySecure              | Secure     | 32     | 2                       | 2,615.32 ns | 10.481 ns |  9.804 ns | 2,614.03 ns |     ? |       ? | 0.0191 |     176 B |           ? |
| BufferSecure                 | Secure     | 32     | 2                       |   116.30 ns |  1.251 ns |  1.044 ns |   116.03 ns |     ? |       ? | 0.0277 |     232 B |           ? |
| GetItemsSecure               | Secure     | 32     | 2                       |   370.29 ns |  3.049 ns |  2.546 ns |   370.88 ns |     ? |       ? | 0.0210 |     176 B |           ? |
| RejectionSampleSecure        | Secure     | 32     | 2                       |   157.19 ns |  2.703 ns |  2.257 ns |   156.89 ns |     ? |       ? | 0.0277 |     232 B |           ? |
| GetItemsWithRejectionSecure  | Secure     | 32     | 2                       |   518.26 ns |  3.547 ns |  3.318 ns |   517.21 ns |     ? |       ? | 0.0210 |     178 B |           ? |
| SpecialLoopSecure            | Secure     | 32     | 2                       |   401.38 ns |  3.397 ns |  3.178 ns |   400.96 ns |     ? |       ? | 0.0210 |     176 B |           ? |
| StackAllocSecure             | Secure     | 32     | 2                       |   400.59 ns |  3.066 ns |  2.867 ns |   400.15 ns |     ? |       ? | 0.0105 |      88 B |           ? |
|                              |            |        |                         |             |           |           |             |       |         |        |           |             |
| **StringBuilder**                | **Vulnerable** | **14**     | **0**                       |   **103.71 ns** |  **2.085 ns** |  **2.230 ns** |   **103.56 ns** |     **?** |       **?** | **0.0191** |     **160 B** |           **?** |
| CharArray                    | Vulnerable | 14     | 0                       |    66.52 ns |  0.477 ns |  0.423 ns |    66.42 ns |     ? |       ? | 0.0134 |     112 B |           ? |
| Buffer                       | Vulnerable | 14     | 0                       |    34.93 ns |  0.725 ns |  0.678 ns |    34.65 ns |     ? |       ? | 0.0181 |     152 B |           ? |
| GetItems                     | Vulnerable | 14     | 0                       |   125.68 ns |  0.874 ns |  0.730 ns |   125.64 ns |     ? |       ? | 0.0134 |     112 B |           ? |
| RejectionSample              | Vulnerable | 14     | 0                       |    37.67 ns |  0.467 ns |  0.414 ns |    37.53 ns |     ? |       ? | 0.0181 |     152 B |           ? |
| GetItemsWithRejection        | Vulnerable | 14     | 0                       |   184.69 ns |  1.629 ns |  1.524 ns |   184.28 ns |     ? |       ? | 0.0134 |     112 B |           ? |
| SpecialLoop                  | Vulnerable | 14     | 0                       |   148.74 ns |  0.908 ns |  0.709 ns |   149.01 ns |     ? |       ? | 0.0134 |     112 B |           ? |
| StackAlloc                   | Vulnerable | 14     | 0                       |   145.10 ns |  0.831 ns |  0.694 ns |   145.13 ns |     ? |       ? | 0.0067 |      56 B |           ? |
|                              |            |        |                         |             |           |           |             |       |         |        |           |             |
| **StringBuilder**                | **Vulnerable** | **14**     | **1**                       |   **100.21 ns** |  **1.307 ns** |  **1.159 ns** |   **100.23 ns** |     **?** |       **?** | **0.0191** |     **160 B** |           **?** |
| CharArray                    | Vulnerable | 14     | 1                       |    66.53 ns |  0.279 ns |  0.218 ns |    66.54 ns |     ? |       ? | 0.0134 |     112 B |           ? |
| Buffer                       | Vulnerable | 14     | 1                       |    35.07 ns |  0.408 ns |  0.341 ns |    35.09 ns |     ? |       ? | 0.0181 |     152 B |           ? |
| GetItems                     | Vulnerable | 14     | 1                       |   124.14 ns |  1.444 ns |  1.280 ns |   123.71 ns |     ? |       ? | 0.0134 |     112 B |           ? |
| RejectionSample              | Vulnerable | 14     | 1                       |    36.73 ns |  0.534 ns |  0.446 ns |    36.82 ns |     ? |       ? | 0.0181 |     152 B |           ? |
| GetItemsWithRejection        | Vulnerable | 14     | 1                       |   206.37 ns |  0.633 ns |  0.494 ns |   206.34 ns |     ? |       ? | 0.0138 |     117 B |           ? |
| SpecialLoop                  | Vulnerable | 14     | 1                       |   149.17 ns |  2.276 ns |  2.129 ns |   148.53 ns |     ? |       ? | 0.0134 |     112 B |           ? |
| StackAlloc                   | Vulnerable | 14     | 1                       |   145.35 ns |  0.719 ns |  0.600 ns |   145.47 ns |     ? |       ? | 0.0067 |      56 B |           ? |
|                              |            |        |                         |             |           |           |             |       |         |        |           |             |
| **StringBuilder**                | **Vulnerable** | **14**     | **2**                       |   **108.28 ns** |  **3.840 ns** | **11.322 ns** |   **113.80 ns** |     **?** |       **?** | **0.0191** |     **160 B** |           **?** |
| CharArray                    | Vulnerable | 14     | 2                       |    67.02 ns |  0.284 ns |  0.222 ns |    67.00 ns |     ? |       ? | 0.0134 |     112 B |           ? |
| Buffer                       | Vulnerable | 14     | 2                       |    38.87 ns |  0.425 ns |  0.355 ns |    38.78 ns |     ? |       ? | 0.0181 |     152 B |           ? |
| GetItems                     | Vulnerable | 14     | 2                       |   124.88 ns |  0.858 ns |  0.717 ns |   125.03 ns |     ? |       ? | 0.0134 |     112 B |           ? |
| RejectionSample              | Vulnerable | 14     | 2                       |    48.20 ns |  0.666 ns |  0.590 ns |    48.02 ns |     ? |       ? | 0.0181 |     152 B |           ? |
| GetItemsWithRejection        | Vulnerable | 14     | 2                       |   268.35 ns |  1.793 ns |  1.590 ns |   268.00 ns |     ? |       ? | 0.0162 |     137 B |           ? |
| SpecialLoop                  | Vulnerable | 14     | 2                       |   209.33 ns |  1.761 ns |  1.647 ns |   209.00 ns |     ? |       ? | 0.0134 |     112 B |           ? |
| StackAlloc                   | Vulnerable | 14     | 2                       |   205.04 ns |  1.408 ns |  1.317 ns |   204.45 ns |     ? |       ? | 0.0067 |      56 B |           ? |
|                              |            |        |                         |             |           |           |             |       |         |        |           |             |
| **StringBuilder**                | **Vulnerable** | **24**     | **0**                       |   **140.14 ns** |  **2.861 ns** |  **8.022 ns** |   **142.42 ns** |     **?** |       **?** | **0.0229** |     **192 B** |           **?** |
| CharArray                    | Vulnerable | 24     | 0                       |   107.90 ns |  0.898 ns |  0.840 ns |   107.54 ns |     ? |       ? | 0.0172 |     144 B |           ? |
| Buffer                       | Vulnerable | 24     | 0                       |    40.86 ns |  0.265 ns |  0.221 ns |    40.81 ns |     ? |       ? | 0.0229 |     192 B |           ? |
| GetItems                     | Vulnerable | 24     | 0                       |   189.76 ns |  1.358 ns |  1.204 ns |   189.70 ns |     ? |       ? | 0.0172 |     144 B |           ? |
| RejectionSample              | Vulnerable | 24     | 0                       |    44.86 ns |  0.281 ns |  0.235 ns |    44.80 ns |     ? |       ? | 0.0229 |     192 B |           ? |
| GetItemsWithRejection        | Vulnerable | 24     | 0                       |   286.75 ns |  2.619 ns |  2.322 ns |   286.46 ns |     ? |       ? | 0.0172 |     144 B |           ? |
| SpecialLoop                  | Vulnerable | 24     | 0                       |   202.88 ns |  1.616 ns |  1.349 ns |   202.67 ns |     ? |       ? | 0.0172 |     144 B |           ? |
| StackAlloc                   | Vulnerable | 24     | 0                       |   200.00 ns |  1.594 ns |  1.413 ns |   199.66 ns |     ? |       ? | 0.0086 |      72 B |           ? |
|                              |            |        |                         |             |           |           |             |       |         |        |           |             |
| **StringBuilder**                | **Vulnerable** | **24**     | **1**                       |   **195.29 ns** |  **3.455 ns** |  **3.232 ns** |   **195.30 ns** |     **?** |       **?** | **0.0229** |     **192 B** |           **?** |
| CharArray                    | Vulnerable | 24     | 1                       |   108.35 ns |  0.594 ns |  0.496 ns |   108.21 ns |     ? |       ? | 0.0172 |     144 B |           ? |
| Buffer                       | Vulnerable | 24     | 1                       |    37.21 ns |  0.189 ns |  0.147 ns |    37.21 ns |     ? |       ? | 0.0229 |     192 B |           ? |
| GetItems                     | Vulnerable | 24     | 1                       |   186.75 ns |  1.421 ns |  1.329 ns |   185.99 ns |     ? |       ? | 0.0172 |     144 B |           ? |
| RejectionSample              | Vulnerable | 24     | 1                       |    45.08 ns |  0.705 ns |  0.659 ns |    44.87 ns |     ? |       ? | 0.0229 |     192 B |           ? |
| GetItemsWithRejection        | Vulnerable | 24     | 1                       |   302.02 ns |  1.534 ns |  1.281 ns |   302.21 ns |     ? |       ? | 0.0172 |     145 B |           ? |
| SpecialLoop                  | Vulnerable | 24     | 1                       |   203.26 ns |  1.391 ns |  1.162 ns |   203.63 ns |     ? |       ? | 0.0172 |     144 B |           ? |
| StackAlloc                   | Vulnerable | 24     | 1                       |   200.42 ns |  1.133 ns |  1.004 ns |   199.96 ns |     ? |       ? | 0.0086 |      72 B |           ? |
|                              |            |        |                         |             |           |           |             |       |         |        |           |             |
| **StringBuilder**                | **Vulnerable** | **24**     | **2**                       |   **190.69 ns** |  **3.839 ns** |  **5.628 ns** |   **191.55 ns** |     **?** |       **?** | **0.0229** |     **192 B** |           **?** |
| CharArray                    | Vulnerable | 24     | 2                       |   111.76 ns |  1.222 ns |  1.083 ns |   111.49 ns |     ? |       ? | 0.0172 |     144 B |           ? |
| Buffer                       | Vulnerable | 24     | 2                       |    37.13 ns |  0.167 ns |  0.139 ns |    37.12 ns |     ? |       ? | 0.0229 |     192 B |           ? |
| GetItems                     | Vulnerable | 24     | 2                       |   187.46 ns |  2.519 ns |  2.356 ns |   186.15 ns |     ? |       ? | 0.0172 |     144 B |           ? |
| RejectionSample              | Vulnerable | 24     | 2                       |    56.36 ns |  0.772 ns |  0.722 ns |    55.91 ns |     ? |       ? | 0.0229 |     192 B |           ? |
| GetItemsWithRejection        | Vulnerable | 24     | 2                       |   320.71 ns |  2.698 ns |  2.524 ns |   320.62 ns |     ? |       ? | 0.0176 |     150 B |           ? |
| SpecialLoop                  | Vulnerable | 24     | 2                       |   234.60 ns |  1.279 ns |  1.134 ns |   234.32 ns |     ? |       ? | 0.0172 |     144 B |           ? |
| StackAlloc                   | Vulnerable | 24     | 2                       |   230.01 ns |  2.024 ns |  1.893 ns |   229.09 ns |     ? |       ? | 0.0086 |      72 B |           ? |
|                              |            |        |                         |             |           |           |             |       |         |        |           |             |
| **StringBuilder**                | **Vulnerable** | **32**     | **0**                       |   **211.11 ns** |  **3.472 ns** |  **3.247 ns** |   **210.95 ns** |     **?** |       **?** | **0.0267** |     **224 B** |           **?** |
| CharArray                    | Vulnerable | 32     | 0                       |   144.00 ns |  1.119 ns |  0.934 ns |   143.89 ns |     ? |       ? | 0.0210 |     176 B |           ? |
| Buffer                       | Vulnerable | 32     | 0                       |    46.17 ns |  0.258 ns |  0.202 ns |    46.14 ns |     ? |       ? | 0.0277 |     232 B |           ? |
| GetItems                     | Vulnerable | 32     | 0                       |   244.44 ns |  2.530 ns |  2.366 ns |   244.46 ns |     ? |       ? | 0.0210 |     176 B |           ? |
| RejectionSample              | Vulnerable | 32     | 0                       |    53.89 ns |  0.578 ns |  0.512 ns |    53.71 ns |     ? |       ? | 0.0277 |     232 B |           ? |
| GetItemsWithRejection        | Vulnerable | 32     | 0                       |   370.80 ns |  2.476 ns |  2.316 ns |   370.08 ns |     ? |       ? | 0.0210 |     176 B |           ? |
| SpecialLoop                  | Vulnerable | 32     | 0                       |   256.39 ns |  1.353 ns |  1.056 ns |   256.77 ns |     ? |       ? | 0.0210 |     176 B |           ? |
| StackAlloc                   | Vulnerable | 32     | 0                       |   253.48 ns |  1.874 ns |  1.753 ns |   253.80 ns |     ? |       ? | 0.0105 |      88 B |           ? |
|                              |            |        |                         |             |           |           |             |       |         |        |           |             |
| **StringBuilder**                | **Vulnerable** | **32**     | **1**                       |   **256.33 ns** |  **3.118 ns** |  **2.764 ns** |   **256.21 ns** |     **?** |       **?** | **0.0267** |     **224 B** |           **?** |
| CharArray                    | Vulnerable | 32     | 1                       |   140.86 ns |  0.484 ns |  0.404 ns |   140.96 ns |     ? |       ? | 0.0210 |     176 B |           ? |
| Buffer                       | Vulnerable | 32     | 1                       |    44.40 ns |  0.480 ns |  0.449 ns |    44.24 ns |     ? |       ? | 0.0277 |     232 B |           ? |
| GetItems                     | Vulnerable | 32     | 1                       |   240.46 ns |  1.365 ns |  1.140 ns |   240.36 ns |     ? |       ? | 0.0210 |     176 B |           ? |
| RejectionSample              | Vulnerable | 32     | 1                       |    53.50 ns |  0.721 ns |  0.602 ns |    53.39 ns |     ? |       ? | 0.0277 |     232 B |           ? |
| GetItemsWithRejection        | Vulnerable | 32     | 1                       |   369.59 ns |  2.814 ns |  2.632 ns |   368.95 ns |     ? |       ? | 0.0210 |     176 B |           ? |
| SpecialLoop                  | Vulnerable | 32     | 1                       |   256.90 ns |  1.895 ns |  1.582 ns |   257.15 ns |     ? |       ? | 0.0210 |     176 B |           ? |
| StackAlloc                   | Vulnerable | 32     | 1                       |   253.26 ns |  1.736 ns |  1.539 ns |   253.48 ns |     ? |       ? | 0.0105 |      88 B |           ? |
|                              |            |        |                         |             |           |           |             |       |         |        |           |             |
| **StringBuilder**                | **Vulnerable** | **32**     | **2**                       |   **253.24 ns** |  **5.010 ns** |  **5.568 ns** |   **254.21 ns** |     **?** |       **?** | **0.0267** |     **224 B** |           **?** |
| CharArray                    | Vulnerable | 32     | 2                       |   144.44 ns |  1.747 ns |  1.634 ns |   144.03 ns |     ? |       ? | 0.0210 |     176 B |           ? |
| Buffer                       | Vulnerable | 32     | 2                       |    44.42 ns |  0.508 ns |  0.475 ns |    44.18 ns |     ? |       ? | 0.0277 |     232 B |           ? |
| GetItems                     | Vulnerable | 32     | 2                       |   241.91 ns |  1.967 ns |  1.744 ns |   241.59 ns |     ? |       ? | 0.0210 |     176 B |           ? |
| RejectionSample              | Vulnerable | 32     | 2                       |    65.01 ns |  0.655 ns |  0.547 ns |    65.10 ns |     ? |       ? | 0.0277 |     232 B |           ? |
| GetItemsWithRejection        | Vulnerable | 32     | 2                       |   385.69 ns |  2.443 ns |  2.166 ns |   385.52 ns |     ? |       ? | 0.0210 |     178 B |           ? |
| SpecialLoop                  | Vulnerable | 32     | 2                       |   276.94 ns |  2.463 ns |  2.184 ns |   276.36 ns |     ? |       ? | 0.0210 |     176 B |           ? |
| StackAlloc                   | Vulnerable | 32     | 2                       |   271.10 ns |  0.982 ns |  0.870 ns |   271.37 ns |     ? |       ? | 0.0105 |      88 B |           ? |


</details>]]></content><author><name>Richard Cocks</name></author><summary type="html"><![CDATA[Fixing co-pilot output, with micro-benchmarking tool BenchmarkDotNet]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://richardcocks.github.io/assets/img/7aStackAlloc0.png" /><media:content medium="image" url="https://richardcocks.github.io/assets/img/7aStackAlloc0.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>