Порівняння двох байтових масивів у .NET


541

Як я можу це зробити швидко?

Звичайно, я можу це зробити:

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

працює чудово, але це не виглядає так, як би це працювало для x64.

Зверніть увагу на мою надшвидку відповідь тут .


1
"Цей вид розраховує на те, що масиви починають вирівнювати qword." Це здорово, якщо. Ви повинні виправити код, щоб це відобразити.
Джо Чунг

4
повернути a1.Length == a2.Length &&! a1.Where ((t, i) => t! = a2 [i]). Будь-який ();
алерія

Мені сподобалась відповідь @OhadSchneider проIStructuralEquatable
LCJ

Відповіді:


613

Ви можете використовувати метод 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 з якихось причин, ваш метод у порядку.
Компілятор \ середовище виконання буде оптимізувати ваш цикл, тому вам не потрібно турбуватися про продуктивність.


4
Але чи не потрібно SequenceEqual обробляти більше часу, ніж небезпечне порівняння? Особливо, коли ти робиш 1000 порівнянь?
tcables

90
Так, це працює приблизно в 50 разів повільніше, ніж небезпечне порівняння.
Хафтор

27
Це дійсно воскресіння мертвих тут, але повільний - це справді погане слово, яке тут слід вживати. У 50 разів повільніше звучить погано, але не часто ви порівнюєте достатню кількість даних, щоб змінити значення, і якщо ви є, вам дійсно потрібно порівняти це для власної справи, з безлічі причин. Наприклад, зауважте, що творець небезпечної відповіді відзначає різницю в 7 разів повільніше, на відміну від 50-кратного повільніше (швидкість небезпечного методу також залежить від вирівнювання даних). У рідкісних випадках, коли ці числа мають значення, P / Invoke відбувається навіть швидше.
Selali Adobor

4
Тож повільніша реалізація отримує понад 300 лайків? Я б запропонував підключити msvcrt.dll, оскільки це був би найшвидший спосіб виконати роботу.
TGarrett

69
Найшвидший - це не найважливіше для бізнесу. Технічне обслуговування значно «швидше», ніж економія на цьому коді складе у 99% випадків. Я використовую SequenceEqual, і весь мій код <1ms. Ці мк, які ви економите, ніколи не додадуть до 5 хвилин відсутності читабельності P / Invoke.
PRMan

236

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

48
P / Invoke Yaay - це виявилося самим швидким на сьогоднішній день на растрові зображення , по крайней мере: stackoverflow.com/questions/2031217 / ...
Erik Forbes

25
Закріплення в цьому випадку не потрібно. Маршаллер виконує автоматичне закріплення під час виклику нативного коду за допомогою PInvoke. Довідка: stackoverflow.com/questions/2218444/…
Марк Глазго

14
P / Invoke може спричинити прискорення, але це, безумовно, найшвидший з усіх представлених рішень, включаючи реалізацію, яку я придумав, яка використовує небезпечні порівняння розміру вказівника. Існує кілька оптимізацій, які ви можете зробити, перш ніж звертатися до власного коду, включаючи рівність еталонів та порівняння першого та останнього елементів.
Джош

38
Чому бу? Плакат хотів швидкої реалізації і оптимізованого порівняння мови збірки неможливо перемогти. Я не знаю, як отримати "REPE CMPSD" з .NET без P / INVOKE.
Джейсон Гімаат

14
Nitpick: MSVCR.dll не повинен використовуватися кодом користувача. Щоб використовувати MSVCR, вам доведеться розподіляти час виконання за допомогою версії, яку ви поширюєте. ( msdn.microsoft.com/en-us/library/… та blogs.msdn.com/b/oldnewthing/archive/2014/04/11/10516280.aspx )
Мітч

160

Для цього в .NET 4 є нове вбудоване рішення - IStructuralEquatable

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

17
Відповідно до цієї публікації в блозі , це насправді дуже повільно.
Метт Джонсон-Пінт

48
Божевільний повільний. Приблизно на 180 разів повільніше, ніж просте для циклу.
Хафтор

Це працює, але я не розумію, чому. Байт [] - це примітивний тип, який не реалізує IStructuralEquatable, тому чому ви можете його передати - і неявний склад у цьому! І тоді метод інтерфейсу "Дорівнює" чарівно стає доступним ... звідки береться реалізація цього методу? Може хтось підкаже мене?
Джош

1
Чому б не просто StructuralComparisons.StructuralEqualityComparer.Equals(a1, a2). Тут немає NullReferenceException.
ta.speot.is

1
@ ta.speot.is Дякую, не можна сперечатися з одним вкладишем! Попереднє рішення було дещо ефективнішим, оскільки воно врятувало команду IStructuralEquatable (масив статично відомий як IStructuralEquatable), але, дійсно, ваші пропозиції змушують метод працювати і для нульових аргументів.
Охад Шнайдер

76

Користувач 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.
мммммммм

12
Нові дані тесту на випуск .NET 4 x64: IStructualEquatable.equals ~ на 180 разів повільніше, SequenceEqual на 15 разів повільніше, хеш SHA1 порівняно на 11 разів повільніше, бітконвертор ~ те саме, небезпечний на 7 разів швидше, пінвоке на 11 разів швидше. Досить круто, що небезпечно лише трохи повільніше, ніж P / Invoke на memcmp.
Хафтор

3
Це посилання дає добру інформацію про те, чому значення має вирівнювання пам’яті ibm.com/developerworks/library/pa-dalign - таким чином, оптимізацією можна було б перевірити вирівнювання, і якщо обидва масиви не вирівнюються на однакову кількість, виконайте байт порівняння, поки вони обидва на кордоні qword.
Хафтор

5
чи не дасть це значення false, коли і a1, і a2 є нульовими?
nawfal

2
@CristiDiaconescu Я переказав відповідь КевінаДрідджера. Що, мабуть, я повинен зробити, це зробити тестовий набір і мої результати доступні на github та посилання на нього у моїй відповіді.
Хафтор

73

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

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

В (кишках) реалізації як в .NET Ядро 3.1.0 можна знайти тут .

Я переглянув суть @ EliArbel, щоб додати цей метод як SpansEqual, опустити більшість менш цікавих виконавців у інших орієнтирах, запустити його з різними розмірами масивів, вивести графіки та позначити SpansEqualяк базову лінію, щоб він повідомляв про порівняння різних методів з SpansEqual.

Нижче наведені номери з результатів, злегка відредаговані для видалення стовпця "Помилка".

|        Method |  ByteCount |               Mean |            StdDev | Ratio |
|-------------- |----------- |-------------------:|------------------:|------:|
|    SpansEqual |         15 |           3.562 ns |         0.0035 ns |  1.00 |
|  LongPointers |         15 |           4.611 ns |         0.0028 ns |  1.29 |
|      Unrolled |         15 |          18.035 ns |         0.0195 ns |  5.06 |
| PInvokeMemcmp |         15 |          11.210 ns |         0.0353 ns |  3.15 |
|               |            |                    |                   |       |
|    SpansEqual |       1026 |          20.048 ns |         0.0286 ns |  1.00 |
|  LongPointers |       1026 |          63.347 ns |         0.1062 ns |  3.16 |
|      Unrolled |       1026 |          39.175 ns |         0.0304 ns |  1.95 |
| PInvokeMemcmp |       1026 |          40.830 ns |         0.0350 ns |  2.04 |
|               |            |                    |                   |       |
|    SpansEqual |    1048585 |      44,070.526 ns |        35.3348 ns |  1.00 |
|  LongPointers |    1048585 |      59,973.407 ns |        80.4145 ns |  1.36 |
|      Unrolled |    1048585 |      55,032.945 ns |        24.4745 ns |  1.25 |
| PInvokeMemcmp |    1048585 |      55,593.719 ns |        22.4301 ns |  1.26 |
|               |            |                    |                   |       |
|    SpansEqual | 2147483591 | 253,648,180.000 ns | 1,112,524.3074 ns |  1.00 |
|  LongPointers | 2147483591 | 249,412,064.286 ns | 1,079,409.5670 ns |  0.98 |
|      Unrolled | 2147483591 | 246,329,091.667 ns |   852,021.7992 ns |  0.97 |
| PInvokeMemcmp | 2147483591 | 247,795,940.000 ns | 3,390,676.3644 ns |  0.98 |

Я був здивований, побачивши, що SpansEqualне вийшов на перше місце для методів максимального масиву, але різниця настільки незначна, що я не думаю, що це колись матиме значення.

Інформація про мою систему:

BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18362
Intel Core i7-6850K CPU 3.60GHz (Skylake), 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=3.1.100
  [Host]     : .NET Core 3.1.0 (CoreCLR 4.700.19.56402, CoreFX 4.700.19.56404), X64 RyuJIT
  DefaultJob : .NET Core 3.1.0 (CoreCLR 4.700.19.56402, CoreFX 4.700.19.56404), X64 RyuJIT

Я ніколи не думав, що буду використовувати Span <T> або щось близьке до нього у всіх речах, які я роблю. Завдяки вам я зараз можу похвалитися цим своїм колегам.
jokab

Чи реалізується SequenceEqual як метод Span? Думав, що це лише один із методів розширення IEnumerable.
Застави

1
@Zastai так, {ReadOnly,}Span<T>має свою версію SequenceEqual(те саме ім'я, оскільки у нього є той самий контракт, що і відповідний IEnumerable<T>метод продовження, це просто швидше). Зауважте, що {ReadOnly,}Span<T>не можна використовувати IEnumerable<T>методи розширення через обмеження ref structтипів.
Джо Амента

1
@Sentinel Пакет System.Memory має "портативні" / "повільні" Span<T>реалізації для netstandard1.1і вище (тому пограйте з цим інтерактивним діаграмою, щоб побачити, що це таке). На сьогоднішній день "швидкий" Span<T>доступний лише у .NET Core 2.1, але зауважте, що SequenceEqual<T>конкретно має бути дуже мала різниця між "швидким" та "повільним" / "портативним" (хоча netstandard2.0цілі повинні побачити незначне поліпшення, оскільки вони мають векторний шлях коду).
Джо Амента

1
Установка-пакет system.memory
Кріс Москіні

30

Якщо ви не проти цього зробити, ви можете імпортувати збірку J # "vjslib.dll" і використовувати її метод Arrays.equals (байт [], байт []) ...

Не звинувачуй мене, якщо хтось з тебе сміється ...


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

25

.NET 3.5 і новіші мають новий загальнодоступний тип, System.Data.Linq.Binaryякий інкапсулює byte[]. Він реалізує, IEquatable<Binary>що (фактично) порівнює два байтові масиви. Зауважте, що System.Data.Linq.Binaryтакож має неявний оператор перетворення від byte[].

Документація MSDN: System.Data.Linq.Binary

Декомпіляція рефлекторного методу рівняння:

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, я не думаю, що це найшвидший спосіб порівняти два байтові масиви: - |.


20

Я розмістив аналогічне запитання щодо перевірки, чи байт [] повний нулів. (SIM-код був побитий, тому я видалив його з цієї відповіді.) Ось найшвидший код із моїх порівнянь:

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 небезпечний виклик.
ujeenator

1
@AmberdeBlack Ви впевнені? Ви тестували за допомогою крихітних масивів?
Зар Шардан

@ArekBulski Ви впевнені, що це швидше, ніж memcmp, тому що моє тестування показує інакше?
Зар Шардан

Я отримав практично однакові показники між цим і memcmp, тому +1 для повністю керованого рішення.
Майк Мариновський

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
Так, але вже згадується у прийнятій відповіді. btw, ви можете видалити там специфікацію типу.
nawfal

10

Додамо ще одну!

Нещодавно Microsoft випустила спеціальний пакет 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все-таки повернеться початкова довжина масиву, оскільки він зберігається в полі в структурі пам'яті масиву.

Цей метод не настільки швидкий, як інші методи, продемонстровані тут, але він набагато швидший, ніж наївний метод, не використовує небезпечний код, P / Invoke або фіксацію, і реалізація є досить простою (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 |

Я також створив суть усіх тестів .


Він не використовує небезпечне ключове слово, але він все одно називає небезпечний код за допомогою System.Runtime.CompilerServices.Unsafe
Paulo Zemek

Я оновив свою NewMemCmpвідповідь на використання AVX-2
містер Андерсон

8

Я розробив метод, який злегка б’є 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;
}

Мої вимірювання відрізняються .NET 462 може НЕТЦОР:
Мотлічек Петро,

Код виходить з ладу при порівнянні двох масивів 0 довжини, оскільки повернення повертається null.
Гленн Слейден

memcmp - це не просто порівняння власного капіталу. Він забезпечує інформацію, який об'єкт більший чи менший. Чи можете ви прийняти для цього свій алгоритм і перевірити працездатність?
nicolay.anykienko

Це швидше, ніж Spanі memcpy?
шовковий вогонь

@silkfire У .NET core 3 та сучасному процесорі він повинен бути в 2-3 рази швидшим для великих масивів.
Містер Андерсон

6

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

Можливо, вам слід також розглянути можливість перевірки масивів ненульовими.


5

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

Це може бути використано як шаблон для порівняння байтових масивів, який я здійснив у своїй публікації в блозі Швидке порівняння масиву байтів у C # . Я також зробив деякі рудиментарні орієнтири, щоб побачити, коли безпечна реалізація швидша, ніж небезпечна.

Однак, якщо вам справді не потрібна продуктивність вбивць, я б пішов на просте порівняння циклу fr.


3

Не вдалося знайти рішення, яким я повністю задоволений (розумна продуктивність, але немає небезпечного коду / пінвоке), тому я придумав це, нічого насправді оригінального, але працює:

    /// <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

EqualBytesLongUnroll: 637 тиків, 31.09

P / Invoke memcmp: 369 тиків, 53,67

Тестовано на linqpad, 1000000 байт однакових масивів (найгірший сценарій), 500 ітерацій кожен.


так, я зазначив, що в коментарі stackoverflow.com/a/1445280/4489, що моє тестування показує, це насправді трохи повільніше, ніж простий цикл, який я мав в оригінальному запитанні.
Хафтор

ти впевнений? У моєму тестуванні це в 4 рази швидше? Ніщо не перемагає старого доброго кодового коду, навіть із маршируючими накладними.
Зар Шардан

3

Здається, що EqualBytesLongUnroll є найкращим із запропонованого вище.

Пропущені методи (Unumerable.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 |

Я оновив свою NewMemCmpвідповідь щодо використання AVX-2
містер Андерсон

3

Я не бачив тут багатьох рішень 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();
 }

Вся суть питання - більш швидке рішення, яке функція розмістила в питанні.
CodesInChaos

3

Я зробив кілька вимірювань, використовуючи додану програму .net 4.7 випуску версії без доданого налагоджувача. Я думаю, що люди користуються невірною метрикою, оскільки те, про що ти йдеш, якщо ти турбуєшся про швидкість, - це скільки часу потрібно, щоб зрозуміти, чи рівні два байтові масиви. тобто пропускна здатність в байтах.

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

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

Тоді я б, мабуть, випав із рішення, переліченого у питанні.

Було б цікаво провести аналіз продуктивності цього коду.


int i = 0; for (; i <a1.Length-7; i + = 8), якщо (BitConverter.ToInt64 (a1, i)! = BitConverter.ToInt64 (a2, i)) повертає false; для (; i <a1.Length; i ++), якщо (a1 [i]! = a2 [i]) повернути помилкове; повернути правду; // трохи повільніше, ніж просте для циклу.
Хафтор

2

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

В оригінальній пропозиції GitHub обговорення включало порівняння підходів з обчисленнями таблиці стрибків, зчитуванням byte[]як long[], використання SIMD та p / викликом до реалізації 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

1

Я подумав про способи прискорення блокової передачі, вбудовані у багато відеокарт. Але тоді вам доведеться скопіювати всі дані в байтовій формі, так що це не дуже допоможе вам, якщо ви не хочете реалізувати цілу частину своєї логіки в некерованому та залежно від обладнання коді ...

Ще одним способом оптимізації, подібним до наведеного вище підходу, було б зберігати якомога більше своїх даних у довгий [], а не в байт [] з самого початку, наприклад, якщо ви читаєте їх послідовно з двійкового файлу, або якщо ви використовуєте файл, відображений у пам'яті, читайте дані як довгі [] або окремі довгі значення. Тоді для циклу порівняння знадобиться лише 1/8 числа кількості ітерацій, які він повинен зробити для байта [], що містить однаковий обсяг даних. Це питання про те, коли і як часто вам потрібно порівнювати порівняно, коли і як часто вам потрібно отримувати доступ до даних в байтовому порядку, наприклад, використовувати їх у виклику API як параметр у методі, який очікує байт []. Зрештою, ви можете лише сказати, чи справді ви знаєте випадок використання ...


Прийнята відповідь перетворює байтовий буфер як довгий буфер і порівнює його, як ви описуєте.
Хафтор

1

Це майже напевно набагато повільніше, ніж будь-яка інша версія, подана тут, але писати було весело.

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

1

Я зважився на рішення, натхнене методом EqualBytesLongUnroll, розміщеним АрекБульським, з додатковою оптимізацією. У моєму випадку відмінності масивів у масивах мають тенденцію бути біля хвоста масивів. Під час тестування я виявив, що коли це стосується великих масивів, можливість порівняння елементів масиву у зворотному порядку дає цьому рішенню величезний приріст продуктивності над рішенням на основі 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

Вибачте, якщо ви шукаєте керований спосіб, ви вже робите це правильно, наскільки мені відомо, в BCL немає вбудованого методу для цього.

Ви повинні додати декілька початкових нульових перевірок, а потім просто повторно використовувати його, як ніби це в BCL.


Ви мали рацію, коли писали, що в 2010 році (.NET 4.0) прийшов метод BCL, дивіться відповідь Охада Шнайдера. На момент запитання .NET 3.5 мав Linq (див. Відповідь Аку).
Jeppe Stig Nielsen

-1

Використовуйте SequenceEqualsдля цього для порівняння.


-2

Якщо ви шукаєте дуже швидкий порівняння рівності байтового масиву, пропоную вам поглянути на цю статтю Labs STSd: Порівняння рівності байтових масивів. У ньому представлені деякі з найшвидших реалізацій для порівняння рівності байтів [], які представлені, перевірені та узагальнені.

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

BigEndianByteArrayComparer - швидкий байт [] масив Comparer зліва направо (BigEndian) BigEndianByteArrayEqualityComparer - - швидкий байт [] рівність Comparer зліва направо (BigEndian) LittleEndianByteArrayComparer - швидкий байт [] масив Comparer справа наліво (LittleEndian) LittleEndianByteArrayEqualityComparer - швидкий байт [] порівняння рівності справа наліво (LittleEndian)


-2

Коротка відповідь така:

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

У моїх тестах перетворення на рядок знищує перевагу швидшого порівняння. Це було приблизно в 2,5 рази повільніше, ніж простий для циклу.
Doug Clutter

Коли я зробив те саме, простий був приблизно в 8 разів повільніше. Чи можете ви написати тут свій код?
Алон

1
Чи буде цей розрив, якщо байт містить нульове значення (0)?
Джозеф Леннокс

-1 Окрім того, що буде повільним через перетворення у рядок, як вказав @DougClutter, це не вдасться, якщо байтовий масив містить дані, що не містять ASCII. Для отримання правильного результату потрібно було б використовувати iso-8859-1.
Джо

2
Compare(new byte[]{128}, new byte[]{ 255 }) == trueзовсім не баггі ...
CodesInChaos

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

-1, оскільки цей код порушений і, мабуть, не перевірений. Це підказує IndexOutOfRangeExceptionпри порівнянні непустих масивів, оскільки ви отримуєте доступ до елементів 1через те, ba0.Lengthколи він повинен бути 0наскрізним ba0.Length - 1. Якщо ви зафіксуєте, що з Enumerable.Range(0, ba0.Length)ним все-таки неправильно повертається trueдля масивів однакової довжини, де відрізняються лише перші елементи, тому що ви не можете розрізняти перші задовольняючі елементи predicateта не задовольняти елементи predicate; FirstOrDefault<int>()повернення 0в обох випадках.
БАКОН

Урок тут, діти: не приносьте ножа в бій з пістолетом
Річард Хауер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.