.NET7是如何优化Guid.Equals性能的?

2个月前 (11-17 23:52)阅读2回复0
niannian
niannian
  • 总版主
  • 注册排名10
  • 经验值80195
  • 级别网站编辑
  • 主题16039
  • 回复0
楼主

简介

在之前的文章中,我们屡次提到 Vector - SIMD 手艺,也容许各人在后面分享更多.NET7 中优化的例子,今天就带来一个利用 SIMD 优化 Guid.Equals 办法性能的例子。

为什么 Guid 能利用 SIMD 优化?

起首就需要介绍一些布景常识,那就是 Guid 它是什么,在我们人类眼中, Guid 就是一串字符串,如下方所示的那样。

"D313CD46-2724-7359-84A0-9E73C861CCD2"

而在定义中,全局独一标识符(GUID,Globally Unique Identifier)是一种由算法生成的二进造长度为 128 位的数字标识符。GUID 次要用于在拥有多个节点、多台计算机的收集或系统中。在抱负情状下,任何计算机和计算机集群都不会生成两个不异的 GUID。GUID 的总数到达了 2^128(3.4×10^38)个,所以随机生成两个不异 GUID 的可能性十分小,但其实不为 0。GUID 一词有时也专指微软对 UUID 原则的实现。

各人能够看到我着重标识表记标帜了它的位数是 128 位,128 位意味着什么?就是若是比力两个 Guid 能否相等的话,不论是 64 位 CPU 仍是 32 位的 CPU 需要多条指令比力屡次。若是我们用上了 Vector?是不是会有更好的性能呢?

起首我们来看看 Guid 是若何定义的,看看能不克不及间接读取 128 位数据,从而用上 Vector。Guid 它是值类型的,是一个构造体。代码如下所示,我省略了部门信息。

publicreadonlypartialstructGuid

privatereadonlyint_a; // Do not rename (binary serialization)

privatereadonlyshort_b; // Do not rename (binary serialization)

privatereadonlyshort_c; // Do not rename (binary serialization)

privatereadonlybyte_d; // Do not rename (binary serialization)

privatereadonlybyte_e; // Do not rename (binary serialization)

privatereadonlybyte_f; // Do not rename (binary serialization)

展开全文

privatereadonlybyte_g; // Do not rename (binary serialization)

privatereadonlybyte_h; // Do not rename (binary serialization)

privatereadonlybyte_i; // Do not rename (binary serialization)

privatereadonlybyte_j; // Do not rename (binary serialization)

privatereadonlybyte_k; // Do not rename (binary serialization)

能够看到它由 1 个 32 位 int,2 个 16 位的 short 和 8 个 8 位的 byte 构成,至于为什么需要如许构成,其实是一个原则化的工具,为了在生成和序列化时更快。

我们利用 ObjectLayoutInspector 能够打印出 Guid 的数据构造,数据成果如下图所示,和我们源码里面看到的一致:

那么 Guid 能否能利用 SIMD 优化的结论显而易见:

Guid 有 128 位,如今 CPU 都是 64 位或者 32 位,还存在提拔空间

Guid 是构造体类型,构造体类型在内存中是持续存储,我们能够间接读取内存来拜候整个构造体

Guid 有 128 位,如今 CPU 都是 64 位或者 32 位,还存在提拔空间

Guid 是构造体类型,构造体类型在内存中是持续存储,我们能够间接读取内存来拜候整个构造体

根据我们前面文章中,Min 和 Max 办法在.NET7 被优化的经历,我们能够间接写下面如许的代码。

[ MethodImpl(MethodImplOptions.AggressiveInlining)]

privatestaticboolEqualsCore( inGuid left, inGuid right )

// 检测硬件能否撑持Vector128

if(Vector128.IsHardwareAccelerated)

// 撑持Vector128就好办了,间接加载比力

returnVector128.LoadUnsafe( refUnsafe.AsGuid, byte( refUnsafe.AsRef( inleft))) == Vector128.LoadUnsafe( refUnsafe.AsGuid, byte( refUnsafe.AsRef( inright)));

// 若是不撑持,那么从Guid头部读取内存

// 32位比力四次

refintrA = refUnsafe.AsRef( inleft._a);

refintrB = refUnsafe.AsRef( inright._a);

returnrA == rB

Unsafe.Add( refrA, 1) == Unsafe.Add( refrB, 1)

Unsafe.Add( refrA, 2) == Unsafe.Add( refrB, 2)

Unsafe.Add( refrA, 3) == Unsafe.Add( refrB, 3);

在上面的代码中,我们能够看到不只供给了 Vector 加速的计划,还有不撑持回退的场景。不外那段 Vector 代码是不是不太好理解?我们逐个部门来解析一下。我们起首看摆布的部门,右边也是同样的意思 Vector128.LoadUnsafe(ref Unsafe.AsGuid, byte(ref Unsafe.AsRef(in left))) 。

ref Unsafe.AsRef(in left) 是获取 left Guid 它的首地址指针,此时返回的其实是 Guid*

ref Unsafe.AsGuid, byte(...) 将 Guid* 指针转换为 byte* 指针

Vector128.LoadUnsafe(...) 因为 Guid 已经变成 Byte 指针,所以就能间接 LoadUnsafe 了

ref Unsafe.AsRef(in left) 是获取 left Guid 它的首地址指针,此时返回的其实是 Guid*

ref Unsafe.AsGuid, byte(...) 将 Guid* 指针转换为 byte* 指针

Vector128.LoadUnsafe(...) 因为 Guid 已经变成 Byte 指针,所以就能间接 LoadUnsafe 了

最初 right Guid 也利用不异的体例加载,最初利用 == 比力两个 Vector 能否相等就好了。其实 == 还利用了 CompareEqual 和 MoveMask 两个指令,只是在.NET7 中 JIT 会把两个向量的比力给优化。看下方图片中红色框标识表记标帜的部门,就是那两个指令。

那么.NET6 下 == 没有优化,那该怎么办呢?根据那里的汇编指令, Meziantou[1] 大佬给出了.NET6 下同样成效的优化代码:

staticclassGuidExtensions

publicstaticboolOptimizedGuidEquals( inGuid left, inGuid right )

if(Sse2.IsSupported)

Vector128 byte leftVector = Unsafe.ReadUnalignedVector128 byte(

refUnsafe.AsGuid, byte(

refUnsafe.AsRef( inleft)));

Vector128 byte rightVector = Unsafe.ReadUnalignedVector128 byte(

refUnsafe.AsGuid, byte(

refUnsafe.AsRef( inright)));

// 利用Sse2.CompareEqual比力能否相等,它的返回值是一个128位向量,若是相等,该位置返回0xffff,不然返回0x0

// CompareEqual的成果是128位的,我们能够通过Sse2.MoveMask来从头摆列成16位,最末看能否等于0xffff就好

varequals= Sse2.CompareEqual(leftVector, rightVector);

varresult = Sse2.MoveMask( equals);

return(result 0xFFFF) == 0xFFFF;

returnleft == right;

从下图的汇编代码中,能够看到是一样的效果:

总结

最末那一波操做下来,我们能够看到 Guid.Equals 的性能提拔了 30%。若是你的法式中利用 Guid 做为数据库、对象主键的,只需要晋级.NET7 或者用上面的 GuidExtensions 就能获得如许的性能提拔。

参考材料

[1]

Meziantou:

0
回帖

.NET7是如何优化Guid.Equals性能的? 期待您的回复!

取消
载入表情清单……
载入颜色清单……
插入网络图片

取消确定

图片上传中
编辑器信息
提示信息