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

406

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

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

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
    «Это своего рода рассчитывает на то, что массивы начинаются с выравнивания qword». Это большое, если. Вы должны исправить код, чтобы отразить это.
  • 4
    return a1.Length == a2.Length &&! a1.Where ((t, i) => t! = a2 [i]). Any ();
Показать ещё 1 комментарий
Теги:
arrays
performance
j#

28 ответов

64
Лучший ответ

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

  • 3
    Хорошее решение. Но один (маленький) совет: сравнение, если ссылки a1 и a2 равны, может ускорить процесс, если дать один и тот же массив для a1 и b1.
  • 0
    Кстати: пробовал приведение к Guid для 128-битного сравнения, но это заставляло его работать медленнее.
Показать ещё 15 комментариев
544

Вы можете использовать метод 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
    Но разве SequenceEqual не займет больше времени, чем небезопасное сравнение? Особенно, когда вы делаете тысячи сравнений?
  • 82
    Да, это работает примерно в 50 раз медленнее, чем небезопасное сравнение.
Показать ещё 8 комментариев
222

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;
}
  • 41
    P / Invoke ... буо ...
  • 45
    P / Invoke yaay - это оказалось самым быстрым, по крайней мере, для растровых изображений, по крайней мере: stackoverflow.com/questions/2031217/…
Показать ещё 15 комментариев
157

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

static bool ByteArrayCompare(byte[] a1, byte[] a2) 
{
    return StructuralComparisons.StructuralEqualityComparer.Equals(a1, a2);
}
  • 17
    Согласно этому сообщению в блоге, это на самом деле очень медленно.
  • 45
    Сумасшедший медленный Примерно в 180 раз медленнее, чем простой цикл.
Показать ещё 8 комментариев
25

.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, я не думаю, что это самый быстрый способ сравнить два байтовых массива: - |.

25

Если вы не против этого, вы можете импортировать сборку 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;
}
14

Я отправил аналогичный вопрос об проверке, если 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
  • 1
    Я подтверждаю. Я также провел тесты. Это быстрее, чем ответ, использующий небезопасный вызов memcmp.
  • 0
    @AmberdeBlack Вы уверены? Вы тестировали с крошечными массивами?
Показать ещё 3 комментария
12

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

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

Вариант (кишки) можно найти здесь.

Я переработал @EliArbel gist, чтобы добавить этот метод в SpansEqual, удалить большинство менее интересных исполнителей в тестах других, запустить его с разными размерами массива, выводить графики и отмечать SpansEqual в качестве базовой линии, чтобы он SpansEqual о том, как различные методы сравнить с SpansEqual.

Ниже приведены номера из результатов, слегка отредактированные, чтобы удалить столбец "Ошибка".

|        Method |  ByteCount |               Mean |         StdDev | Scaled |
|-------------- |----------- |-------------------:|---------------:|-------:|
|    SpansEqual |         15 |           3.614 ns |      0.0069 ns |   1.00 |
|  LongPointers |         15 |           4.762 ns |      0.0009 ns |   1.32 |
|      Unrolled |         15 |          16.933 ns |      0.0024 ns |   4.68 |
| PInvokeMemcmp |         15 |          11.448 ns |      0.0183 ns |   3.17 |
|               |            |                    |                |        |
|    SpansEqual |       1026 |          25.957 ns |      0.0081 ns |   1.00 |
|  LongPointers |       1026 |          60.336 ns |      0.0211 ns |   2.32 |
|      Unrolled |       1026 |          37.216 ns |      0.0042 ns |   1.43 |
| PInvokeMemcmp |       1026 |          43.531 ns |      0.0229 ns |   1.68 |
|               |            |                    |                |        |
|    SpansEqual |    1048585 |      42,708.279 ns |      6.7683 ns |   1.00 |
|  LongPointers |    1048585 |      57,952.010 ns |      6.0004 ns |   1.36 |
|      Unrolled |    1048585 |      52,768.967 ns |      5.1800 ns |   1.24 |
| PInvokeMemcmp |    1048585 |      53,270.846 ns |     11.9056 ns |   1.25 |
|               |            |                    |                |        |
|    SpansEqual | 2147483591 | 243,281,911.498 ns | 65,006.3172 ns |   1.00 |
|  LongPointers | 2147483591 | 237,786,969.675 ns | 96,332.7202 ns |   0.98 |
|      Unrolled | 2147483591 | 237,151,053.500 ns | 74,137.6513 ns |   0.97 |
| PInvokeMemcmp | 2147483591 | 235,829,644.641 ns | 50,390.2144 ns |   0.97 |

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

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

BenchmarkDotNet=v0.10.14, OS=Windows 10.0.17134
Intel Core i7-6850K CPU 3.60GHz (Skylake), 1 CPU, 12 logical and 6 physical cores
Frequency=3515619 Hz, Resolution=284.4449 ns, Timer=TSC
.NET Core SDK=2.1.300
  [Host]     : .NET Core 2.1.0 (CoreCLR 4.6.26515.07, CoreFX 4.6.26515.06), 64bit RyuJIT
  DefaultJob : .NET Core 2.1.0 (CoreCLR 4.6.26515.07, CoreFX 4.6.26515.06), 64bit RyuJIT
  • 0
    Я никогда не думал, что буду использовать Span <T> или что-то похожее во всем, что я делаю. Благодаря вам я теперь могу похвастаться этим перед моими сотрудниками.
  • 0
    SequenceEqual специально реализован как метод Span? Думал, что это был только один из методов расширения IEnumerable.
Показать ещё 6 комментариев
10
 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");
 }
  • 1
    Это то, что я использовал. Но это ... звучит как последовательное сравнение, которое вы иначе сделали бы, используя простой цикл, следовательно, не очень быстрый. Было бы неплохо отразить это и посмотреть, что на самом деле происходит. Судя по названию, ничего особенного.
  • 1
    Да, но уже упоминается в принятом ответе. Кстати, вы можете удалить спецификацию типа там.
7

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

Недавно 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 со всеми тестами.

  • 0
    Он не использует ключевое слово unsafe, но в любом случае вызывает небезопасный код с помощью System.Runtime.CompilerServices.Unsafe.
6

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

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

5

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

public static unsafe bool NewMemCmp(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;
}

public static unsafe bool NewMemCmp(byte[] arr0, byte[] arr1, int length)
{
    fixed (byte* b0 = arr0, b1 = arr1)
    {
        return b0 == b1 || NewMemCmp(b0, b1, length);
    }
}

Это работает на 25% быстрее, чем memcmp() и примерно на 5% быстрее, чем EqualBytesLongUnrolled() на моей машине.

  • 0
    Мои измерения отличаются .NET 462 может NETCORE:
  • 0
    Сбой кода при сравнении двух массивов null длины, потому что закрепление возвращает null .
Показать ещё 1 комментарий
5

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

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

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

3

Кажется, что 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 |
2

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

  • 0
    да, я заметил, что в комментарии stackoverflow.com/a/1445280/4489, что мое тестирование показывает, что это на самом деле немного медленнее, чем простой цикл for, который я имел в первоначальном вопросе.
  • 0
    уверены ли вы? В моем тестировании это в 4 раза быстрее? Ничто не сравнится со старым добрым нативным кодом, даже с использованием маршалинга.
2

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

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

Тогда я, вероятно, выпал бы на решение, указанное в вопросе.

Было бы интересно провести анализ производительности этого кода.

  • 0
    int i = 0; for (; i <a1.Length-7; i + = 8) if (BitConverter.ToInt64 (a1, i)! = BitConverter.ToInt64 (a2, i)) вернуть false; for (; i <a1.Length; i ++) if (a1 [i]! = a2 [i]) возвращает false; вернуть истину; // немного медленнее простого цикла for.
1

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

StructuralComparison :       2838.8 MiB/s
for                  :   30553811.0 MiB/s
ToUInt32             :   23864406.8 MiB/s
ToUInt64             :    5526595.7 MiB/s
memcmp               : 1848977556.1 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();

            var offset = Measure("offset", new TimeSpan(), () => { return; }, ignore: true);

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

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

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

  • 0
    Принятый ответ преобразует байтовый буфер в длинный буфер и сравнивает его, как вы описываете.
0

Я остановился на решении, основанном на методе 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;
}
0

Я не видел много решений 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();
 }
  • 0
    Весь смысл вопроса заключается в более быстром решении, чем функция, размещенная в вопросе.
0

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

static bool ByteArrayEquals(byte[] a1, byte[] a2) 
{
    return a1.Zip(a2, (l, r) => l == r).All(x => x);
}
  • 0
    Обратите внимание на 'l' в конце метода вместо ';'. Не могу изменить его, потому что вам нужно изменить как минимум шесть символов, и я не хочу переименовывать метод, а затем редактировать и переименовывать его обратно = /
  • 0
    @BrentRittenhouse Спасибо. (ТАК, очевидно, позволяет автору внести одну букву изменения)
0

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

    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);
    }
}
  • 0
    В моих тестах преобразование в строку уничтожает преимущество более быстрого сравнения. Это было примерно в 2,5 раза медленнее, чем простой цикл for.
  • 0
    Когда я делал то же самое, простой был примерно в 8 раз медленнее. Вы можете написать свой код здесь?
Показать ещё 4 комментария
0

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

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

  • 0
    Вы были правы, когда писали, что, однако, в 2010 году (.NET 4.0) появился метод BCL, см. Ответ Охада Шнайдера. Во время вопроса .NET 3.5 имел Linq (см. Ответ aku).
-1

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

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

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

-2

Вид грубой силы, но ее простая конвертация массива байтов в строку 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
}

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

-2

Так как многие из причудливых решений выше не работают с 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);
  • 0
    -1 потому что этот код не работает и явно не проверен. Это создает IndexOutOfRangeException при сравнении непустых массивов, потому что вы обращаетесь к элементам с 1 по ba0.Length когда оно должно быть от 0 до ba0.Length - 1 . Если вы исправить это Enumerable.Range(0, ba0.Length) он по- прежнему неправильно возвращает true для массивов одинаковой длиной , где только первые элементы отличаются , потому что вы не можете отличить первые элементы , удовлетворяющих predicate и без каких - либо элементов , удовлетворяющих predicate ; FirstOrDefault<int>() возвращает 0 в обоих случаях.
  • 0
    Урок здесь, дети: не приносите нож в перестрелку
-2

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

-5

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

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

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

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

  • 2
    Это может работать, а может и не работать, в зависимости от вашего локального строкового кодирования и того, какие кодовые точки он поддерживает.
  • 0
    Но поскольку здесь байтовый массив является независимым фактором, это не должно быть проблемой.
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню