欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

教你使用.NET快速比較兩個(gè)byte數(shù)組是否相等

 更新時(shí)間:2022年04月06日 12:41:38   作者:InCerry  
在.NET中如何快速的比較兩個(gè)byte數(shù)組是否完全相等,聽(tīng)起來(lái)是一個(gè)比較兩個(gè)byte數(shù)組是完全相等是一個(gè)簡(jiǎn)單的問(wèn)題,但是深入研究以后,覺(jué)得還是有很多方案的,這里和大家一起分享下

前言

之前在群里面有群友問(wèn)過(guò)一個(gè)這樣的問(wèn)題,在.NET中如何快速的比較兩個(gè)byte數(shù)組是否完全相等,聽(tīng)起來(lái)是一個(gè)比較兩個(gè)byte數(shù)組是完全相等是一個(gè)簡(jiǎn)單的問(wèn)題,但是深入研究以后,覺(jué)得還是有很多方案的,這里和大家一起分享下。

評(píng)測(cè)方案

這里為了評(píng)測(cè)不同方案的性能,我們用到了BenchmarkDotNet這個(gè)庫(kù),這個(gè)庫(kù)目前已經(jīng)被收入.NET基金會(huì)下,BenchmarkDotNet可以很方便的評(píng)測(cè)方法執(zhí)行的性能,支持幾乎所有的.NET運(yùn)行環(huán)境,并且能輸出詳細(xì)的報(bào)表。使用起來(lái)也非常簡(jiǎn)單,你只需要安裝BenchmakrDotNet的Nuget包,然后使用其提供的類(lèi)和方法即可,這里是它的項(xiàng)目地址和幫助文檔。

BenchmarkDotNet項(xiàng)目地址

BenchmarkDotNet幫助文檔

我們通過(guò)BenchmarkDotNet來(lái)構(gòu)建一個(gè)這樣的評(píng)測(cè)用例.

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using CompareByte;
// 需要引入BenchmarkDotNet的命名空間
// 運(yùn)行Benchmark相當(dāng)簡(jiǎn)單,只需要執(zhí)行這個(gè)靜態(tài)方法,泛型是需要評(píng)測(cè)的類(lèi)
var summary = BenchmarkRunner.Run<BenchmarkCompareMethod>();
// 我們需要一些評(píng)測(cè)內(nèi)存結(jié)果信息
// 并且生成HTML報(bào)表
[MemoryDiagnoser]
[HtmlExporter]
public class BenchmarkCompareMethod
{
    // 準(zhǔn)備兩個(gè)數(shù)組,填充4MB大小的數(shù)據(jù)
    private static readonly byte[] XBytes = Enumerable.Range(0, 4096000).Select(c => (byte) c).ToArray();
    private static readonly byte[] YBytes = Enumerable.Range(0, 4096000).Select(c => (byte) c).ToArray();
    public BenchmarkCompareMethod()
    {
        // 修改數(shù)組最后一個(gè)元素,使其不同
        XBytes[4095999] = 1;
        YBytes[4095999] = 2;
    }
    [Benchmark(Baseline = true)]
    public void ForCompare()
    {
        .....
    }
}

需要注意的是,為了保證評(píng)測(cè)的結(jié)果與生產(chǎn)環(huán)境一致,BenchmarkDotNet是要求使用Release模式運(yùn)行程序,這樣的話不僅代碼編譯成IL時(shí)優(yōu)化,程序運(yùn)行中JIT也會(huì)更加積極的參與生產(chǎn)機(jī)器碼優(yōu)化。需要在項(xiàng)目文件夾下面使用dotnet run -c Release來(lái)運(yùn)行評(píng)測(cè)。

幾種不同的方案

For循環(huán)

一開(kāi)始看到這個(gè)需求,第一個(gè)想到的就是直接使用for循環(huán)對(duì)byte[]進(jìn)行按下標(biāo)比較,我覺(jué)得也是大家第一時(shí)間能想到的方案,那我們就上代碼跑跑看速度。

public static bool ForCompare(byte[]? x, byte[]? y)
{
    if (ReferenceEquals(x, y)) return true;		// 引用相等,可以直接認(rèn)為相等
    if (x is null || y is null) return false;	// 兩者引用不相等情況下,一方為null那就不相等
    if (x.Length != y.Length) return false;		// 兩者長(zhǎng)度不等,那么肯定也不相等
    for (var index = 0; index < x.Length; index++)
    {
        if (x[index] != y[index]) return false;
    }
    return true;
}

最終的結(jié)果如下所示,我們可以看到其實(shí)使用for循環(huán)進(jìn)行比較是很快的,4MB大小的數(shù)組2ms左右就能比較完畢。

其實(shí)還有一個(gè)優(yōu)化點(diǎn),.NET的JIT對(duì)一些方法默認(rèn)是不做inline內(nèi)聯(lián)優(yōu)化的,這樣每次還有一個(gè)方法調(diào)用的開(kāi)銷(xiāo),我們讓jit去積極的進(jìn)行內(nèi)聯(lián),再來(lái)試試。方法也很簡(jiǎn)單,只需要引入System.Runtime.CompilerServices命名空間,然后在方法上面打上頭標(biāo)記即可。

要搞清楚為什么方法內(nèi)聯(lián)有用,首先要知道當(dāng)一個(gè)方法被調(diào)用的時(shí)候發(fā)生了什么

1、首先會(huì)有個(gè)執(zhí)行棧,存儲(chǔ)目前所有活躍的方法,以及它們的本地變量和參數(shù)2、當(dāng)一個(gè)新的方法被調(diào)用了,一個(gè)新的棧幀會(huì)被加到棧頂,分配的本地變量和參數(shù)會(huì)存儲(chǔ)在這個(gè)棧幀3、跳到目標(biāo)方法代碼執(zhí)行4、方法返回的時(shí)候,本地方法和參數(shù)會(huì)被銷(xiāo)毀,棧頂被移除5、返回原來(lái)地址執(zhí)行

這就是通常說(shuō)的方法調(diào)用的壓棧和出棧過(guò)程,因此,方法調(diào)用需要有一定的時(shí)間開(kāi)銷(xiāo)和空間開(kāi)銷(xiāo),當(dāng)一個(gè)方法體不大,但又頻繁被調(diào)用時(shí),這個(gè)時(shí)間和空間開(kāi)銷(xiāo)會(huì)相對(duì)變得很大,變得非常不劃算,同時(shí)降低了程序的性能。所以內(nèi)聯(lián)簡(jiǎn)單的說(shuō)就是把目標(biāo)方法里面代碼復(fù)制到調(diào)用方法的地方,無(wú)需壓棧、跳轉(zhuǎn)和出棧。

不過(guò)并不是所有的方法內(nèi)聯(lián)都有益處,需要方法體比較小,如果方法體很大的話在每一個(gè)調(diào)用的地方都會(huì)發(fā)生替換,浪費(fèi)內(nèi)存。

using System.Runtime.CompilerServices;
.....
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static bool ForCompare(byte[]? x, byte[]? y)

再來(lái)跑一下試試。

最后可以看到性能提升了30%呀,分配的字節(jié)數(shù)少了50% (雖然本來(lái)就只有2字節(jié)),講道理就可以直接交差了。

Memcmp

但是群里面還有小伙伴就不滿足了,有沒(méi)有其它的方案?有個(gè)小伙伴就跳出來(lái)說(shuō),操作系統(tǒng)是不是提供了類(lèi)似的功能?會(huì)不會(huì)使用C/C++代碼運(yùn)行起來(lái)會(huì)更加快速?

沒(méi)錯(cuò),操作系統(tǒng)確實(shí)提供了這樣的函數(shù),微軟提供了一個(gè)名為mscrt(微軟C運(yùn)行時(shí)庫(kù))的庫(kù),里面就提到了memcmp這個(gè)函數(shù)就可以來(lái)比較兩個(gè)buffer是否相等。MSDN鏈接.

函數(shù)簽名是這樣的,這個(gè)函數(shù)位于mscrt.dll內(nèi)。

int memcmp(
   const void *buffer1, // 數(shù)組1指針
   const void *buffer2, // 數(shù)組2指針
   size_t count	// 比較字節(jié)數(shù)
);

既然有現(xiàn)成的C語(yǔ)言代碼,那么C#應(yīng)該如何調(diào)用它呢?實(shí)際上C#經(jīng)常被大家成為C++++是有一定道理的,它在設(shè)計(jì)之初就考慮了和C、C++等代碼的交互。這里使用到了C#的Native Interop - P/Invoke技術(shù),可以很方便的使用C風(fēng)格的ABI(C++、Rust等等都提供C語(yǔ)言ABI生成),在.NET底層大量的代碼都是通過(guò)這種方式和底層交互,有興趣的可以戳鏈接了解更詳細(xì)的信息。

那么如何使用它呢?以我們上面的函數(shù)為例,我們只需要引入System.Runtime.InteropServices命名空間,然后按照上面memcmp函數(shù)的簽名轉(zhuǎn)換為C#代碼就行了,最終的代碼如下所示。

using System;
using System.Runtime.InteropServices;
namespace CompareByte;
public static class BytesCompare
{
    [DllImport("msvcrt.dll")]	// 需要使用的dll名稱(chēng)
    private static extern unsafe int memcmp(byte* b1, byte* b2, int count);
    // 由于指針使用是內(nèi)存不安全的操作,所以需要使用unsafe關(guān)鍵字
    // 項(xiàng)目文件中也要加入<AllowUnsafeBlocks>true</AllowUnsafeBlocks>來(lái)允許unsafe代碼
    public static unsafe bool MemcmpCompare(byte[]? x,byte[]? y)
    {
        if (ReferenceEquals(x, y)) return true;
        if (x is null || y is null) return false;
        if (x.Length != y.Length) return false;
        
        // 在.NET程序的運(yùn)行中,垃圾回收器可能會(huì)整理和壓縮內(nèi)存,這樣會(huì)導(dǎo)致數(shù)組地址變動(dòng)
        // 所以,我們需要使用fixed關(guān)鍵字,將x和y數(shù)組'固定'在內(nèi)存中,讓GC不移動(dòng)它
        // 更多詳情請(qǐng)看 https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/fixed-statement
        fixed (byte* xPtr = x, yPtr = y)	
        {
            return memcmp(xPtr, yPtr, x.Length) == 0;
        }
    }
}

那我們來(lái)跑個(gè)分吧,看看結(jié)果怎么樣。

結(jié)果真的是Amazing呀,比我們使用的for循環(huán)方案足足快了80+%,從原來(lái)需要1.7ms左右到現(xiàn)在只需要300us。

64字長(zhǎng)優(yōu)化

那是不是證明C#就是沒(méi)有C跑的那么快呢?C#那還有沒(méi)有優(yōu)化的空間呢?當(dāng)然是有方法的,實(shí)際上memcmp使用的算法和我們現(xiàn)在用的不一樣。

我們知道衡量算法的時(shí)間復(fù)雜度是使用大O來(lái)表示,而這個(gè)其實(shí)是代碼執(zhí)行時(shí)間隨數(shù)據(jù)規(guī)模增長(zhǎng)的變化趨勢(shì)的一個(gè)體現(xiàn)。比如我輸入的數(shù)據(jù)量大小為n,完成這個(gè)函數(shù)我近似需要執(zhí)行n次,那么時(shí)間復(fù)雜度就是O(n)。

再來(lái)回到我們的問(wèn)題中,在最壞的情況下(xy引用不相等且的長(zhǎng)度相等),我們上面寫(xiě)的ForCompare就會(huì)進(jìn)入for循環(huán)來(lái)遍歷xy每一個(gè)元素進(jìn)行比較,所以它的時(shí)間復(fù)雜度就是O(n),那么問(wèn)題的關(guān)鍵就是如何降低它的時(shí)間復(fù)雜度。

一個(gè)數(shù)組它的地址空間是連續(xù)的,另外byte類(lèi)型的長(zhǎng)度是8bit,默認(rèn)比較方式就像下圖一樣,一個(gè)元素一個(gè)元素的比較,也就是每8bit8bit進(jìn)行比較。

那我們能讓他一次比較更多的位數(shù)嗎?比如一次16位、32位、64位?當(dāng)然是可以的,畢竟我們現(xiàn)在基本都是64位的CPU,不嚴(yán)謹(jǐn)?shù)恼f(shuō)實(shí)際上CPU一次能處理64位數(shù)據(jù),那么我們?nèi)绾巫屗淮涡阅鼙容^64位呢?

有小伙伴就說(shuō),很簡(jiǎn)單嘛,byte8bit,我們直接用long不就有64bit了嗎?沒(méi)錯(cuò),就是這么簡(jiǎn)單,我們可以把byte*指針強(qiáng)轉(zhuǎn)為long*指針,然后一次性比較64位,如下圖所示。

上代碼(我這用的是UInt64包裝的ulong,一樣是64位,沒(méi)有符號(hào)位會(huì)更快一點(diǎn)):

public static unsafe bool UlongCompare(byte[]? x, byte[]? y)
{
    if (ReferenceEquals(x, y)) return true;
    if (x is null || y is null) return false;
    if (x.Length != y.Length) return false;
    
    fixed (byte* xPtr = x, yPtr = y)
    {
        return UlongCompareInternal(xPtr, yPtr, x.Length);
    }
}
private static unsafe bool UlongCompareInternal(byte* xPtr, byte* yPtr, int length)
{
    // 指針+偏移量計(jì)算出數(shù)組最后一個(gè)元素地址
    byte* lastAddr = xPtr + length;
    byte* lastAddrMinus32 = lastAddr - 32;
    while (xPtr < lastAddrMinus32) // 我們一次循環(huán)比較32字節(jié),也就是256位
    {
        // 一次判斷比較前64位
        if (*(ulong*) xPtr != *(ulong*) yPtr) return false;
        // 第二次從64為開(kāi)始,比較接下來(lái)的64位,需要指針偏移64位,一個(gè)byte指針是8為,所以需要偏移8個(gè)位置才能到下一輪起始位置
        // 所以代碼就是xPtr+8
        if (*(ulong*) (xPtr + 8) != *(ulong*) (yPtr + 8)) return false;
        // 同上面一樣,第三次從第128位開(kāi)始比較64位
        if (*(ulong*) (xPtr + 16) != *(ulong*) (yPtr + 16)) return false;
        // 第四次從第192位開(kāi)始比較64位
        if (*(ulong*) (xPtr + 24) != *(ulong*) (yPtr + 24)) return false;
        // 一輪總共比較了256位,讓指針偏移256位
        xPtr += 32;
        yPtr += 32;
    }
    // 因?yàn)樯厦媸且淮涡员容^32字節(jié)(256位),可能數(shù)組不能為32整除,最后只留下比如30字節(jié),20字節(jié)
    // 最后的幾個(gè)字節(jié),我們用循環(huán)來(lái)逐字節(jié)比較
    while (xPtr < lastAddr)
    {
        if (*xPtr != *yPtr) return false;
        xPtr++;
        yPtr++;
    }
    return true;
}

那我們來(lái)跑個(gè)分吧。

可以看到基本和memcmp打平了,幾u(yù)s的差別可以看做是誤差。大佬們一直說(shuō),C#是一門(mén)下限低,上限高的語(yǔ)言,你開(kāi)心的話寫(xiě)出來(lái)的代碼完全媲美C++,代碼里面還能嵌入?yún)R編,只是有點(diǎn)麻煩,O(∩_∩)O哈哈~

SIMD

那么我們就這樣滿足了嗎?

小伙伴又問(wèn)了,既然我們可以一次性比較64位,那我們能比較更多的位數(shù)嗎?比如128位,256位?答案是當(dāng)然可以,這個(gè)是CPU的一個(gè)技術(shù),叫Single Instruction Multiple Data,簡(jiǎn)稱(chēng)為SIMD,SIMD主要就是說(shuō)CPU中可以單條指令實(shí)現(xiàn)數(shù)據(jù)的并行處理,這類(lèi)指令在數(shù)字信號(hào)處理、圖像處理、以及多媒體信息處理等領(lǐng)域非常有效。

我們打開(kāi)CPU-Z,可以看到指令集有很多,這都是CPU為了特殊的程序單獨(dú)做的優(yōu)化。

MMX:MMX 是MultiMedia eXtensions(多媒體擴(kuò)展)的縮寫(xiě),是第六代CPU芯片的重要特點(diǎn)。MMX技術(shù)是在CPU中加入了特地為視頻信號(hào)(Video Signal),音頻信號(hào)(Audio Signal)以及圖像處理(Graphical Manipulation)而設(shè)計(jì)的57條指令,因此,MMX CPU極大地提高了電腦的多媒體(如立體聲、視頻、三維動(dòng)畫(huà)等)處理功能。

SSE:SSE是 “因特網(wǎng)數(shù)據(jù)流單指令序列擴(kuò)展 ( Internet Streaming SIMD Extensions)的縮寫(xiě)。SSE除保持原有的MMX指令外,又新增了70條指令,在加快浮點(diǎn)運(yùn)算的同時(shí),改善了內(nèi)存的使用效率,使內(nèi)存速度更快,后面有一些增強(qiáng)版SSE2、SSE3等等。

EM64T:Intel的EM64T技術(shù),EM64T技術(shù)官方全名是Extended Memory 64 Technology,中文解釋就是擴(kuò)展64bit內(nèi)存技術(shù)。

AES:AES(Advanced Encryption Standard,高級(jí)加密標(biāo)準(zhǔn))指令集,是專(zhuān)門(mén)為加密解密設(shè)計(jì)的,與此前相比AES加密/解密之性能高出3倍。

AVX:Advanced Vector eXtentions(AVX)在2008年由Intel與AMD提出,并于2011年分別在Sandy Bridge以及Bulldozer架構(gòu)上提供?持。AVX的主要改進(jìn)在于對(duì)寄存器長(zhǎng)度的擴(kuò)展以及提供了更靈活的指令集。 AVX對(duì) XMM 寄存器做了擴(kuò)展,從原來(lái)的128-bit擴(kuò)展到了256-bit,256-bit的寄存器命名為 YMM 。YMM的低128-bit是與XMM混? 的。

對(duì)于這些指令集,在.NET上提供了System.Runtime.Intrinsics.X86命名空間,其中支持了各種指令集原生的訪問(wèn),想了解更多的東西,可以戳這個(gè)鏈接。由于SIMD在.NET上有著天然的支持,可以很方便的寫(xiě)出SIMD代碼,而其它編程語(yǔ)言平臺(tái)或多或少支持都不是很完美。

類(lèi)名作用
Aes此類(lèi)通過(guò)內(nèi)部函數(shù)提供對(duì) Intel AES 硬件指令的訪問(wèn)權(quán)限。
Avx該類(lèi)通過(guò)內(nèi)聯(lián)函數(shù)提供對(duì) Intel AVX 硬件指令的訪問(wèn)權(quán)限。
Avx2此類(lèi)通過(guò)內(nèi)部函數(shù)提供對(duì) Intel AVX2 硬件指令的訪問(wèn)。
Bmi1此類(lèi)通過(guò)內(nèi)部函數(shù)提供對(duì) Intel BMI1 硬件指令的訪問(wèn)權(quán)限。
Bmi2此類(lèi)通過(guò)內(nèi)部函數(shù)提供對(duì) Intel BMI2 硬件指令的訪問(wèn)權(quán)限。
Fma此類(lèi)通過(guò)內(nèi)部函數(shù)提供對(duì) Intel FMA 硬件指令的訪問(wèn)權(quán)限。
Lzcnt此類(lèi)通過(guò)內(nèi)部函數(shù)提供對(duì) Intel LZCNT 硬件指令的訪問(wèn)權(quán)限。
Pclmulqdq此類(lèi)通過(guò)內(nèi)部函數(shù)提供對(duì) Intel PCLMULQDQ 硬件指令的訪問(wèn)權(quán)限。
Popcnt此類(lèi)通過(guò)內(nèi)部函數(shù)提供對(duì) Intel POPCNT 硬件指令的訪問(wèn)權(quán)限。
Sse此類(lèi)通過(guò)內(nèi)部函數(shù)提供對(duì) Intel SSE 硬件指令的訪問(wèn)權(quán)限。
Sse2此類(lèi)通過(guò)內(nèi)部函數(shù)提供對(duì) Intel SSE2 硬件指令的訪問(wèn)權(quán)限。
Sse3此類(lèi)通過(guò)內(nèi)部函數(shù)提供對(duì) Intel SSE3 硬件指令的訪問(wèn)權(quán)限。
Sse41此類(lèi)通過(guò)內(nèi)部函數(shù)提供對(duì) Intel SSE 4.1 硬件指令的訪問(wèn)。
Sse42此類(lèi)通過(guò)內(nèi)部函數(shù)提供對(duì) Intel SSE4.2 硬件指令的訪問(wèn)權(quán)限。
Ssse3此類(lèi)通過(guò)內(nèi)部函數(shù)提供對(duì) Intel SSSE3 硬件指令的訪問(wèn)權(quán)限。
X86Base通過(guò)內(nèi)部函數(shù)提供對(duì) x86 基本硬件指令的訪問(wèn)。

Sse

我們看到SSE系列的指令集可以操作128位,那我們就來(lái)試試128位會(huì)不會(huì)更快一些,直接上代碼。

using System.Runtime.Intrinsics.X86; // 需要引入這個(gè)命名空間
namespace CompareByte;
public static class BytesCompare
{
    ......
    public static unsafe bool Sse2Compare(byte[]? x, byte[]? y)
    {
        if (ReferenceEquals(x, y)) return true;
        if (x is null || y is null) return false;
        if (x.Length != y.Length) return false;
        
        fixed (byte* xPtr = x, yPtr = y)
        {
            return Sse2CompareInternal(xPtr, yPtr, x.Length);
        }
    }
    
    private static unsafe bool Sse2CompareInternal(byte* xPtr, byte* yPtr, int length)
        // 這里的算法與64位大體一樣,只是位數(shù)變成了128位
        byte* lastAddr = xPtr + length;
        byte* lastAddrMinus64 = lastAddr - 64;
        const int mask = 0xFFFF;
        while (xPtr < lastAddrMinus64)
            // 使用Sse2.LoadVector128()各加載x和y的128位數(shù)據(jù)
            // 再使用Sse2.CompareEqual()比較是否相等,它的返回值是一個(gè)128位向量,如果相等,該位置返回0xffff,否則返回0x0
            // CompareEqual的結(jié)果是128位的,我們可以通過(guò)Sse2.MoveMask()來(lái)重新排列成32位,最終看是否等于0xffff就好
            if (Sse2.MoveMask(Sse2.CompareEqual(Sse2.LoadVector128(xPtr), Sse2.LoadVector128(yPtr))) != mask)
            {
                return false;
            }
            if (Sse2.MoveMask(Sse2.CompareEqual(Sse2.LoadVector128(xPtr + 16), Sse2.LoadVector128(yPtr + 16))) != mask)
            if (Sse2.MoveMask(Sse2.CompareEqual(Sse2.LoadVector128(xPtr + 32), Sse2.LoadVector128(yPtr + 32))) != mask)
            if (Sse2.MoveMask(Sse2.CompareEqual(Sse2.LoadVector128(xPtr + 48), Sse2.LoadVector128(yPtr + 48))) != mask)
            xPtr += 64;
            yPtr += 64;
        while (xPtr < lastAddr)
            if (*xPtr != *yPtr) return false;
            xPtr++;
            yPtr++;
        return true;
}

放到JIT里面看看,有沒(méi)有生成SIMD代碼,可以明顯的看到匯編代碼里面已經(jīng)有了SIMD代碼。

來(lái)看看跑分結(jié)果。

可以看到對(duì)比memcmp的方式快了2+%,那按道理來(lái)說(shuō)從64位到128位應(yīng)該快50%左右才對(duì),為什么只快了2+%呢?

其實(shí)這是因?yàn)镾IMD是單條指令多數(shù)據(jù)處理,其中運(yùn)算還是CPU內(nèi)部的64位單元處理,只是少了多條指令的開(kāi)銷(xiāo)。另外是因?yàn)樵?4位是只比較了一次,而SIMD需要經(jīng)歷CompareEqual、MoveMask最后還需和mask掩碼比較,總共次數(shù)多了2次。只能說(shuō)明在我們的這個(gè)場(chǎng)景下,提升會(huì)比較有限。

需要注意目標(biāo)平臺(tái)需要能支持這些特殊的指令集,可以通過(guò)Sse2.IsSupported方法來(lái)判斷。

Avx2

既然128位的SSE系列指令集能在原來(lái)的基礎(chǔ)上提升2%,那我們來(lái)看看支持256位的Avx2指令集會(huì)提升多少。代碼和SSE指令集幾乎一樣,只是調(diào)用的方法類(lèi)名變動(dòng)了。

using System.Runtime.Intrinsics.X86; // 需要引入這個(gè)命名空間
namespace CompareByte;
public static class BytesCompare
{
    ......
	public static unsafe bool Avx2Compare(byte[]? x, byte[]? y)
    {
        if (ReferenceEquals(x, y)) return true;
        if (x is null || y is null) return false;
        if (x.Length != y.Length) return false;
        
        fixed (byte* xPtr = x, yPtr = y)
        {
            return Avx2CompareInternal(xPtr, yPtr, x.Length);
        }
    }
    
    private static unsafe bool Avx2CompareInternal(byte* xPtr, byte* yPtr, int length)
        byte* lastAddr = xPtr + length;
        byte* lastAddrMinus128 = lastAddr - 128;
        const int mask = -1;
        while (xPtr < lastAddrMinus128)
            // 更換為Avx2指令集,一次加載256位
            if (Avx2.MoveMask(Avx2.CompareEqual(Avx.LoadVector256(xPtr), Avx.LoadVector256(yPtr))) != mask)
            {
                return false;
            }
            if (Avx2.MoveMask(Avx2.CompareEqual(Avx.LoadVector256(xPtr + 32), Avx.LoadVector256(yPtr + 32))) != mask)
            if (Avx2.MoveMask(Avx2.CompareEqual(Avx.LoadVector256(xPtr + 64), Avx.LoadVector256(yPtr + 64))) != mask)
            if (Avx2.MoveMask(Avx2.CompareEqual(Avx.LoadVector256(xPtr + 96), Avx.LoadVector256(yPtr + 96))) != mask)
            xPtr += 128;
            yPtr += 128;
        while (xPtr < lastAddr)
            if (*xPtr != *yPtr) return false;
            xPtr++;
            yPtr++;
        return true;
}

再來(lái)看看跑分結(jié)果。

可以看到,Avx2指令集對(duì)于memcmpSse2是有一定的提升的,有2+%左右的速度提升,另外相較于原本的for循環(huán)比較提升了86%。

SequenceCompare

那么是不是以后我們寫(xiě)比較兩個(gè)數(shù)組相等的代碼都要寫(xiě)這一長(zhǎng)串的unsafe代碼呢?其實(shí)并不是,在.NET Core時(shí)代引入了Span這個(gè)特性,這個(gè)特性就是為了能安全的直接操作內(nèi)存;與此同時(shí),也提供了SequenceEquals方法,能快速的比較兩個(gè)序列,使用也非常簡(jiǎn)單,那究竟性能怎么樣呢?我們上代碼,跑個(gè)分。

// 代碼非常簡(jiǎn)單,只需要調(diào)用System.Linq.SequenceEqual方法即可
public static bool SequenceCompare(byte[]? x, byte[]? y)
{
    if (ReferenceEquals(x, y)) return true;
    if (x is null || y is null) return false;
    if (x.Length != y.Length) return false;
    
    return x.SequenceEqual(y);
}

結(jié)果也是相當(dāng)不錯(cuò)的,比memcmp和SSE2的方式都要快一點(diǎn),略遜于Avx2,但是它用起來(lái)很簡(jiǎn)單,那么它是如何做到這么快的呢?讓我們看看它的源碼,

鏈接貌似也沒(méi)有什么技巧,那是不是JIT編譯的時(shí)候有優(yōu)化,給自動(dòng)向量化了呢?我們將代碼復(fù)制出來(lái),然后單獨(dú)跑了一下,再用WinDBG打開(kāi),我們可以看到確實(shí)JIT優(yōu)化引入了一些自動(dòng)向量化(SIMD)的操作。

總結(jié)

通過(guò)這幾種方案的對(duì)比,最推薦的用法當(dāng)然就是直接使用.NET庫(kù)提供的SequenceEquals方法來(lái)完成比較,如果是在.NET Framework中,由于沒(méi)有這樣的優(yōu)化,所以大家也可以嘗試上文中提到的SSE2等方法。

那么大家還有什么其它好的方式呢?歡迎在評(píng)論區(qū)留言!

筆者水平有限,如有錯(cuò)漏請(qǐng)批評(píng)指正 :)

本文源碼鏈接

參考文獻(xiàn)

BenchmarkDotNet項(xiàng)目地址

BenchmarkDotNet幫助文檔

MSCRT庫(kù)參考

C# Interop

JVM的方法內(nèi)聯(lián)

.NET SIMD API

SSE2 Intrinsics各函數(shù)介紹

Checking equality for two byte arrays

到此這篇關(guān)于.NET如何快速比較兩個(gè)byte數(shù)組是否相等的文章就介紹到這了,更多相關(guān).NET比較兩個(gè)byte數(shù)組是否相等內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • asp.net生成驗(yàn)證碼代碼(純中文)

    asp.net生成驗(yàn)證碼代碼(純中文)

    下面我來(lái)寫(xiě)一個(gè)生成純中文的驗(yàn)證碼技術(shù),寫(xiě)一個(gè)類(lèi)庫(kù),需要的朋友可以參考下
    2012-06-06
  • .Net使用Cancellation?Framework取消并行任務(wù)

    .Net使用Cancellation?Framework取消并行任務(wù)

    這篇文章介紹了.Net使用Cancellation?Framework取消并行任務(wù)的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-06-06
  • asp.net core為IHttpClientFactory添加動(dòng)態(tài)命名配置

    asp.net core為IHttpClientFactory添加動(dòng)態(tài)命名配置

    某些時(shí)候我們需要為HttpClient動(dòng)態(tài)配置一些東西, 例如證書(shū)等, 例如服務(wù)是一個(gè)回調(diào)服務(wù), 而被回調(diào)方采用了自定義的https(即自定義證書(shū)),本文就將講述如何實(shí)現(xiàn)這種需求
    2021-06-06
  • ASP.NET 文件斷點(diǎn)續(xù)傳實(shí)現(xiàn)代碼

    ASP.NET 文件斷點(diǎn)續(xù)傳實(shí)現(xiàn)代碼

    在文件下載的時(shí)候,使用斷點(diǎn)續(xù)傳可以將上次未下載完成的文件繼續(xù)下載,該功能在開(kāi)發(fā)文件下載的時(shí)候非常重要。這里我將介紹一種比較簡(jiǎn)單的斷點(diǎn)續(xù)傳功能的實(shí)現(xiàn)方法,僅供初學(xué)者參考使用
    2012-06-06
  • 詳解ASP.NET中Session的用法

    詳解ASP.NET中Session的用法

    我們可以使用 Session 對(duì)象存儲(chǔ)特定的用戶會(huì)話所需的信息,這篇文章就為大家詳細(xì)介紹了ASP.NET中Session的用法,感興趣的小伙伴們可以參考一下
    2016-05-05
  • WPF使用AForge調(diào)用攝像頭

    WPF使用AForge調(diào)用攝像頭

    這篇文章介紹了WPF使用AForge調(diào)用攝像頭的方法,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-01-01
  • ASP.NET中的跳轉(zhuǎn) 200, 301, 302轉(zhuǎn)向?qū)崿F(xiàn)代碼

    ASP.NET中的跳轉(zhuǎn) 200, 301, 302轉(zhuǎn)向?qū)崿F(xiàn)代碼

    跳轉(zhuǎn)非常常用,在哪里都一樣,這里的一些說(shuō)明和用法也如此,不止適用于asp.net,其他語(yǔ)言也會(huì)用得到。跳轉(zhuǎn)的目的本來(lái)很簡(jiǎn)單,就是當(dāng)用戶或系統(tǒng)需要時(shí)從一個(gè)頁(yè)面轉(zhuǎn)向另一個(gè)頁(yè)面,但自從有了各種各樣的需求,還有那個(gè)什么SEO的東西之后,跳轉(zhuǎn)被搞得極其復(fù)雜
    2008-09-09
  • asp.net各種cookie代碼和解析實(shí)例

    asp.net各種cookie代碼和解析實(shí)例

    這篇文章主要介紹了asp.net各種cookie代碼和解析實(shí)例,需要的朋友可以參考下
    2014-02-02
  • ADO.NET無(wú)連接模式的詳細(xì)介紹

    ADO.NET無(wú)連接模式的詳細(xì)介紹

    這篇文章詳細(xì)介紹了ADO.NET無(wú)連接模式及操作實(shí)例,有需要的朋友可以參考一下
    2013-10-10
  • VS2005 水晶報(bào)表在時(shí)部署時(shí)遇到的問(wèn)題

    VS2005 水晶報(bào)表在時(shí)部署時(shí)遇到的問(wèn)題

    前幾天在服務(wù)器上部署一個(gè)B/S程序的時(shí)候,程序中的水晶報(bào)表部分出了些問(wèn)題,報(bào)錯(cuò):Server Error in '/' Application.
    2010-02-02

最新評(píng)論