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

简介

在之前的文章中,我们多次提到 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 它是值类型的,是一个结构体。代码如下所示,我省略了部分信息。

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)...}

可以看到它由 1 个 32 位 int,2 个 16 位的 short 和 8 个 8 位的 byte 组成,至于为什么需要这样组成,其实是一个标准化的东西,为了在生成和序列化时更快。

我们使用ObjectLayoutInspector可以打印出 Guid 的数据结构,数据结果如下图所示,和我们源码里面看到的一致:

914623c687af4fed282bf86da6ac8a1a.png

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

  • Guid 有 128 位,现在 CPU 都是 64 位或者 32 位,还存在提升空间

  • Guid 是结构体类型,结构体类型在内存中是连续存储,我们可以直接读取内存来访问整个结构体

SIMD 优化代码

根据我们前面文章中,Min 和 Max 方法在.NET7 被优化的经验,我们可以直接写下面这样的代码。

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool EqualsCore(in Guid left, in Guid right)
{// 检测硬件是否支持Vector128if (Vector128.IsHardwareAccelerated){// 支持Vector128就好办了,直接加载比较return Vector128.LoadUnsafe(ref Unsafe.As<Guid, byte>(ref Unsafe.AsRef(in left))) == Vector128.LoadUnsafe(ref Unsafe.As<Guid, byte>(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&& Unsafe.Add(ref rA, 1) == Unsafe.Add(ref rB, 1)&& Unsafe.Add(ref rA, 2) == Unsafe.Add(ref rB, 2)&& Unsafe.Add(ref rA, 3) == Unsafe.Add(ref rB, 3);
}

在上面的代码中,我们可以看到不仅提供了 Vector 加速的方案,还有不支持回退的场景。不过那段 Vector 代码是不是不太好理解?我们逐个部分来解析一下。我们首先看左右的部分,右边也是同样的意思Vector128.LoadUnsafe(ref Unsafe.As<Guid, byte>(ref Unsafe.AsRef(in left)))

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

  • ref Unsafe.As<Guid, byte>(...)Guid*指针转换为byte*指针

  • Vector128.LoadUnsafe(...) 由于 Guid 已经变为 Byte 指针,所以就能直接 LoadUnsafe 了

最后 right Guid 也使用相同的方式加载,最后使用==比较两个Vector是否相等就好了。其实==还使用了CompareEqualMoveMask两个指令,只是在.NET7 中 JIT 会把两个向量的比较给优化。看下方图片中红色框标记的部分,就是这两个指令。

5c08807d34276774207a0379e1043db6.png

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

static class GuidExtensions
{public static bool OptimizedGuidEquals(in Guid left, in Guid right){if (Sse2.IsSupported){Vector128<byte> leftVector = Unsafe.ReadUnaligned<Vector128<byte>>(ref Unsafe.As<Guid, byte>(ref Unsafe.AsRef(in left)));Vector128<byte> rightVector = Unsafe.ReadUnaligned<Vector128<byte>>(ref Unsafe.As<Guid, byte>(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 & 0xFFFF) == 0xFFFF;}return left == right;}
}

从下图的汇编代码中,可以看到是一样的效果:2831d55beb1ea1a7033bc6b7a8d031cf.png

总结

最终这一波操作下来,我们可以看到Guid.Equals的性能提升了 30%。如果你的程序中使用 Guid 作为数据库、对象主键的,只需要升级.NET7 或者用上面的GuidExtensions就能获得这样的性能提升。b9adb5f45517840ab37b1379bd9f88a9.png

参考资料

[1]

Meziantou: https://www.meziantou.net/faster-guid-comparisons-using-vectors-simd-in-dotnet.htm

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/281464.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

go语言载入json的一个坑

问题简介 go语言标准库汇总内置了对 json 文件的处理&#xff0c;非常方便&#xff0c;最近在写一个应用的时候&#xff0c;需要从 json 文件中载入 配置&#xff0c;由于是 go 新手&#xff0c;忽略一个细节&#xff0c;导致载入内容始终为空 代码演示 代码是最好的说明载体 p…

el表达式 if 和 if else 的写法

javaScript的if else大家都不会陌生&#xff0c;但可能很多小伙伴并不知道在jsp文件里&#xff0c;el表达式的if else是怎么写的&#xff0c;下面安利给各位小伙伴。 el表达式 if 代码示例示例1<c:if test"${jsonObject.transportTypeName eq 火车}"><li>…

Effective_STL 学习笔记(二十七) 用 distance 和 advance 把 const_iterator 转化成 iterator...

并不存在从 const_iterator 到 iterator 之间的隐式转换 一种安全的、可移植的方法获取他所对应的 iterator&#xff1a; 1   typedef deque<int> IntDeque;     // 方便的typedef 2   typedef IntDeque::iterator Iter; 3   typedef IntDeque::const_iterato…

分布式唯一Id

支持.Net/.Net Core/.Net Framework&#xff0c;可以部署在Docker, Windows, Linux, Mac。分布式唯一Id&#xff0c;顾名思义&#xff0c;是指在全世界任何一台计算机上都不会重复的唯一Id。在单机/单服务器/单数据库的小型应用中&#xff0c;不需要用到这类东西。但在高并发、…

缺氧游戏黑科技计算机,《缺氧》游戏内参数修改图文详解

很多玩家都很喜欢缺氧这款游戏&#xff0c;有时候因为一些不可告人的秘密我们需要修改游戏中的内容来达到简化我们的生存难度&#xff0c;这样就需要修改游戏的脚本&#xff0c;所幸《缺氧 》对于这个问题很宽容&#xff0c;完全没有加密地图的生成脚本&#xff0c;让我们可以完…

Python 项目实践三(Web应用程序)第四篇

接着上节继续学习&#xff0c;本章将建立用户账户 Web应用程序的核心是让任何用户都能够注册账户并能够使用它&#xff0c;不管用户身处何方。在本章中&#xff0c;你将创建一些表单&#xff0c;让用户能够添加主题和条目&#xff0c;以及编辑既有的条目。你还将学习Django如何…

新手想买二手车 先看看买车后这五个步骤吧

买二手车你该知道 很多人因为资金短缺又或者是想要一辆便宜车“练手”而选择去买一辆价格低廉&#xff0c;有着一定车龄的二手车。很多人看中二手车正正是因为便宜&#xff0c;以为是购买以后基本不需要再投入新的花费&#xff0c;殊不知这是非常错误的想法&#xff0c;因为以下…

yii---where or该如何使用

今天调试YII项目的时候&#xff0c;遇到一个奇葩的事儿&#xff0c;在调试 where or 查询的时候&#xff1a;调试语句是这样&#xff1a; $str static::find()->where([or,username > $username,mobile > $account]); echo "<br>"; echo $st…

十六进制编辑器--ImHex

十六进制编辑器是用于编辑单个字节数据的软件应用程序&#xff0c;主要由程序员或系统管理员使用。常规文本编辑器和十六进制编辑器之间的区别在于常规编辑器表示文件的逻辑内容&#xff0c;而十六进制编辑器表示文件的物理内容。十六进制编辑器可以让你以十六进制的形式查看或…

计算机第一课 教案 纪律,信息技术开学第一课-纪律

《信息技术开学第一课-纪律》由会员分享&#xff0c;可在线阅读&#xff0c;更多相关《信息技术开学第一课-纪律(3页珍藏版)》请在人人文库网上搜索。1、信息技术开学第一课教学目标&#xff1a;1、 了解学生的基础情况&#xff0c;选出课代表2、 分好学习小组&#xff0c;选出…

奥迪坚SVRM(Screen-Voice Recording Manager)录屏软件正式发布

奥迪坚SVRM(Screen-Voice Recording Manager)能够对座席通话同步录音的同时进行座席操作录屏 实时监控座席屏幕操作&#xff0c;及时纠正操作问题。 座席质检可以边听边看&#xff0c;为KPI考核提供依据。 利用优秀座席操作记录对座席进行培训。 监控坐席人员对敏感信息访问次数…

小米:开源不仅要站在巨人的肩膀上,还要为巨人指方向

今天上午&#xff0c;第一届小米开源技术峰会在北京举行&#xff0c;会上&#xff0c;小米人工智能与云平台副总裁崔宝秋致开场词&#xff0c;并发表了《小米开源之路》的演讲。 崔宝秋强调小米一直在推动开源&#xff0c;也是开源的倡导者。他告诉我们雷军创立小米的其中一个重…

容器基本操作

docker run --namefang -it image_id /bin/bash 执行的shell docker ps -a 查看所有容器&#xff0c; -l 查看最近运行容器 docker inspect 查看容器的详细信息 docker start [-i] container_id 或者名字也可以 docker rm 删除容器docker ps 查看正在运行的容器do…

计算机及相关设备制造业2020,在现实生活中,接近完全垄断市场类型的行业包括()。A.计算机及相关设备制造业B.稀有...

在现实生活中&#xff0c;接近完全垄断市场类型的行业包括()。A&#xff0e;计算机及相关设备制造业B&#xff0e;稀有更多相关问题【简答题】请解释以下名词: 制动、缓解、实施制动作用、实施缓解作用、制动距离。【简答题】请解释以下名词: 制动、缓解、实施制动作用、实施缓…

CAS原理

像synchronized这种独占锁属于悲观锁&#xff0c;它是在假设一定会发生冲突的&#xff0c;那么加锁恰好有用&#xff0c;除此之外&#xff0c;还有乐观锁&#xff0c;乐观锁的含义就是假设没有发生冲突&#xff0c;那么我正好可以进行某项操作&#xff0c;如果要是发生冲突呢&a…

数据分析师的职业规划之路

“数据分析师作为一个出现时间不长的工种&#xff0c;大数据时代下&#xff0c;成为螺丝钉还是成为龙头&#xff0c;需要尝试新的可能。” 数据分析师手中拥有一座宝藏。作为滴滴出行数据分析团队的负责人&#xff0c;刘普成发现了数据分析师通往卓越的秘诀&#xff1a;视野。数…

《设计模式》3.结构型模式

点击进入我的博客 3.1 适配器模式 适配器模式把一个类的接口变换成客户端所期待的另一种接口&#xff0c;使得原本因接口不匹配而无法在一起工作的两个类能够在一起工作。 3.1.1 类的适配器结构 目标&#xff08;Target&#xff09;角色&#xff1a;这就是所期待得到的接口&…

最快的计算机操作,世界十大最快的超级计算机

最近&#xff0c;《联邦储备技术》杂志对全球超级计算机进行了排名&#xff0c;并从中选出了十个最快的超级计算机. 其中&#xff0c;中国有两台超级计算机进入了榜单&#xff0c;而“天河2号”则依靠双精度浮点算术峰. 速度达到了每秒5490亿次&#xff0c;占据了王位.这也是两…

苹果iOS 10.3.1修复博通Wi-Fi芯片重大安全漏洞

如果你还没有将设备升级到 iOS 10.3.1 的话&#xff0c;那么现在是个机会了。因为不久前发布的 iOS 10.3.1&#xff0c;修复了 iPhone 中博通 Wi-Fi 芯片的一个重大安全漏洞&#xff0c;该安全漏洞可能会使在 Wi-Fi 范围内的攻击者在智能手机上注入并运行代码。 Google Project…

计算机开机跳过硬盘检查,如何设置开机跳过检测硬盘

如何设置开机跳过检测硬盘导读&#xff1a;很多win用户都想设置开机跳过检测硬盘&#xff0c;能够更加快速的进入系统&#xff0c;今天百分网小编给大家整理出了方法。1、系统的问题系统解决在Windows界面下&#xff0c;通过“磁盘碎片整理”程序来完成。首先启动“CMD”键入“…