代码之家  ›  专栏  ›  技术社区  ›  Hafthor

比较.NET中的两字节数组

  •  471
  • Hafthor  · 技术社区  · 17 年前

    我怎么能这么快?

    我当然可以做到:

    static bool ByteArrayCompare(byte[] a1, byte[] a2)
    {
        if (a1.Length != a2.Length)
            return false;
    
        for (int i=0; i<a1.Length; i++)
            if (a1[i]!=a2[i])
                return false;
    
        return true;
    }
    

    但我想找一个 BCL 功能或一些经过高度优化的行之有效的方法。

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

    注意我超快速的回答 here .

    28 回复  |  直到 9 年前
        1
  •  668
  •   aku    17 年前

    你可以用 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
    


    编译器\运行时环境将优化您的循环,因此您无需担心性能。

        2
  •  259
  •   Peter Mortensen Pieter Jan Bonestroo    11 年前

    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;
    }
    
        3
  •  160
  •   Ohad Schneider    12 年前

    IStructuralEquatable

    static bool ByteArrayCompare(byte[] a1, byte[] a2) 
    {
        return StructuralComparisons.StructuralEqualityComparer.Equals(a1, a2);
    }
    
        4
  •  123
  •   Joe Amenta    5 年前

    Span<T> 提供了极具竞争力的替代方案,而无需将混乱和/或不可移植的绒毛扔进您自己的应用程序代码库:

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

    可以找到从.NET5.0.0开始的实现(细节) here .

    我已经 revised SpansEqual ,删除其他基准测试中大多数不太感兴趣的执行者,使用不同的数组大小、输出图和标记运行它 斯潘塞科 斯潘塞科 .

    |        Method |  ByteCount |               Mean |            StdDev | Ratio | RatioSD |
    |-------------- |----------- |-------------------:|------------------:|------:|--------:|
    |    SpansEqual |         15 |           4.629 ns |         0.0289 ns |  1.00 |    0.00 |
    |  LongPointers |         15 |           4.598 ns |         0.0416 ns |  0.99 |    0.01 |
    |      Unrolled |         15 |          18.199 ns |         0.0291 ns |  3.93 |    0.02 |
    | PInvokeMemcmp |         15 |           9.872 ns |         0.0441 ns |  2.13 |    0.02 |
    |               |            |                    |                   |       |         |
    |    SpansEqual |       1026 |          19.965 ns |         0.0880 ns |  1.00 |    0.00 |
    |  LongPointers |       1026 |          63.005 ns |         0.5217 ns |  3.16 |    0.04 |
    |      Unrolled |       1026 |          38.731 ns |         0.0166 ns |  1.94 |    0.01 |
    | PInvokeMemcmp |       1026 |          40.355 ns |         0.0202 ns |  2.02 |    0.01 |
    |               |            |                    |                   |       |         |
    |    SpansEqual |    1048585 |      43,761.339 ns |        30.8744 ns |  1.00 |    0.00 |
    |  LongPointers |    1048585 |      59,585.479 ns |        17.3907 ns |  1.36 |    0.00 |
    |      Unrolled |    1048585 |      54,646.243 ns |        35.7638 ns |  1.25 |    0.00 |
    | PInvokeMemcmp |    1048585 |      55,198.289 ns |        23.9732 ns |  1.26 |    0.00 |
    |               |            |                    |                   |       |         |
    |    SpansEqual | 2147483591 | 240,607,692.857 ns | 2,733,489.4894 ns |  1.00 |    0.00 |
    |  LongPointers | 2147483591 | 238,223,478.571 ns | 2,033,769.5979 ns |  0.99 |    0.02 |
    |      Unrolled | 2147483591 | 236,227,340.000 ns | 2,189,627.0164 ns |  0.98 |    0.00 |
    | PInvokeMemcmp | 2147483591 | 238,724,660.000 ns | 3,726,140.4720 ns |  0.99 |    0.02 |
    

    斯潘塞科 对于最大数组大小的方法来说,这并不是最好的方法,但是差别太小了,我认为这根本不重要。

    BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
    Intel Core i7-6850K CPU 3.60GHz (Skylake), 1 CPU, 12 logical and 6 physical cores
    .NET Core SDK=5.0.100
      [Host]     : .NET Core 5.0.0 (CoreCLR 5.0.20.51904, CoreFX 5.0.20.51904), X64 RyuJIT
      DefaultJob : .NET Core 5.0.0 (CoreCLR 5.0.20.51904, CoreFX 5.0.20.51904), X64 RyuJIT
    
        5
  •  84
  •   Glenn Slayden    9 年前

    使用者 产生此解决方案的建议不安全代码:

    // 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#库与原始库相当 对于 环使用.SequenceEqual运行速度大约慢7倍;我认为这只是因为它正在使用IEnumerator.MoveNext。我认为基于LINQ的解决方案至少有那么慢或更糟。

        6
  •  31
  •   Jason Bunting    17 年前

    Arrays.equals(byte[], byte[]) method ...

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


    编辑:不管它值多少钱,我使用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
  •  26
  •   Milan Gardian    17 年前

    .NET 3.5及更新版本具有新的公共类型, System.Data.Linq.Binary byte[] . 它实现 IEquatable<Binary> 这(实际上)比较了两个字节数组。注意 还具有来自的隐式转换运算符 字节[] .

    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
  •  21
  •   ArekBulski    10 年前

    I posted 关于检查字节[]是否充满零的类似问题。(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
  •  11
  •   Eli Arbel    9 年前

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

    Unsafe.As<T>(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<long[]>(a1);
        var long2 = Unsafe.As<long[]>(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

    此方法不如本文演示的其他方法快,但它比naive方法快得多,不使用不安全的代码或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 |
    

    gist with all the tests

        10
  •  10
  •   user565710    15 年前
     using System.Linq; //SequenceEqual
    
     byte[] ByteArray1 = null;
     byte[] ByteArray2 = null;
    
     ByteArray1 = MyFunct1();
     ByteArray2 = MyFunct2();
    
     if (ByteArray1.SequenceEqual<byte>(ByteArray2) == true)
     {
        MessageBox.Show("Match");
     }
     else
     {
       MessageBox.Show("Don't match");
     }
    
        11
  •  9
  •   Mr Anderson    7 年前

    我发明了一种方法,可以稍微 memcmp() (普林斯的回答)和非常微弱的节奏 EqualBytesLongUnrolled() (阿雷克·布尔斯基的回答)在我的电脑上。基本上,它将循环展开4而不是8。

    更新日期:2019年3月30日

    从.NET core 3.0开始,我们支持SIMD!

    在我的电脑上,此解决方案以相当大的优势最快:

    #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;
    }
    
        12
  •  6
  •   Peter Mortensen Pieter Jan Bonestroo    11 年前

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

    也许你也应该考虑检查数组是非空的。

        13
  •  6
  •   Peter Mortensen Pieter Jan Bonestroo    11 年前

    如果您看看.NET是如何处理string.Equals的,您会发现它使用了一个名为EqualHelper的私有方法,该方法有一个“不安全”的指针实现。 .NET Reflector

    这可以用作字节数组比较的模板,我在博客文章中对其进行了实现 Fast byte array comparison in C#

        14
  •  5
  •   John Leidegren    7 年前

    我使用附加的程序.NET4.7发行版进行了一些测量,没有附加调试程序。我认为人们使用了错误的度量标准,因为如果你关心速度的话,这里的问题是计算两个字节数组是否相等需要多长时间。i、 e.以字节为单位的吞吐量。

    StructuralComparison :              4.6 MiB/s
    for                  :            274.5 MiB/s
    ToUInt32             :            263.6 MiB/s
    ToUInt64             :            474.9 MiB/s
    memcmp               :           8500.8 MiB/s
    

    正如你所见,没有比这更好的方法了 memcmp for 循环是第二个最好的选择。我仍然很奇怪为什么微软不能简单地包括一个 Buffer.Compare 方法

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Runtime.InteropServices;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace memcmp
    {
        class Program
        {
            static byte[] TestVector(int size)
            {
                var data = new byte[size];
                using (var rng = new System.Security.Cryptography.RNGCryptoServiceProvider())
                {
                    rng.GetBytes(data);
                }
                return data;
            }
    
            static TimeSpan Measure(string testCase, TimeSpan offset, Action action, bool ignore = false)
            {
                var t = Stopwatch.StartNew();
                var n = 0L;
                while (t.Elapsed < TimeSpan.FromSeconds(10))
                {
                    action();
                    n++;
                }
                var elapsed = t.Elapsed - offset;
                if (!ignore)
                {
                    Console.WriteLine($"{testCase,-16} : {n / elapsed.TotalSeconds,16:0.0} MiB/s");
                }
                return elapsed;
            }
    
            [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
            static extern int memcmp(byte[] b1, byte[] b2, long count);
    
            static void Main(string[] args)
            {
                // how quickly can we establish if two sequences of bytes are equal?
    
                // note that we are testing the speed of different comparsion methods
    
                var a = TestVector(1024 * 1024); // 1 MiB
                var b = (byte[])a.Clone();
    
                // was meant to offset the overhead of everything but copying but my attempt was a horrible mistake... should have reacted sooner due to the initially ridiculous throughput values...
                // Measure("offset", new TimeSpan(), () => { return; }, ignore: true);
                var offset = TimeZone.Zero
    
                Measure("StructuralComparison", offset, () =>
                {
                    StructuralComparisons.StructuralEqualityComparer.Equals(a, b);
                });
    
                Measure("for", offset, () =>
                {
                    for (int i = 0; i < a.Length; i++)
                    {
                        if (a[i] != b[i]) break;
                    }
                });
    
                Measure("ToUInt32", offset, () =>
                {
                    for (int i = 0; i < a.Length; i += 4)
                    {
                        if (BitConverter.ToUInt32(a, i) != BitConverter.ToUInt32(b, i)) break;
                    }
                });
    
                Measure("ToUInt64", offset, () =>
                {
                    for (int i = 0; i < a.Length; i += 8)
                    {
                        if (BitConverter.ToUInt64(a, i) != BitConverter.ToUInt64(b, i)) break;
                    }
                });
    
                Measure("memcmp", offset, () =>
                {
                    memcmp(a, b, a.Length);
                });
            }
        }
    }
    
        15
  •  4
  •   Zar Shardan    10 年前

    找不到一个我完全满意的解决方案(性能合理,但没有不安全的代码/pinvoke),所以我提出了这个解决方案,没有什么真正的原创,但很有效:

        /// <summary>
        /// 
        /// </summary>
        /// <param name="array1"></param>
        /// <param name="array2"></param>
        /// <param name="bytesToCompare"> 0 means compare entire arrays</param>
        /// <returns></returns>
        public static bool ArraysEqual(byte[] array1, byte[] array2, int bytesToCompare = 0)
        {
            if (array1.Length != array2.Length) return false;
    
            var length = (bytesToCompare == 0) ? array1.Length : bytesToCompare;
            var tailIdx = length - length % sizeof(Int64);
    
            //check in 8 byte chunks
            for (var i = 0; i < tailIdx; i += sizeof(Int64))
            {
                if (BitConverter.ToInt64(array1, i) != BitConverter.ToInt64(array2, i)) return false;
            }
    
            //check the remainder of the array, always shorter than 8 bytes
            for (var i = tailIdx; i < length; i++)
            {
                if (array1[i] != array2[i]) return false;
            }
    
            return true;
        }
    

    与本页上的其他一些解决方案相比,性能:

    *位转换器:4886个刻度,4.06

    不完美比较:1636勾,12.12

    等长展开时间:637节,31.09

    P/memcmp:369个刻度,53.67

        16
  •  4
  •   Motlicek Petr    9 年前

    看来 等长展开 是以上建议中最好的。

    Host Process Environment Information:
    BenchmarkDotNet.Core=v0.9.9.0
    OS=Microsoft Windows NT 6.2.9200.0
    Processor=Intel(R) Core(TM) i7-3770 CPU 3.40GHz, ProcessorCount=8
    Frequency=3323582 ticks, Resolution=300.8802 ns, Timer=TSC
    CLR=MS.NET 4.0.30319.42000, Arch=64-bit RELEASE [RyuJIT]
    GC=Concurrent Workstation
    JitModules=clrjit-v4.6.1590.0
    
    Type=CompareMemoriesBenchmarks  Mode=Throughput  
    
                     Method |      Median |    StdDev | Scaled | Scaled-SD |
    ----------------------- |------------ |---------- |------- |---------- |
                 NewMemCopy |  30.0443 ms | 1.1880 ms |   1.00 |      0.00 |
     EqualBytesLongUnrolled |  29.9917 ms | 0.7480 ms |   0.99 |      0.04 |
              msvcrt_memcmp |  30.0930 ms | 0.2964 ms |   1.00 |      0.03 |
              UnsafeCompare |  31.0520 ms | 0.7072 ms |   1.03 |      0.04 |
           ByteArrayCompare | 212.9980 ms | 2.0776 ms |   7.06 |      0.25 |
    

    OS=Windows
    Processor=?, ProcessorCount=8
    Frequency=3323582 ticks, Resolution=300.8802 ns, Timer=TSC
    CLR=CORE, Arch=64-bit ? [RyuJIT]
    GC=Concurrent Workstation
    dotnet cli version: 1.0.0-preview2-003131
    
    Type=CompareMemoriesBenchmarks  Mode=Throughput  
    
                     Method |      Median |    StdDev | Scaled | Scaled-SD |
    ----------------------- |------------ |---------- |------- |---------- |
                 NewMemCopy |  30.1789 ms | 0.0437 ms |   1.00 |      0.00 |
     EqualBytesLongUnrolled |  30.1985 ms | 0.1782 ms |   1.00 |      0.01 |
              msvcrt_memcmp |  30.1084 ms | 0.0660 ms |   1.00 |      0.00 |
              UnsafeCompare |  31.1845 ms | 0.4051 ms |   1.03 |      0.01 |
           ByteArrayCompare | 212.0213 ms | 0.1694 ms |   7.03 |      0.01 |
    
        17
  •  4
  •   Simon Opelt    6 年前

    对于那些关心秩序的人(即想要 memcmp int 就像它应该有的那样,.NETCore3.0(大概是.NET标准2.1,也就是.NET5.0) will include a Span.SequenceCompareTo(...) extension method (加上 Span.SequenceEqualTo )这可以用来比较两种情况 ReadOnlySpan<T> where T: IComparable<T> ).

    在里面 the original GitHub proposal byte[] long[] ,SIMD用法,以及CLR实现的 memcmp .

    接下来,这应该是比较字节数组或字节范围的go-to方法(使用 Span<byte> 字节[] 对于您的.NET标准2.1 API),并且它的速度足够快,您不应该再关心优化它(而且不,尽管名称上有相似之处,但它的性能没有可怕的API那么糟糕 Enumerable.SequenceEqual

    #if NETCOREAPP3_0
    // Using the platform-native Span<T>.SequenceEqual<T>(..)
    public static int Compare(byte[] range1, int offset1, byte[] range2, int offset2, int count)
    {
        var span1 = range1.AsSpan(offset1, count);
        var span2 = range2.AsSpan(offset2, count);
    
        return span1.SequenceCompareTo(span2);
        // or, if you don't care about ordering
        // return span1.SequenceEqual(span2);
    }
    #else
    // The most basic implementation, in platform-agnostic, safe C#
    public static bool Compare(byte[] range1, int offset1, byte[] range2, int offset2, int count)
    {
        // Working backwards lets the compiler optimize away bound checking after the first loop
        for (int i = count - 1; i >= 0; --i)
        {
            if (range1[offset1 + i] != range2[offset2 + i])
            {
                return false;
            }
        }
    
        return true;
    }
    #endif
    
        18
  •  2
  •   Kevin Driedger    16 年前

    对于比较短字节数组,以下是一个有趣的技巧:

    if(myByteArray1.Length != myByteArray2.Length) return false;
    if(myByteArray1.Length == 8)
       return BitConverter.ToInt64(myByteArray1, 0) == BitConverter.ToInt64(myByteArray2, 0); 
    else if(myByteArray.Length == 4)
       return BitConverter.ToInt32(myByteArray2, 0) == BitConverter.ToInt32(myByteArray2, 0); 
    

    对这段代码进行性能分析会很有趣。

        19
  •  2
  •   Zapnologica    8 年前

    我在这里没有看到很多linq解决方案。

    linq 根据经验,然后在必要时进行优化。

    public bool CompareTwoArrays(byte[] array1, byte[] array2)
     {
       return !array1.Where((t, i) => t != array2[i]).Any();
     }
    

    请注意,这仅适用于大小相同的阵列。 扩展可能是这样的

    public bool CompareTwoArrays(byte[] array1, byte[] array2)
     {
       if (array1.Length != array2.Length) return false;
       return !array1.Where((t, i) => t != array2[i]).Any();
     }
    
        20
  •  1
  •   Mirko Klemm    17 年前

    我想到了许多图形卡中内置的块传输加速方法。但是,您必须按字节复制所有数据,因此如果您不想在非托管和硬件相关的代码中实现整个逻辑部分,这对您没有多大帮助。。。

    这是一个需要比较的时间和频率与需要以逐字节方式访问数据的时间和频率的问题,例如,在API调用中将其用作需要字节[]的方法中的参数。最后,您只能知道您是否真正了解用例。。。

        21
  •  1
  •   Casey Chester    8 年前

    public enum CompareDirection { Forward, Backward }
    
    private static unsafe bool UnsafeEquals(byte[] a, byte[] b, CompareDirection direction = CompareDirection.Forward)
    {
        // returns when a and b are same array or both null
        if (a == b) return true;
    
        // if either is null or different lengths, can't be equal
        if (a == null || b == null || a.Length != b.Length)
            return false;
    
        const int UNROLLED = 16;                // count of longs 'unrolled' in optimization
        int size = sizeof(long) * UNROLLED;     // 128 bytes (min size for 'unrolled' optimization)
        int len = a.Length;
        int n = len / size;         // count of full 128 byte segments
        int r = len % size;         // count of remaining 'unoptimized' bytes
    
        // pin the arrays and access them via pointers
        fixed (byte* pb_a = a, pb_b = b)
        {
            if (r > 0 && direction == CompareDirection.Backward)
            {
                byte* pa = pb_a + len - 1;
                byte* pb = pb_b + len - 1;
                byte* phead = pb_a + len - r;
                while(pa >= phead)
                {
                    if (*pa != *pb) return false;
                    pa--;
                    pb--;
                }
            }
    
            if (n > 0)
            {
                int nOffset = n * size;
                if (direction == CompareDirection.Forward)
                {
                    long* pa = (long*)pb_a;
                    long* pb = (long*)pb_b;
                    long* ptail = (long*)(pb_a + nOffset);
                    while (pa < ptail)
                    {
                        if (*(pa + 0) != *(pb + 0) || *(pa + 1) != *(pb + 1) ||
                            *(pa + 2) != *(pb + 2) || *(pa + 3) != *(pb + 3) ||
                            *(pa + 4) != *(pb + 4) || *(pa + 5) != *(pb + 5) ||
                            *(pa + 6) != *(pb + 6) || *(pa + 7) != *(pb + 7) ||
                            *(pa + 8) != *(pb + 8) || *(pa + 9) != *(pb + 9) ||
                            *(pa + 10) != *(pb + 10) || *(pa + 11) != *(pb + 11) ||
                            *(pa + 12) != *(pb + 12) || *(pa + 13) != *(pb + 13) ||
                            *(pa + 14) != *(pb + 14) || *(pa + 15) != *(pb + 15)
                        )
                        {
                            return false;
                        }
                        pa += UNROLLED;
                        pb += UNROLLED;
                    }
                }
                else
                {
                    long* pa = (long*)(pb_a + nOffset);
                    long* pb = (long*)(pb_b + nOffset);
                    long* phead = (long*)pb_a;
                    while (phead < pa)
                    {
                        if (*(pa - 1) != *(pb - 1) || *(pa - 2) != *(pb - 2) ||
                            *(pa - 3) != *(pb - 3) || *(pa - 4) != *(pb - 4) ||
                            *(pa - 5) != *(pb - 5) || *(pa - 6) != *(pb - 6) ||
                            *(pa - 7) != *(pb - 7) || *(pa - 8) != *(pb - 8) ||
                            *(pa - 9) != *(pb - 9) || *(pa - 10) != *(pb - 10) ||
                            *(pa - 11) != *(pb - 11) || *(pa - 12) != *(pb - 12) ||
                            *(pa - 13) != *(pb - 13) || *(pa - 14) != *(pb - 14) ||
                            *(pa - 15) != *(pb - 15) || *(pa - 16) != *(pb - 16)
                        )
                        {
                            return false;
                        }
                        pa -= UNROLLED;
                        pb -= UNROLLED;
                    }
                }
            }
    
            if (r > 0 && direction == CompareDirection.Forward)
            {
                byte* pa = pb_a + len - r;
                byte* pb = pb_b + len - r;
                byte* ptail = pb_a + len;
                while(pa < ptail)
                {
                    if (*pa != *pb) return false;
                    pa++;
                    pb++;
                }
            }
        }
    
        return true;
    }
    
        22
  •  0
  •   Markus Olsson    17 年前

    抱歉,如果您正在寻找一种管理方式,那么您已经正确地执行了,据我所知,BCL中没有内置的方法来执行此操作。

    您应该添加一些初始的空检查,然后像在BCL中一样重用它。

        23
  •  0
  •   James Curran    8 年前

    static bool ByteArrayEquals(byte[] a1, byte[] a2) 
    {
        return a1.Zip(a2, (l, r) => l == r).All(x => x);
    }
    
        24
  •  0
  •   Antidisestablishmentarianism    4 年前

    这与其他方法类似,但这里的区别在于,我不能一次检查下一个最高的字节数,例如,如果我有63个字节(在我的SIMD示例中),我可以检查前32个字节的相等性,然后检查最后32个字节的相等性,这比检查32个字节、16个字节、8个字节等都快。输入的第一个检查是比较所有字节所需的唯一检查。

    在我的测试中,这确实排在第一位,但只差一点点。

    下面的代码正是我在airbreather/ArrayComparePerf.cs中测试它的方式。

    public unsafe bool SIMDNoFallThrough()    #requires  System.Runtime.Intrinsics.X86
    {
        if (a1 == null || a2 == null)
            return false;
    
        int length0 = a1.Length;
    
        if (length0 != a2.Length) return false;
    
        fixed (byte* b00 = a1, b01 = a2)
        {
            byte* b0 = b00, b1 = b01, last0 = b0 + length0, last1 = b1 + length0, last32 = last0 - 31;
    
            if (length0 > 31)
            {
                while (b0 < last32)
                {
                    if (Avx2.MoveMask(Avx2.CompareEqual(Avx.LoadVector256(b0), Avx.LoadVector256(b1))) != -1)
                        return false;
                    b0 += 32;
                    b1 += 32;
                }
                return Avx2.MoveMask(Avx2.CompareEqual(Avx.LoadVector256(last0 - 32), Avx.LoadVector256(last1 - 32))) == -1;
            }
    
            if (length0 > 15)
            {
                if (Sse2.MoveMask(Sse2.CompareEqual(Sse2.LoadVector128(b0), Sse2.LoadVector128(b1))) != 65535)
                    return false;
                return Sse2.MoveMask(Sse2.CompareEqual(Sse2.LoadVector128(last0 - 16), Sse2.LoadVector128(last1 - 16))) == 65535;
            }
    
            if (length0 > 7)
            {
                if (*(ulong*)b0 != *(ulong*)b1)
                    return false;
                return *(ulong*)(last0 - 8) == *(ulong*)(last1 - 8);
            }
    
            if (length0 > 3)
            {
                if (*(uint*)b0 != *(uint*)b1)
                    return false;
                return *(uint*)(last0 - 4) == *(uint*)(last1 - 4);
            }
    
            if (length0 > 1)
            {
                if (*(ushort*)b0 != *(ushort*)b1)
                    return false;
                return *(ushort*)(last0 - 2) == *(ushort*)(last1 - 2);
            }
    
            return *b0 == *b1;
        }
    }
    

    public unsafe bool LongPointersNoFallThrough()
    {
        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;
            if ((l & 8) != 0)
            {
                for (int i = 0; i < l / 8; i++, x1 += 8, x2 += 8)
                    if (*(long*)x1 != *(long*)x2) return false;
                return *(long*)(x1 + (l - 8)) == *(long*)(x2 + (l - 8));
            }
            if ((l & 4) != 0)
            {
                if (*(int*)x1 != *(int*)x2) return false; x1 += 4; x2 += 4;
                return *(int*)(x1 + (l - 4)) == *(int*)(x2 + (l - 4));
            }
            if ((l & 2) != 0)
            {
                if (*(short*)x1 != *(short*)x2) return false; x1 += 2; x2 += 2;
                return *(short*)(x1 + (l - 2)) == *(short*)(x2 + (l - 2));
            }
            return *x1 == *x2;
        }
    }
    
        25
  •  -1
  •   Magnilex BesaFX    11 年前

    使用 SequenceEquals

        26
  •  -2
  •   Kristiyan Dimitrov    12 年前

    如果您正在寻找一个非常快速的字节数组相等比较器,我建议您阅读这篇STSdb实验室文章: Byte array equality comparer. 它提供了一些字节[]数组相等比较的最快实现,并对这些实现进行了介绍、性能测试和总结。

    您还可以关注这些实现:

    BigEndianByteArrayComparer -从左到右的快速字节[]数组比较器(BigEndian) BigEndianByteArrayEqualityComparer LittleEndianByteArrayComparer LittleEndianByteArrayEqualityComparer

        27
  •  -2
  •   Peter Mortensen Pieter Jan Bonestroo    11 年前

    简单的回答是:

        public bool Compare(byte[] b1, byte[] b2)
        {
            return Encoding.ASCII.GetString(b1) == Encoding.ASCII.GetString(b2);
        }
    

    通过这种方式,您可以使用优化的.NET字符串比较来进行字节数组比较,而无需编写不安全的代码。这是如何做到这一点的 background :

    private unsafe static bool EqualsHelper(String strA, String strB)
    {
        Contract.Requires(strA != null);
        Contract.Requires(strB != null);
        Contract.Requires(strA.Length == strB.Length);
    
        int length = strA.Length;
    
        fixed (char* ap = &strA.m_firstChar) fixed (char* bp = &strB.m_firstChar)
        {
            char* a = ap;
            char* b = bp;
    
            // Unroll the loop
    
            #if AMD64
                // For the AMD64 bit platform we unroll by 12 and
                // check three qwords at a time. This is less code
                // than the 32 bit case and is shorter
                // pathlength.
    
                while (length >= 12)
                {
                    if (*(long*)a     != *(long*)b)     return false;
                    if (*(long*)(a+4) != *(long*)(b+4)) return false;
                    if (*(long*)(a+8) != *(long*)(b+8)) return false;
                    a += 12; b += 12; length -= 12;
                }
           #else
               while (length >= 10)
               {
                   if (*(int*)a != *(int*)b) return false;
                   if (*(int*)(a+2) != *(int*)(b+2)) return false;
                   if (*(int*)(a+4) != *(int*)(b+4)) return false;
                   if (*(int*)(a+6) != *(int*)(b+6)) return false;
                   if (*(int*)(a+8) != *(int*)(b+8)) return false;
                   a += 10; b += 10; length -= 10;
               }
           #endif
    
            // This depends on the fact that the String objects are
            // always zero terminated and that the terminating zero is not included
            // in the length. For odd string sizes, the last compare will include
            // the zero terminator.
            while (length > 0)
            {
                if (*(int*)a != *(int*)b) break;
                a += 2; b += 2; length -= 2;
            }
    
            return (length <= 0);
        }
    }
    
        28
  •  -2
  •   Raymond Osterbrink    8 年前

    由于上述许多奇特的解决方案不适用于UWP,并且因为我喜欢Linq和函数方法,我向您介绍了我的版本来解决这个问题。 为了在出现第一个差异时避免比较,我选择了.FirstOrDefault()

    public static bool CompareByteArrays(byte[] ba0, byte[] ba1) =>
        !(ba0.Length != ba1.Length || Enumerable.Range(1,ba0.Length)
            .FirstOrDefault(n => ba0[n] != ba1[n]) > 0);