Сравнение двух байтовых массивов в .NET.

Как я могу сделать это быстро?

Конечно, я могу это сделать:

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;
}

Но я ищу либо функцию здесь.

Ответ 1

Пользователь 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, по крайней мере, медленные или худшие.

Ответ 2

Вы можете использовать метод 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 по какой-либо причине, ваш метод в порядке.
Compiler\run-time environment оптимизирует ваш цикл, поэтому вам не нужно беспокоиться о производительности.

Ответ 3

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;
}

Ответ 4

В этом .NET 4.0 есть новое встроенное решение - IStructuralEquatable

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

Ответ 5

Если вы не против этого, вы можете импортировать сборку J # "vjslib.dll" и использовать ее Arrays.equals(byte [ ], byte [])...

Не обвиняйте меня, если кто-то смеется над вами, хотя...


EDIT: для чего это мало, я использовал 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;
}

Ответ 6

Span<T> предлагает чрезвычайно конкурентоспособную альтернативу без необходимости вводить запутанный и/или непереносимый пух в вашу собственную базу кода приложения:

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

Реализация (внутренности).NET Core 2.2.3 может быть найдена здесь.

Я пересмотрел суть @EliArbel, чтобы добавить этот метод как SpansEqual, отбросить большинство менее интересных исполнителей в тестах других, запустить его с различными размерами массива, вывести графики и пометить 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 не вышел на первое место для методов max-array-size, но разница настолько мала, что я не думаю, что это когда-либо будет иметь значение.

Информация о моей системе:

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

Ответ 7

.NET 3.5 и new имеют новый открытый тип System.Data.Linq.Binary, который инкапсулирует byte[]. Он реализует IEquatable<Binary>, который (по сути) сравнивает два байтовых массива. Обратите внимание, что 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:-)).

Вышеприведенная реализация означает, что в худшем случае вам придется трассировать массивы три раза: сначала вычислить хэш массива 1, затем вычислить хэш массива2 и, наконец, (поскольку это худший сценарий, длины и хэши равны ) для сравнения байтов в массиве 1 с байтами в массиве 2.

В целом, хотя System.Data.Linq.Binary встроен в BCL, я не думаю, что это самый быстрый способ сравнить два байтовых массива: - |.

Ответ 8

Я отправил аналогичный вопрос об проверке, если 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;
    }
}

Измеряется на двух массивах 256 Мбайт байт:

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

Ответ 9

 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");
 }

Ответ 10

Добавьте еще один!

Недавно Microsoft выпустила специальный пакет NuGet, System.Runtime.CompilerServices.Unsafe. Это особенное, потому что оно написано в IL и обеспечивает низкоуровневую функциональность, недоступную непосредственно на С#.

Один из его методов Unsafe.As<T>(object) позволяет лить любой ссылочный тип другому ссылочному типу, пропуская любые проверки безопасности. Обычно это плохая идея очень, но если оба типа имеют одинаковую структуру, она может работать. Поэтому мы можем использовать это для приведения a 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 все равно вернет исходную длину массива, поскольку он хранится в поле в структуре памяти массива.

Этот метод работает не так быстро, как другие методы, продемонстрированные здесь, но он намного быстрее, чем наивный метод, не использует небезопасный код или 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 со всеми тестами.

Ответ 11

Я разработал метод, который немного memcmp() (memcmp() ответ) и очень немного memcmp() (ответ Арека EqualBytesLongUnrolled()) на моем ПК. По сути, он развертывает цикл на 4 вместо 8.

Обновление 30 марта 2019 года:

Начиная с .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

Я бы использовал небезопасный код и запускал цикл for, сравнивающий указатели Int32.

Возможно, вам стоит также рассмотреть возможность проверки массивов на непустые.

Ответ 13

Если вы посмотрите, как .NET создает string.Equals, вы видите, что он использует частный метод под названием EqualsHelper, который имеет "небезопасную" реализацию указателя. .NET Reflector - ваш друг, чтобы увидеть, как все делается внутренне.

Это может использоваться как шаблон для сравнения массива байтов, который я сделал в блоге post Сравнение байтов с быстрым байтом в С#. Я также сделал некоторые рудиментарные тесты, чтобы увидеть, когда безопасная реализация быстрее, чем опасная.

Тем не менее, если вы действительно не нуждаетесь в производительности убийцы, я бы пошел на простое сравнение циклов fr.

Ответ 14

Не удалось найти решение. Я полностью доволен (разумная производительность, но не опасный код /​​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;
    }

Производительность по сравнению с некоторыми другими решениями на этой странице:

Простая петля: 19837 тиков, 1,00

* BitConverter: 4886 тиков, 4,06

UnsafeCompare: 1636 тиков, 12.12

EqualBytesLongUnrolled: 637 тиков, 31.09

P/Invoke memcmp: 369 тиков, 53.67

Протестировано в linqpad, 1000000 байт идентичных массивов (наихудший сценарий), 500 итераций каждый.

Ответ 15

Кажется, что EqualBytesLongUnrolled является лучшим из предложенного выше.

Пропущенные методы (Enumerable.SequenceEqual, StructuralComparisons.StructuralEqualityComparer.Equals), были не-пациента для замедления. На массивах 265 МБ я измерил это:

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 |

Ответ 16

Я провел некоторые измерения, используя прилагаемую программу .net 4.7 Release build без отладчика. Я думаю, что люди используют неправильную метрику, поскольку то, о чем вы говорите, если вы заботитесь о скорости, - это сколько времени нужно, чтобы выяснить, равны ли два байтовых массива. т.е. пропускная способность в байтах.

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 цикла является вторым лучшим вариантом. И это все еще поражает, почему Microsoft не может просто включить метод Buffer.Compare.

[Program.cs]:

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

Ответ 17

Я не видел много решений 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();
 }

Ответ 18

Для сравнения массивов коротких байтов интересен взлом:

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

Я думал о методах ускорения блочной передачи, встроенных во многие видеокарты. Но тогда вам придется копировать все данные побайтно, так что это не поможет вам, если вы не хотите реализовать целую часть своей логики в неуправляемом и аппаратно-зависимом коде...

Другим способом оптимизации, подобным описанному выше, было бы сохранить как можно больше ваших данных в длинном [], а не в байте [] с самого начала, например, если вы читаете его последовательно из бинарный файл, или если вы используете файл с отображением памяти, считаете данные длинными [] или одиночными длинными значениями. Тогда вашему циклу сравнения потребуется только 1/8 числа итераций, которые он должен был бы сделать для байта [], содержащего такой же объем данных. Речь идет о том, когда и как часто вам нужно сравнивать против того, когда и как часто вам нужно обращаться к данным побайтно, например, использовать его в вызове API как параметр в методе, который ожидает байта []. В конце концов, вы можете узнать, действительно ли вы знаете прецедент...

Ответ 20

Это почти наверняка намного медленнее, чем любая другая версия, приведенная здесь, но было весело писать.

static bool ByteArrayEquals(byte[] a1, byte[] a2) 
{
    return a1.Zip(a2, (l, r) => l == r).All(x => x);
}

Ответ 21

Я остановился на решении, основанном на методе EqualBytesLongUnrolled, опубликованном ArekBulski с дополнительной оптимизацией. В моем случае различия в массивах в массивах, как правило, близки к хвосту массивов. При тестировании я обнаружил, что когда это имеет место для больших массивов, возможность сравнить элементы массива в обратном порядке дает этому решению огромное увеличение производительности по сравнению с решением на основе memcmp. Вот это решение:

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

Извините, если вы ищете управляемый способ, который вы уже делаете правильно, и, насколько мне известно, в BCL нет встроенного метода для этого.

Вы должны добавить несколько начальных нулевых проверок, а затем просто повторно использовать их так, как если бы они были в BCL.

Ответ 23

Для тех из вас, кто заботится о порядке (т. memcmp чтобы ваш memcmp возвращал int как следует, а не ничего),.NET Core 3.0 (и, по-видимому,.NET Standard 2.1 или .NET 5.0) будет включать Span.SequenceCompareTo(...) метод расширения (плюс Span.SequenceEqualTo), который можно использовать для сравнения двух экземпляров ReadOnlySpan<T> (where T: IComparable<T>).

В первоначальном предложении GitHub обсуждение включало в себя сравнение подходов с вычислениями таблицы переходов, чтение byte[] как long[], использование SIMD и p/invoke для реализации CLR memcmp.

В дальнейшем это должен быть метод перехода для сравнения байтовых массивов или диапазонов байтов (как следует использовать Span<byte> вместо byte[] для API-интерфейсов .NET Standard 2.1), и он достаточно быстр, чтобы вам не приходилось больше не нужно его оптимизировать (и нет, несмотря на сходство в названии, оно не так ужасно, как ужасный 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

Ответ 24

Используйте SequenceEquals для сравнения.

Ответ 25

Короткий ответ таков:

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

Таким образом, вы можете использовать оптимизированную таблицу .NET для сравнения массива байтов без необходимости писать небезопасный код. Вот как это делается на фоне :

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

Ответ 26

Так как многие из причудливых решений выше не работают с 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);

Ответ 27

Вид грубой силы, но ее простая конвертация массива байтов в строку Base64 и сравнение двух строк. Удобно, если у вас есть большие массивы для сравнения. Или если один из байтовых массивов уже находится в формате Base64.

static bool ByteArrayCompare(byte[] a1, byte[] a2)
{
    string s1 = Convert.ToBase64String(a1);
    string s2 = Convert.ToBase64String(a2);
    if(s1 == s2) return true;
    return false
}

Я полагаю, что самый быстрый способ (с лучшей производительностью для больших массивов) состоит в хешировании как байтовых массивов, так и сравнении хешей.

Ответ 28

Если вы ищете очень быстрый сравнительный анализатор массива байтов, я предлагаю вам ознакомиться с этой статьей STSdb ​​Labs: Баланс сравнений равенства. Он содержит некоторые из самых быстрых реализаций для сравнения равенства массива byte [], которые представлены, проверены на производительность и суммированы.

Вы также можете сосредоточиться на этих реализациях:

BigEndianByteArrayComparer - быстрый байтовый [] сопоставление массива слева направо (BigEndian) BigEndianByteArrayEqualityComparer - - быстрый байт [] сравнительный коэффициент слева направо (BigEndian) LittleEndianByteArrayComparer - быстрый байтовый [] сопоставитель массива справа налево (LittleEndian) LittleEndianByteArrayEqualityComparer - быстрый байт [] равенство сравнения справа налево (LittleEndian)

Ответ 29

Если у вас огромный массив байтов, вы можете сравнить их, переведя их в строку.

Вы можете использовать что-то вроде

byte[] b1 = // Your array
byte[] b2 = // Your array
string s1 = Encoding.Default.GetString( b1 );
string s2 = Encoding.Default.GetString( b2 );

Я использовал это, и я видел огромное влияние на производительность.