当前位置:  开发笔记 > 编程语言 > 正文

比较.NET中的两个字节数组

如何解决《比较.NET中的两个字节数组》经验,为你挑选了13个好方法。

我怎么能快速做到这一点?

当然,我可以这样做:

static bool ByteArrayCompare(byte[] a1, byte[] a2)
{
    if (a1.Length != a2.Length)
        return false;

    for (int i=0; i

但我正在寻找BCL功能或一些经过高度优化的可靠方法来实现这一目标.

java.util.Arrays.equals((sbyte[])(Array)a1, (sbyte[])(Array)a2);

很好地工作,但它看起来不适用于x64.

请注意我的超快速的答案在这里.



1> aku..:

您可以使用Enumerable.SequenceEqual方法.

using System;
using System.Linq;
...
var a1 = new int[] { 1, 2, 3};
var a2 = new int[] { 1, 2, 3};
var a3 = new int[] { 1, 2, 4};
var x = a1.SequenceEqual(a2); // true
var y = a1.SequenceEqual(a3); // false

如果由于某种原因无法使用.NET 3.5,那么您的方法就可以了.
编译器\运行时环境将优化您的循环,因此您不必担心性能.


是的,这比不安全的比较慢了大约50倍.
对业务而言,最快并不是最重要的事情.在99%的情况下,可维护性比此代码的节省要快得多.我正在使用SequenceEqual,我的整个代码<1ms.你保存的那些μs永远不会累加到P/Invoke缺乏可读性的5分钟.
这实际上是在这里提升死者,但在这里使用慢是一个坏词.慢50倍_sounds_糟糕,但通常不会比较足够的数据来产生差异,如果你是,你真的需要根据自己的情况对此进行基准测试,原因有很多.例如,请注意不安全答案的创建者注意到7x慢的差异,而不是50x的差异(不安全方法的速度也取决于数据的对齐).在极少数情况下,这些数字很重要,P/Invoke甚至更快.
那么较慢的实现有超过300个喜欢?我建议挂钩msvcrt.dll,因为这将是完成工作的最快方法.
快速也取决于数据的大小以及您执行此操作的频率.我在一个循环之外比较一个字节数组,其中包含10个字节.如果我试图比较10mb位图,也许值得研究p/invoke解决方案.过早优化和微观优化可能是杀手锏
但是,SequenceEqual不是比不安全的比较需要更长的时间来处理吗?特别是当你进行1000次比较时?

2> plinth..:

P/Invoke功能激活!

[DllImport("msvcrt.dll", CallingConvention=CallingConvention.Cdecl)]
static extern int memcmp(byte[] b1, byte[] b2, long count);

static bool ByteArrayCompare(byte[] b1, byte[] b2)
{
    // Validate buffers are the same length.
    // This also ensures that the count does not exceed the length of either buffer.  
    return b1.Length == b2.Length && memcmp(b1, b2, b1.Length) == 0;
}


P/Invoke yaay - 至少在位图上证明是最快的:http://stackoverflow.com/questions/2031217/what-is-the-fastest-way-i-can-compare-two-equal-size -bitmaps到确定,无论/ 2038515#2038515
为什么嘘?海报希望快速实现并且优化的汇编语言比较无法击败.我不知道如何在没有P/INVOKE的情况下从.NET中获取"REPE CMPSD".
在这种情况下,不需要固定.使用PInvoke调用本机代码时,编组器会执行自动固定.参考:http://stackoverflow.com/questions/2218444/when-passing-a-managed-byte-array-through-pinvoke-to-be-filled-in-by-win32-doe
P/Invoke可以引出嘘声,但它是迄今为止所提出的所有解决方案中最快的,包括我提出的使用不安全的指针大小比较的实现.在调用本机代码(包括引用相等性和比较第一个和最后一个元素)之前,您可以进行一些优化.
Nitpick:用户代码不应该使用MSVCR.dll.要使用MSVCR,您必须使用您分发的版本分发运行时.(http://msdn.microsoft.com/en-us/library/abx4dbyh%28VS.80%29.aspx#sectiontoggle2和http://blogs.msdn.com/b/oldnewthing/archive/2014/04/11 /10516280.aspx)
比简单的循环快约11倍.比不安全的.NET代码快约50%.
这很快,但也可能与使用for循环不同.此实现将比较内存而不是使用Equals比较.在byte []的情况下,结果应该相同,但是这个解决方案不能推广到任意数组.
请注意,`memcmp`的最后一个参数是`IntPtr`,而不是`long`,因为如果程序以32位运行,则为32位,64位为64位.显然,你必须`memcmp(b1,b2,(IntPtr)b1.Length)`
很好的解决方案,但除非你将字节数组固定到位,否则你会遇到大问题.
在部署时,不要忘记将CRT与您的应用程序一起分发......
我有一些问题,这个方法的确切实现; 解决了他们w /这里找到的信息:http://www.pinvoke.net/default.aspx/msvcrt.memcmp

3> Ohad Schneid..:

.NET 4中有一个新的内置解决方案--IStructuralEquatable

static bool ByteArrayCompare(byte[] a1, byte[] a2) 
{
    return StructuralComparisons.StructuralEqualityComparer.Equals(a1, a2);
}


疯狂的慢.比简单的循环慢约180倍.
根据[这篇博文](http://uhurumkate.blogspot.com/2012/07/byte-array-comparison-benchmarks.html),这实际上非常慢.

4> Hafthor..:

用户gil提出了产生此解决方案的不安全代码:

// Copyright (c) 2008-2013 Hafthor Stefansson
// Distributed under the MIT/X11 software license
// Ref: http://www.opensource.org/licenses/mit-license.php.
static unsafe bool UnsafeCompare(byte[] a1, byte[] a2) {
  if(a1==a2) return true;
  if(a1==null || a2==null || a1.Length!=a2.Length)
    return false;
  fixed (byte* p1=a1, p2=a2) {
    byte* x1=p1, x2=p2;
    int l = a1.Length;
    for (int i=0; i < l/8; i++, x1+=8, x2+=8)
      if (*((long*)x1) != *((long*)x2)) return false;
    if ((l & 4)!=0) { if (*((int*)x1)!=*((int*)x2)) return false; x1+=4; x2+=4; }
    if ((l & 2)!=0) { if (*((short*)x1)!=*((short*)x2)) return false; x1+=2; x2+=2; }
    if ((l & 1)!=0) if (*((byte*)x1) != *((byte*)x2)) return false;
    return true;
  }
}

对尽可能多的数组进行基于64位的比较.这种情况取决于数组启动qword对齐的事实.如果没有qword对齐它就会起作用,只是没有像它那样快.

它比简单for循环执行大约七个定时器.使用J#库与原始for循环等效地执行.使用.SequenceEqual大约慢七倍; 我想是因为它使用的是IEnumerator.MoveNext.我认为基于LINQ的解决方案至少要慢或者差.


.NET 4 x64版本的新测试数据:IStructualEquatable.equals~180x慢,SequenceEqual慢15x,SHA1散列比较慢11x,bitconverter~相同,不安全快7倍,pinvoke快11倍.非常酷,不安全只比memcmp上的P/Invoke慢一点.
当a1和a2都为空时,这会给出错误吗?
好的解决方案 但是一个(小)提示:比较如果引用a1和a2相等可能会加速事情,如果给a1和b1给出相同的数组.
此链接提供了有关内存对齐为何重要的详细信息http://www.ibm.com/developerworks/library/pa-dalign/ - 因此,优化可能是检查对齐,如果两个数组的对齐量相同, do字节进行比较,直到它们都在qword边界上.
@CristiDiaconescu我循环了KevinDriedger的答案.我应该做的是让测试套件和我的结果在github上可用并在我的答案上链接到它.

5> Joe Amenta..:

Span 提供极具竞争力的替代方案,无需将混乱和/或不便携的绒毛扔进您自己的应用程序的代码库:

// byte[] is implicitly convertible to ReadOnlySpan
static bool ByteArrayCompare(ReadOnlySpan a1, ReadOnlySpan a2)
{
    return a1.SequenceEqual(a2);
}

可以在这里找到(实施的)内容.

我修改了 @EliArbel的要点,将这个方法添加为SpansEqual,删除其他基准测试中大多数不那么有趣的表现形式,运行不同的数组大小,输出图形和标记SpansEqual作为基线,以便报告不同方法的比较方式SpansEqual.

以下数字来自结果,轻微编辑以删除"错误"列.

|        Method |  ByteCount |               Mean |            StdDev | Ratio |
|-------------- |----------- |-------------------:|------------------:|------:|
|    SpansEqual |         15 |           3.813 ns |         0.0043 ns |  1.00 |
|  LongPointers |         15 |           4.768 ns |         0.0081 ns |  1.25 |
|      Unrolled |         15 |          17.763 ns |         0.0319 ns |  4.66 |
| PInvokeMemcmp |         15 |          12.280 ns |         0.0221 ns |  3.22 |
|               |            |                    |                   |       |
|    SpansEqual |       1026 |          29.181 ns |         0.0461 ns |  1.00 |
|  LongPointers |       1026 |          63.050 ns |         0.0785 ns |  2.16 |
|      Unrolled |       1026 |          39.070 ns |         0.0412 ns |  1.34 |
| PInvokeMemcmp |       1026 |          44.531 ns |         0.0581 ns |  1.53 |
|               |            |                    |                   |       |
|    SpansEqual |    1048585 |      43,838.865 ns |        56.7144 ns |  1.00 |
|  LongPointers |    1048585 |      59,629.381 ns |       194.0304 ns |  1.36 |
|      Unrolled |    1048585 |      54,765.863 ns |        34.2403 ns |  1.25 |
| PInvokeMemcmp |    1048585 |      55,250.573 ns |        49.3965 ns |  1.26 |
|               |            |                    |                   |       |
|    SpansEqual | 2147483591 | 247,237,201.379 ns | 2,734,143.0863 ns |  1.00 |
|  LongPointers | 2147483591 | 241,535,134.852 ns | 2,720,870.8915 ns |  0.98 |
|      Unrolled | 2147483591 | 240,170,750.054 ns | 2,729,935.0576 ns |  0.97 |
| PInvokeMemcmp | 2147483591 | 238,953,916.032 ns | 2,692,490.7016 ns |  0.97 |

我很惊讶地发现SpansEqual最大阵列大小的方法并不出众,但差别很小,以至于我觉得它不重要.

我的系统信息:

BenchmarkDotNet=v0.11.5, OS=Windows 10.0.17134.706 (1803/April2018Update/Redstone4)
Intel Core i7-6850K CPU 3.60GHz (Skylake), 1 CPU, 12 logical and 6 physical cores
Frequency=3515626 Hz, Resolution=284.4444 ns, Timer=TSC
.NET Core SDK=2.2.202
  [Host]     : .NET Core 2.2.3 (CoreCLR 4.6.27414.05, CoreFX 4.6.27414.05), 64bit RyuJIT
  DefaultJob : .NET Core 2.2.3 (CoreCLR 4.6.27414.05, CoreFX 4.6.27414.05), 64bit RyuJIT



6> Jason Buntin..:

如果您不反对这样做,可以导入J#程序集"vjslib.dll"并使用其Arrays.equals(byte [],byte [])方法 ...

如果有人嘲笑你,不要怪我...


编辑:为了它的价值,我使用Reflector来反汇编代码,这就是它的样子:

public static bool equals(sbyte[] a1, sbyte[] a2)
{
  if (a1 == a2)
  {
    return true;
  }
  if ((a1 != null) && (a2 != null))
  {
    if (a1.Length != a2.Length)
    {
      return false;
    }
    for (int i = 0; i < a1.Length; i++)
    {
      if (a1[i] != a2[i])
      {
        return false;
      }
    }
    return true;
  }
  return false;
}



7> Milan Gardia..:

.NET 3.5和更新版本有一个新的公共类型,System.Data.Linq.Binary它封装起来byte[].它实现IEquatable了(实际上)比较两个字节数组.注意,System.Data.Linq.Binary也有隐式转换运算符byte[].

MSDN文档:System.Data.Linq.Binary

反射器反编译的Equals方法:

private bool EqualsTo(Binary binary)
{
    if (this != binary)
    {
        if (binary == null)
        {
            return false;
        }
        if (this.bytes.Length != binary.bytes.Length)
        {
            return false;
        }
        if (this.hashCode != binary.hashCode)
        {
            return false;
        }
        int index = 0;
        int length = this.bytes.Length;
        while (index < length)
        {
            if (this.bytes[index] != binary.bytes[index])
            {
                return false;
            }
            index++;
        }
    }
    return true;
}

有趣的是,如果两个二进制对象的哈希值相同,它们只会进行逐字节比较循环.然而,这是以在Binary对象的构造函数中计算哈希为代价的(通过使用for循环遍历数组:-)).

上面的实现意味着在最坏的情况下你可能需要遍历数组三次:首先计算array1的散列,然后计算array2的散列,最后(因为这是最坏的情况,长度和散列相等)来比较array1中的字节,数组2中的字节数.

总的来说,即使System.Data.Linq.Binary内置于BCL,我也不认为这是比较两个字节数组的最快方法: - |.



8> ArekBulski..:

我发布了一个关于检查byte []是否充满零的类似问题.(SIMD代码遭到殴打,所以我从这个答案中删除了它.)以下是我比较中最快的代码:

static unsafe bool EqualBytesLongUnrolled (byte[] data1, byte[] data2)
{
    if (data1 == data2)
        return true;
    if (data1.Length != data2.Length)
        return false;

    fixed (byte* bytes1 = data1, bytes2 = data2) {
        int len = data1.Length;
        int rem = len % (sizeof(long) * 16);
        long* b1 = (long*)bytes1;
        long* b2 = (long*)bytes2;
        long* e1 = (long*)(bytes1 + len - rem);

        while (b1 < e1) {
            if (*(b1) != *(b2) || *(b1 + 1) != *(b2 + 1) || 
                *(b1 + 2) != *(b2 + 2) || *(b1 + 3) != *(b2 + 3) ||
                *(b1 + 4) != *(b2 + 4) || *(b1 + 5) != *(b2 + 5) || 
                *(b1 + 6) != *(b2 + 6) || *(b1 + 7) != *(b2 + 7) ||
                *(b1 + 8) != *(b2 + 8) || *(b1 + 9) != *(b2 + 9) || 
                *(b1 + 10) != *(b2 + 10) || *(b1 + 11) != *(b2 + 11) ||
                *(b1 + 12) != *(b2 + 12) || *(b1 + 13) != *(b2 + 13) || 
                *(b1 + 14) != *(b2 + 14) || *(b1 + 15) != *(b2 + 15))
                return false;
            b1 += 16;
            b2 += 16;
        }

        for (int i = 0; i < rem; i++)
            if (data1 [len - 1 - i] != data2 [len - 1 - i])
                return false;

        return true;
    }
}

在两个256MB字节数组上测量:

UnsafeCompare                           : 86,8784 ms
EqualBytesSimd                          : 71,5125 ms
EqualBytesSimdUnrolled                  : 73,1917 ms
EqualBytesLongUnrolled                  : 39,8623 ms



9> user565710..:
 using System.Linq; //SequenceEqual

 byte[] ByteArray1 = null;
 byte[] ByteArray2 = null;

 ByteArray1 = MyFunct1();
 ByteArray2 = MyFunct2();

 if (ByteArray1.SequenceEqual(ByteArray2) == true)
 {
    MessageBox.Show("Match");
 }
 else
 {
   MessageBox.Show("Don't match");
 }



10> Eli Arbel..:

我们再添加一个!

最近微软发布了一个特殊的NuGet包System.Runtime.CompilerServices.Unsafe.它很特别,因为它是用IL编写的,并提供了C#中不能直接提供的低级功能.

其中一种方法Unsafe.As(object)允许将任何引用类型转换为另一种引用类型,跳过任何安全检查.这通常是一个非常糟糕的主意,但如果两种类型具有相同的结构,它可以工作.所以我们可以使用它来byte[]转换为long[]:

bool CompareWithUnsafeLibrary(byte[] a1, byte[] a2)
{
    if (a1.Length != a2.Length) return false;

    var longSize = (int)Math.Floor(a1.Length / 8.0);
    var long1 = Unsafe.As(a1);
    var long2 = Unsafe.As(a2);

    for (var i = 0; i < longSize; i++)
    {
        if (long1[i] != long2[i]) return false;
    }

    for (var i = longSize * 8; i < a1.Length; i++)
    {
        if (a1[i] != a2[i]) return false;
    }

    return true;
}

请注意,它long1.Length仍会返回原始数组的长度,因为它存储在数组内存结构的字段中.

这个方法并不像这里演示的其他方法那么快,但它比朴素方法快得多,不使用不安全的代码或P/Invoke或pinning,并且实现非常简单(IMO).以下是我机器的一些BenchmarkDotNet结果:

BenchmarkDotNet=v0.10.3.0, OS=Microsoft Windows NT 6.2.9200.0
Processor=Intel(R) Core(TM) i7-4870HQ CPU 2.50GHz, ProcessorCount=8
Frequency=2435775 Hz, Resolution=410.5470 ns, Timer=TSC
  [Host]     : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1637.0
  DefaultJob : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1637.0

                 Method |          Mean |    StdDev |
----------------------- |-------------- |---------- |
          UnsafeLibrary |   125.8229 ns | 0.3588 ns |
          UnsafeCompare |    89.9036 ns | 0.8243 ns |
           JSharpEquals | 1,432.1717 ns | 1.3161 ns |
 EqualBytesLongUnrolled |    43.7863 ns | 0.8923 ns |
              NewMemCmp |    65.4108 ns | 0.2202 ns |
            ArraysEqual |   910.8372 ns | 2.6082 ns |
          PInvokeMemcmp |    52.7201 ns | 0.1105 ns |

我还创建了一个包含所有测试的要点.



11> Mr Anderson..:

我开发了一种略微跳动的方法memcmp()(基座的答案)和非常轻微的节拍EqualBytesLongUnrolled()(Arek Bulski的回答).基本上,它将循环展开4而不是8.

#if NETCOREAPP3_0
using System.Runtime.Intrinsics.X86;
#endif
…

public static unsafe bool Compare(byte[] arr0, byte[] arr1)
{
    if (arr0 == arr1)
    {
        return true;
    }
    if (arr0 == null || arr1 == null)
    {
        return false;
    }
    if (arr0.Length != arr1.Length)
    {
        return false;
    }
    if (arr0.Length == 0)
    {
        return true;
    }
    fixed (byte* b0 = arr0, b1 = arr1)
    {
#if NETCOREAPP3_0
        if (Avx2.IsSupported)
        {
            return Compare256(b0, b1, arr0.Length);
        }
        else if (Sse2.IsSupported)
        {
            return Compare128(b0, b1, arr0.Length);
        }
        else
#endif
        {
            return Compare64(b0, b1, arr0.Length);
        }
    }
}
#if NETCOREAPP3_0
public static unsafe bool Compare256(byte* b0, byte* b1, int length)
{
    byte* lastAddr = b0 + length;
    byte* lastAddrMinus128 = lastAddr - 128;
    const int mask = -1;
    while (b0 < lastAddrMinus128) // unroll the loop so that we are comparing 128 bytes at a time.
    {
        if (Avx2.MoveMask(Avx2.CompareEqual(Avx.LoadVector256(b0), Avx.LoadVector256(b1))) != mask)
        {
            return false;
        }
        if (Avx2.MoveMask(Avx2.CompareEqual(Avx.LoadVector256(b0 + 32), Avx.LoadVector256(b1 + 32))) != mask)
        {
            return false;
        }
        if (Avx2.MoveMask(Avx2.CompareEqual(Avx.LoadVector256(b0 + 64), Avx.LoadVector256(b1 + 64))) != mask)
        {
            return false;
        }
        if (Avx2.MoveMask(Avx2.CompareEqual(Avx.LoadVector256(b0 + 96), Avx.LoadVector256(b1 + 96))) != mask)
        {
            return false;
        }
        b0 += 128;
        b1 += 128;
    }
    while (b0 < lastAddr)
    {
        if (*b0 != *b1) return false;
        b0++;
        b1++;
    }
    return true;
}
public static unsafe bool Compare128(byte* b0, byte* b1, int length)
{
    byte* lastAddr = b0 + length;
    byte* lastAddrMinus64 = lastAddr - 64;
    const int mask = 0xFFFF;
    while (b0 < lastAddrMinus64) // unroll the loop so that we are comparing 64 bytes at a time.
    {
        if (Sse2.MoveMask(Sse2.CompareEqual(Sse2.LoadVector128(b0), Sse2.LoadVector128(b1))) != mask)
        {
            return false;
        }
        if (Sse2.MoveMask(Sse2.CompareEqual(Sse2.LoadVector128(b0 + 16), Sse2.LoadVector128(b1 + 16))) != mask)
        {
            return false;
        }
        if (Sse2.MoveMask(Sse2.CompareEqual(Sse2.LoadVector128(b0 + 32), Sse2.LoadVector128(b1 + 32))) != mask)
        {
            return false;
        }
        if (Sse2.MoveMask(Sse2.CompareEqual(Sse2.LoadVector128(b0 + 48), Sse2.LoadVector128(b1 + 48))) != mask)
        {
            return false;
        }
        b0 += 64;
        b1 += 64;
    }
    while (b0 < lastAddr)
    {
        if (*b0 != *b1) return false;
        b0++;
        b1++;
    }
    return true;
}
#endif
public static unsafe bool Compare64(byte* b0, byte* b1, int length)
{
    byte* lastAddr = b0 + length;
    byte* lastAddrMinus32 = lastAddr - 32;
    while (b0 < lastAddrMinus32) // unroll the loop so that we are comparing 32 bytes at a time.
    {
        if (*(ulong*)b0 != *(ulong*)b1) return false;
        if (*(ulong*)(b0 + 8) != *(ulong*)(b1 + 8)) return false;
        if (*(ulong*)(b0 + 16) != *(ulong*)(b1 + 16)) return false;
        if (*(ulong*)(b0 + 24) != *(ulong*)(b1 + 24)) return false;
        b0 += 32;
        b1 += 32;
    }
    while (b0 < lastAddr)
    {
        if (*b0 != *b1) return false;
        b0++;
        b1++;
    }
    return true;
}

memcmp()EqualBytesLongUnrolled()我的机器快约25%,速度快约5%.



12> gil..:

我会使用不安全的代码并运行for比较Int32指针的循环.

也许您还应该考虑将数组检查为非null.



13> Mikael Svens..:

如果你看一下.NET如何执行string.Equals,你会看到它使用一个名为EqualsHelper的私有方法,它有一个"不安全"的指针实现..NET Reflector是你的朋友,看看内部是如何完成的.

这可以用作字节数组比较的模板,我在博客文章C#中的快速字节数组比较中进行了实现.我还做了一些基本的基准测试,看看安全实施何时比不安全更快.

也就是说,除非你真的需要杀手性能,否则我会进行简单的fr循环比较.

推荐阅读
拾味湖
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有