.NET7如何優(yōu)化Guid.Equals性能
簡介
在之前的文章中,我們多次提到 Vector - SIMD 技術(shù),也答應(yīng)大家在后面分享更多.NET7 中優(yōu)化的例子,今天就帶來一個使用 SIMD 優(yōu)化Guid.Equals()方法性能的例子。
為什么 Guid 能使用 SIMD 優(yōu)化?
首先就需要介紹一些背景知識,那就是Guid它是什么,在我們?nèi)祟愌壑校?code>Guid就是一串字符串,如下方所示的那樣。
"D313CD46-2724-7359-84A0-9E73C861CCD2"
而在定義中,全局唯一標(biāo)識符(GUID,Globally Unique Identifier)是一種由算法生成的二進(jìn)制長度為128 位的數(shù)字標(biāo)識符。GUID 主要用于在擁有多個節(jié)點(diǎn)、多臺計算機(jī)的網(wǎng)絡(luò)或系統(tǒng)中。在理想情況下,任何計算機(jī)和計算機(jī)集群都不會生成兩個相同的 GUID。GUID 的總數(shù)達(dá)到了 2^128(3.4×10^38)個,所以隨機(jī)生成兩個相同 GUID 的可能性非常小,但并不為 0。GUID 一詞有時也專指微軟對 UUID 標(biāo)準(zhǔn)的實(shí)現(xiàn)。
大家可以看到我著重標(biāo)記了它的位數(shù)是128 位,128 位意味著什么?就是如果比較兩個 Guid 是否相等的話,不管是 64 位 CPU 還是 32 位的 CPU 需要多條指令比較多次。如果我們用上了 Vector?是不是會有更好的性能呢?
首先我們來看看 Guid 是如何定義的,看看能不能直接讀取 128 位數(shù)據(jù),從而用上 Vector。Guid 它是值類型的,是一個結(jié)構(gòu)體。代碼如下所示,我省略了部分信息。
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 組成,至于為什么需要這樣組成,其實(shí)是一個標(biāo)準(zhǔn)化的東西,為了在生成和序列化時更快。
我們使用ObjectLayoutInspector可以打印出 Guid 的數(shù)據(jù)結(jié)構(gòu),數(shù)據(jù)結(jié)果如下圖所示,和我們源碼里面看到的一致:

那么 Guid 是否能使用 SIMD 優(yōu)化的結(jié)論顯而易見:
- Guid 有 128 位,現(xiàn)在 CPU 都是 64 位或者 32 位,還存在提升空間
- Guid 是結(jié)構(gòu)體類型,結(jié)構(gòu)體類型在內(nèi)存中是連續(xù)存儲,我們可以直接讀取內(nèi)存來訪問整個結(jié)構(gòu)體
SIMD 優(yōu)化代碼
根據(jù)我們前面文章中,Min 和 Max 方法在.NET7 被優(yōu)化的經(jīng)驗,我們可以直接寫下面這樣的代碼。
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool EqualsCore(in Guid left, in Guid right)
{
// 檢測硬件是否支持Vector128
if (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頭部讀取內(nèi)存
// 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 它的首地址指針,此時返回的其實(shí)是Guid*ref Unsafe.As<Guid, byte>(...)將Guid*指針轉(zhuǎn)換為byte*指針Vector128.LoadUnsafe(...)由于 Guid 已經(jīng)變?yōu)?Byte 指針,所以就能直接 LoadUnsafe 了
最后 right Guid 也使用相同的方式加載,最后使用==比較兩個Vector是否相等就好了。其實(shí)==還使用了CompareEqual和MoveMask兩個指令,只是在.NET7 中 JIT 會把兩個向量的比較給優(yōu)化。看下方圖片中紅色框標(biāo)記的部分,就是這兩個指令。

那么.NET6 下==沒有優(yōu)化,那該怎么辦呢?根據(jù)這里的匯編指令,Meziantou[1]大佬給出了.NET6 下同樣功效的優(yōu)化代碼:
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的結(jié)果是128位的,我們可以通過Sse2.MoveMask()來重新排列成16位,最終看是否等于0xffff就好
var equals = Sse2.CompareEqual(leftVector, rightVector);
var result = Sse2.MoveMask(equals);
return (result & 0xFFFF) == 0xFFFF;
}
return left == right;
}
}從下圖的匯編代碼中,可以看到是一樣的效果:

總結(jié)
最終這一波操作下來,我們可以看到Guid.Equals的性能提升了 30%。如果你的程序中使用 Guid 作為數(shù)據(jù)庫、對象主鍵的,只需要升級.NET7 或者用上面的GuidExtensions就能獲得這樣的性能提升。

參考資料
Meziantou: https://www.meziantou.net/faster-guid-comparisons-using-vectors-simd-in-dotnet.htm
到此這篇關(guān)于.NET7是如何優(yōu)化Guid.Equals性能的?的文章就介紹到這了,更多相關(guān).net優(yōu)化guid.equals性能內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Microsoft Visual Studio 2010下如何添加命令提示行
這篇文章主要介紹了Microsoft Visual Studio 2010下如何添加命令提示行的相關(guān)資料,需要的朋友可以參考下2015-11-11
關(guān)于Asp.net頁面Page_Load被執(zhí)行兩次的問題分享
這篇文章介紹了關(guān)于Asp.net頁面Page_Load被執(zhí)行兩次的問題,有需要的朋友可以參考一下2013-09-09
.NET Framework 4.8 多線程編程最佳實(shí)踐
本文全面介紹.NET Framework 4.8多線程編程,涵蓋Task創(chuàng)建、Async/Await異步模式、并行處理、數(shù)據(jù)流TPLDataflow,同步機(jī)制及性能優(yōu)化,指導(dǎo)開發(fā)者構(gòu)建高效可靠的應(yīng)用,感興趣的朋友跟隨小編一起看看吧2025-09-09
在?Net7.0?環(huán)境下如何使用?RestSharp?發(fā)送?Http(FromBody和FromForm)請求
這篇文章主要介紹了在?Net7.0?環(huán)境下使用?RestSharp?發(fā)送?Http(FromBody和FromForm)請求,今天,我就兩個小的知識點(diǎn),就是通過使用?RestSharp?訪問?WebAPI,提交?FromBody?和?FromForm?兩種方式的數(shù)據(jù),還是有些區(qū)別的,本文結(jié)合實(shí)例代碼介紹的非常詳細(xì),需要的朋友參考下吧2023-09-09
asp.net實(shí)現(xiàn)生成縮略圖及給原始圖加水印的方法示例
這篇文章主要介紹了asp.net實(shí)現(xiàn)生成縮略圖及給原始圖加水印的方法,結(jié)合具體實(shí)例形式分析了asp.net圖片的縮略圖與水印操作相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-10-10
C#利用服務(wù)器實(shí)現(xiàn)客戶端之間通信
這篇文章主要為大家詳細(xì)介紹了C#利用服務(wù)器實(shí)現(xiàn)客戶端之間通信,感興趣的小伙伴們可以參考一下2016-08-08
Visual Studio 2017通過SSH調(diào)試Linux上.NET Core
這篇文章主要為大家詳細(xì)介紹了Visual Studio 2017通過SSH調(diào)試Linux 上.NET Core的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-03-03
aspx文件格式使用URLRewriter實(shí)現(xiàn)靜態(tài)化變成html
如何隱藏aspx文件格式,變成html,使用asp.net 開發(fā)的網(wǎng)頁程序,使用URLRewriter.dll 實(shí)現(xiàn)靜態(tài)化,接下來將介紹下具體操作步驟,感興趣的朋友可以參考下2013-04-04
ASP.NET MVC 4 中的JSON數(shù)據(jù)交互的方法
本篇文章主要介紹了ASP.NET MVC 4 中的JSON數(shù)據(jù)交互的方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-04-04

