甜品果子狸 發表於 2025-11-13 08:54:32

.NET7如何优化Guid.Equals性能

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">简介</a></li><li><a href="#_label1">为什么 Guid 能使用 SIMD 优化?</a></li><li><a href="#_label2">SIMD 优化代码</a></li><li><a href="#_label3">总结</a></li><ul class="second_class_ul"><li><a href="#_lab2_3_0">参考资料</a></li></ul></ul></div><p class="maodian"><a name="_label0"></a></p><h2>简介</h2>
<p>在之前的文章中,我们多次提到 Vector - SIMD 技术,也答应大家在后面分享更多.NET7 中优化的例子,今天就带来一个使用 SIMD 优化<code>Guid.Equals()</code>方法性能的例子。</p>
<p class="maodian"><a name="_label1"></a></p><h2>为什么 Guid 能使用 SIMD 优化?</h2>
<p>首先就需要介绍一些背景知识,那就是<code>Guid</code>它是什么,在我们人类眼中,<code>Guid</code>就是一串字符串,如下方所示的那样。</p>
<p><code>&quot;D313CD46-2724-7359-84A0-9E73C861CCD2&quot;</code></p>
<p>而在定义中,全局唯一标识符(GUID,Globally Unique Identifier)是一种由算法生成的二进制长度为<strong>128 位</strong>的数字标识符。GUID 主要用于在拥有多个节点、多台计算机的网络或系统中。在理想情况下,任何计算机和计算机集群都不会生成两个相同的 GUID。GUID 的总数达到了 2^128(3.4&times;10^38)个,所以随机生成两个相同 GUID 的可能性非常小,但并不为 0。GUID 一词有时也专指微软对 UUID 标准的实现。</p>
<p>大家可以看到我着重标记了它的位数是<strong>128 位</strong>,128 位意味着什么?就是如果比较两个 Guid 是否相等的话,不管是 64 位 CPU 还是 32 位的 CPU 需要多条指令比较多次。如果我们用上了 Vector?是不是会有更好的性能呢?</p>
<p>首先我们来看看 Guid 是如何定义的,看看能不能直接读取 128 位数据,从而用上 Vector。Guid 它是值类型的,是一个结构体。代码如下所示,我省略了部分信息。</p>
<div class="jb51code"><pre class="brush:csharp;">public readonly partial struct Guid
    {
      ...
      private readonly int _a;   // Do not rename (binary serialization)
      private readonly short _b; // Do not rename (binary serialization)
      private readonly short _c; // Do not rename (binary serialization)
      private readonly byte _d;// Do not rename (binary serialization)
      private readonly byte _e;// Do not rename (binary serialization)
      private readonly byte _f;// Do not rename (binary serialization)
      private readonly byte _g;// Do not rename (binary serialization)
      private readonly byte _h;// Do not rename (binary serialization)
      private readonly byte _i;// Do not rename (binary serialization)
      private readonly byte _j;// Do not rename (binary serialization)
      private readonly byte _k;// Do not rename (binary serialization)
      ...
    }</pre></div>
<p>可以看到它由 1 个 32 位 int,2 个 16 位的 short 和 8 个 8 位的 byte 组成,至于为什么需要这样组成,其实是一个标准化的东西,为了在生成和序列化时更快。</p>
<p>我们使用<code>ObjectLayoutInspector</code>可以打印出 Guid 的数据结构,数据结果如下图所示,和我们源码里面看到的一致:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202511/2025111309125184.png" /></p>
<p>那么 Guid 是否能使用 SIMD 优化的结论显而易见:</p>
<ul><li>Guid 有 128 位,现在 CPU 都是 64 位或者 32 位,还存在提升空间</li><li>Guid 是结构体类型,结构体类型在内存中是连续存储,我们可以直接读取内存来访问整个结构体</li></ul>
<p class="maodian"><a name="_label2"></a></p><h2>SIMD 优化代码</h2>
<p>根据我们前面文章中,Min 和 Max 方法在.NET7 被优化的经验,我们可以直接写下面这样的代码。</p>
<div class="jb51code"><pre class="brush:csharp;">
private static bool EqualsCore(in Guid left, in Guid right)
{
    // 检测硬件是否支持Vector128
    if (Vector128.IsHardwareAccelerated)
    {
      // 支持Vector128就好办了,直接加载比较
      return Vector128.LoadUnsafe(ref Unsafe.As&lt;Guid, byte&gt;(ref Unsafe.AsRef(in left))) == Vector128.LoadUnsafe(ref Unsafe.As&lt;Guid, byte&gt;(ref Unsafe.AsRef(in right)));
    }
    // 如果不支持,那么从Guid头部读取内存
    // 32位比较四次
    ref int rA = ref Unsafe.AsRef(in left._a);
    ref int rB = ref Unsafe.AsRef(in right._a);
    return rA == rB
      &amp;&amp; Unsafe.Add(ref rA, 1) == Unsafe.Add(ref rB, 1)
      &amp;&amp; Unsafe.Add(ref rA, 2) == Unsafe.Add(ref rB, 2)
      &amp;&amp; Unsafe.Add(ref rA, 3) == Unsafe.Add(ref rB, 3);
}</pre></div>
<p>在上面的代码中,我们可以看到不仅提供了 Vector 加速的方案,还有不支持回退的场景。不过那段 Vector 代码是不是不太好理解?我们逐个部分来解析一下。我们首先看左右的部分,右边也是同样的意思<code>Vector128.LoadUnsafe(ref Unsafe.As&lt;Guid, byte&gt;(ref Unsafe.AsRef(in left)))</code>。</p>
<ul><li><code>ref Unsafe.AsRef(in left)</code> 是获取 left Guid 它的首地址指针,此时返回的其实是<code>Guid*</code></li><li><code>ref Unsafe.As&lt;Guid, byte&gt;(...)</code> 将<code>Guid*</code>指针转换为<code>byte*</code>指针</li><li><code>Vector128.LoadUnsafe(...)</code> 由于 Guid 已经变为 Byte 指针,所以就能直接 LoadUnsafe 了</li></ul>
<p>最后 right Guid 也使用相同的方式加载,最后使用<code>==</code>比较两个<code>Vector</code>是否相等就好了。其实<code>==</code>还使用了<code>CompareEqual</code>和<code>MoveMask</code>两个指令,只是在.NET7 中 JIT 会把两个向量的比较给优化。看下方图片中红色框标记的部分,就是这两个指令。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202511/2025111309125146.png" /></p>
<p>那么.NET6 下<code>==</code>没有优化,那该怎么办呢?根据这里的汇编指令,Meziantou<sup></sup>大佬给出了.NET6 下同样功效的优化代码:</p>
<div class="jb51code"><pre class="brush:csharp;">static class GuidExtensions
{
    public static bool OptimizedGuidEquals(in Guid left, in Guid right)
    {
      if (Sse2.IsSupported)
      {
            Vector128&lt;byte&gt; leftVector = Unsafe.ReadUnaligned&lt;Vector128&lt;byte&gt;&gt;(
                ref Unsafe.As&lt;Guid, byte&gt;(
                  ref Unsafe.AsRef(in left)));
            Vector128&lt;byte&gt; rightVector = Unsafe.ReadUnaligned&lt;Vector128&lt;byte&gt;&gt;(
                ref Unsafe.As&lt;Guid, byte&gt;(
                  ref Unsafe.AsRef(in right)));
            // 使用Sse2.CompareEqual()比较是否相等,它的返回值是一个128位向量,如果相等,该位置返回0xffff,否则返回0x0
            // CompareEqual的结果是128位的,我们可以通过Sse2.MoveMask()来重新排列成16位,最终看是否等于0xffff就好
            var equals = Sse2.CompareEqual(leftVector, rightVector);
            var result = Sse2.MoveMask(equals);
            return (result &amp; 0xFFFF) == 0xFFFF;
      }
      return left == right;
    }
}</pre></div>
<p>从下图的汇编代码中,可以看到是一样的效果:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202511/2025111309125154.png" /></p>
<p class="maodian"><a name="_label3"></a></p><h2>总结</h2>
<p>最终这一波操作下来,我们可以看到<code>Guid.Equals</code>的性能提升了 30%。如果你的程序中使用 Guid 作为数据库、对象主键的,只需要升级.NET7 或者用上面的<code>GuidExtensions</code>就能获得这样的性能提升。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202511/2025111309125137.png" /></p>
<p class="maodian"><a name="_lab2_3_0"></a></p><h3>参考资料</h3>
<p>Meziantou: <a href="https://www.meziantou.net/faster-guid-comparisons-using-vectors-simd-in-dotnet.htm" target="_blank">https://www.meziantou.net/faster-guid-comparisons-using-vectors-simd-in-dotnet.htm</a></p>
頁: [1]
查看完整版本: .NET7如何优化Guid.Equals性能