Сравнение двух байтовых массивов в .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;
}

Но я ищу либо функцию BCL, либо какой-нибудь хорошо оптимизированный проверенный способ сделать это.

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

работает хорошо, но не похоже, что это сработает для x64.

Обратите внимание на мой сверхбыстрый ответ здесь.

«Это вроде как рассчитывается на тот факт, что массивы начинают выровнены по qword». Это большое «если». Вы должны исправить код, чтобы отразить это.

Joe Chung 10.08.2009 00:45

return a1.Length == a2.Length &&! a1.Where ((t, i) => t! = a2 [i]). Any ();

alerya 31.08.2012 16:10
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
576
2
340 533
27
Перейти к ответу Данный вопрос помечен как решенный

Ответы 27

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

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

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

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

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

Jeppe Stig Nielsen 13.07.2016 12:04

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

Но не вините меня, если над вами смеются ...


Обновлено: Как бы то ни было, я использовал Reflector для дизассемблирования кода для этого, и вот как это выглядит:

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

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

Но разве обработка SequenceEqual не занимает больше времени, чем небезопасное сравнение? Особенно, когда вы проводите тысячи сравнений?

tcables 20.01.2011 21:18

Да, это примерно в 50 раз медленнее, чем небезопасное сравнение.

Hafthor 03.02.2011 05:53

Спасибо за сравнение производительности - для меня это проясняет решение - SequenceEqual безопасен, но в этом случае медленный.

UGEEN 04.08.2014 11:31

Здесь действительно воскрешают мертвых, но «медленно» - плохое слово здесь. В 50 раз медленнее звуки - плохо, но не часто вы сравниваете достаточно данных, чтобы иметь значение, и если да, вам действительно нужно сравнить это для вашего собственного случая по множеству причин. Например, обратите внимание, что создатель небезопасного ответа отмечает разницу в 7 раз медленнее, чем в 50 раз (скорость небезопасного метода также зависит от выравнивания данных). В редких случаях, когда эти числа имеют значение, P / Invoke работает еще быстрее.

Selali Adobor 18.09.2014 02:47

Значит, более медленная реализация набирает более 300 лайков? Я бы посоветовал подключить msvcrt.dll, поскольку это был бы самый быстрый способ выполнить работу.

TGarrett 14.05.2015 16:49

Самый быстрый - не самое главное для бизнеса. Ремонтопригодность намного «быстрее», чем экономия на этом коде в 99% случаев. Я использую SequenceEqual, и весь мой код составляет <1 мс. Те мкс, которые вы сохраняете, никогда не добавят 5 минут отсутствия удобочитаемости P / Invoke.

PRMan 26.09.2015 02:54

@PRMan Я понимаю вашу точку зрения, но не полностью согласен с ней. Вы редко возвращаетесь к той части кода, в которой уверены, что она работает, и снова пытаетесь ее понять. То же и с вашими коллегами. Комментирования такой части кода с помощью чего-то вроде / * очень хорошо протестированного и быстрого метода сравнения массивов * / в значительной степени достаточно, и это не повредит вашему бизнесу. с другой стороны, попытка оптимизировать приложение, в котором программист проявлял небрежность на протяжении всей разработки ... он в основном просматривает все части кода;)

Żubrówka 21.11.2016 19:17

Скорость также действительно зависит от размера данных и от того, как часто вы это делаете. Я сравниваю байтовый массив с примерно 10 байтами в нем вне цикла. Если бы я пытался сравнить растровые изображения размером 10 МБ, возможно, стоило бы изучить решение p / invoke. Преждевременная оптимизация и микрооптимизация могут быть убийственными

JoshBerke 21.06.2018 17:54

Очевидно, что метод P / Invoke также не является переносимым; это. Время, которое я потратил на написание этого предложения, намного превышает экономию времени при использовании P / Invoke для этой цели в нашем приложении, не говоря уже о переносимости.

Corrodias 16.03.2019 03:23

Как этот метод сравнивается с производительностью цикла for? Медленнее, чем pinvoke, будет дано, но если он быстрее, чем цикл for, это не победа

Goku 05.11.2019 20:22

.NET 3.5 и новее имеют новый общедоступный тип 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 :-)).

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

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

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

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

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

Hafthor 18.12.2012 21:18

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

Это можно использовать в качестве шаблона для сравнения байтовых массивов, реализацию которого я реализовал в сообщении блога Быстрое сравнение байтовых массивов в C#. Я также провел несколько элементарных тестов, чтобы увидеть, когда безопасная реализация быстрее небезопасной.

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

Вот интересный прием для сравнения коротких байтовых массивов:

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 я = 0; for (; i <a1.Length-7; i + = 8) if (BitConverter.ToInt64 (a1, i)! = BitConverter.ToInt64 (a2, i)) return false; for (; i <a1.Length; i ++) if (a1 [i]! = a2 [i]) return false; вернуть истину; // немного медленнее, чем простой цикл for.

Hafthor 21.12.2012 09:49

P / Invoke полномочия активируются!

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

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

P / Invoke yaay - по крайней мере, это оказалось самым быстрым на растровых изображениях: stackoverflow.com/questions/2031217/…

Erik Forbes 10.01.2010 23:48

Хорошее решение, но если вы не закрепите массивы байтов на месте, вы будете просить о больших проблемах со временем.

Sebastian Good 22.02.2010 21:32

В этом случае закрепление не требуется. Маршаллер выполняет автоматическое закрепление при вызове собственного кода с помощью PInvoke. Ссылка: stackoverflow.com/questions/2218444/…

Mark Glasgow 16.03.2010 11:55

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

Josh 01.09.2011 23:44

Какого черта? Компания Poster хотела быструю реализацию, а оптимизированное сравнение языка ассемблера не могло быть лучше. Я не знаю, как получить REPE CMPSD из .NET без P / INVOKE.

Jason Goemaat 02.10.2011 10:55

Еще одним преимуществом решения memcmp является то, что его можно расширить для реализации полного упорядочения байтовых массивов, а не только для сравнения на равенство. Большой +1.

Constantin 28.12.2011 14:15

У меня были проблемы с точной реализацией этого метода; решил их с информацией, найденной здесь: pinvoke.net/default.aspx/msvcrt.memcmp

Ben Mosher 10.05.2012 01:08

примерно в 11 раз быстрее, чем простой цикл for. Примерно на 50% быстрее, чем небезопасный код .NET.

Hafthor 21.12.2012 09:48

Это быстро, но может отличаться от использования цикла for. Эта реализация будет сравнивать память вместо использования сравнения Equals. В случае byte [] результат должен быть таким же, но это решение не может быть обобщено на произвольные массивы.

Antoine Aubry 14.03.2013 18:31

Это гениально. Поскольку это встроенные функции компилятора, я никогда не думал, что вы можете использовать P / Invoke для их вызова, более того, ускорение связано с оптимизацией суперскалярности / векторизации. Трудно найти где-нибудь еще ... отличная находка!

John Leidegren 20.12.2013 14:47

Nitpick: MSVCR.dll не должен использоваться пользовательским кодом. Чтобы использовать MSVCR, вам нужно будет распространять среду выполнения, используя версию, которую вы распространяете. (msdn.microsoft.com/en-us/library/… и blogs.msdn.com/b/oldnewthing/archive/2014/04/11/10516280.asp‌ x)

Mitch 12.12.2014 07:55

Обратите внимание, что последний параметр memcmp - это IntPtr, а не long, потому что, если программа работает на 32-битном уровне, это 32-битный параметр, в 64-битном - 64-битный. Понятно тогда надо memcmp(b1, b2, (IntPtr)b1.Length)

xanatos 23.07.2015 16:41

Функция должна называться ByteArrayEquals. Методы Equals возвращают true / false. Методы сравнения возвращают <0, 0,> 0 для значений меньше, равно или больше.

James Curran 01.09.2017 20:49

Блин, я люблю P / Invoke.

Jon Fast 20.03.2018 22:58

@fabspro Не нужно ничего распространять. stackoverflow.com/questions/10166412/…

Soonts 15.07.2019 20:02

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

Вот что я использовал. Но это ммм ... звучит как последовательное сравнение, которое в противном случае вы бы использовали с помощью простого цикла, следовательно, не очень быстро. Было бы неплохо отразить это и посмотреть, что на самом деле происходит. Судя по названию, ничего особенного.

Sergey Akopov 06.01.2011 23:33

Да, но уже упоминалось в принятом ответе. кстати, вы можете удалить там спецификацию типа.

nawfal 02.06.2013 19:19

В .NET 4 для этого есть новое встроенное решение - IСтруктурный

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

Согласно это сообщение в блоге, на самом деле это очень медленно.

Matt Johnson-Pint 18.12.2012 03:23

Безумно медленно. Примерно в 180 раз медленнее, чем простой цикл for.

Hafthor 21.12.2012 09:47

Почему не просто StructuralComparisons.StructuralEqualityComparer.Equals(a1, a2). Здесь нет NullReferenceException.

ta.speot.is 25.03.2014 03:51

@ ta.speot.is Спасибо, не могу поспорить с одним лайнером! Предыдущее решение было немного более эффективным, поскольку оно сохраняло приведение в IStructuralEquatable (массив статически известен как IStructuralEquatable), но на самом деле ваши предложения заставляют метод работать и для нулевых аргументов.

Ohad Schneider 25.03.2014 15:39
Ответ принят как подходящий

Пользователь гил предложил небезопасный код, который породил это решение:

// 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 будут по крайней мере такими медленными или даже хуже.

Хорошее решение. Но один (небольшой) совет: сравнение, если ссылки a1 и a2 равны, может ускорить процесс, если дать один и тот же массив для a1 и b1.

mmmmmmmm 20.12.2012 17:34

BTW: пытался выполнить приведение к Guid для 128-битного сравнения, но из-за этого он работал медленнее.

Hafthor 21.12.2012 09:24

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

Hafthor 21.12.2012 09:46

Я понимаю ваш код, но не ваш комментарий по поводу «выровнено по qword». Что это такое и почему это влияет на производительность?

user276648 10.01.2013 06:59

Эта ссылка дает хорошую информацию о том, почему выравнивание памяти имеет значение ibm.com/developerworks/library/pa-dalign - таким образом, оптимизацией может быть проверка выравнивания, и если оба массива не выровнены на одинаковую величину, сравнивайте байты, пока они оба не будут на границе qword.

Hafthor 10.01.2013 20:28

@Hafthor: интересная статья. Однако он довольно устарел (февраль 2005 г.). Интересно, как обстоят дела сейчас (например, если некоторые процессоры все еще просят ОС позаботиться о выравнивании).

user276648 22.01.2013 07:08

Разве это не даст ложь, если и a1, и a2 равны нулю?

nawfal 14.04.2013 14:26

@Hafthor Вы должны интегрировать свои «новые тестовые данные» в свой ответ для лучшей видимости. Я думаю, что это действительно дает перспективу!

Cristian Diaconescu 27.06.2013 13:46

@Hafthor Также, как вы используете Bitconverter для сравнения массивов?

Cristian Diaconescu 27.06.2013 13:48

@CristiDiaconescu Я зациклил ответ Кевина Дриджера. Что мне, вероятно, следует сделать, так это сделать набор тестов и мои результаты доступными на github и дать ссылку на него в моем ответе.

Hafthor 27.06.2013 21:06

Идея @ RüdigerStevens добавить простой if (a1 == a2) return true; в начало также сделает этот возврат истинным, если оба значения равны нулю, что устраняет озабоченность @nawfal о том, что значения NULL не равны.

Joe Amenta 04.04.2015 19:13

@Hafthor unsafe может быть быстрее, чем pinvoke с разворачиванием цикла. Смотрите мой ответ.

Mr Anderson 24.06.2016 02:05

Что касается предположения, что закрепленные объекты GC должны быть выровнены по 64-битной системе, это весьма вероятно, но знаете ли вы какие-либо официальные упоминания или гарантии? Это могло быть просто следствием выравнивания кучи GC, которое было бы надежным агентом, на который можно было бы делать ставки для строгого соблюдения. Случайно х86 под нагрузкой проверяли? Кроме того, небольшой момент в отношении вашего утверждения о том, что рассогласование будет происходить «но не так быстро». Чтобы быть справедливым, вы можете заметить, что, постоянно настаивая на неправильном доступе, ваш код увековечивает довольно извращенное наказание в этом (полностью исключенном) случае, возможно, даже хуже, чем байтовый доступ.

Glenn Slayden 17.04.2017 14:01

@ RüdigerStevens С полным и должным уважением к исходному плакату (мы, конечно, можем отменить), я добавил предложенное вами изменение. Как вы заметили, это добавляет эталонное равенство в качестве истинно-положительного для отказа от сравнения (в данном случае полностью избегая закрепления), формализует новое предположение о корректности null==null, а также позволяет избежать случаев NullReferenceException. Не изменившийся до сих пор, null никогда не равен какому-либо byte[0].

Glenn Slayden 17.04.2017 16:34

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

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

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

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

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

    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 раза медленнее, чем простой цикл for.

Doug Clutter 15.07.2015 18:06

Когда я сделал то же самое, простое упражнение было примерно в 8 раз медленнее. Вы можете написать здесь свой код?

Alon 16.07.2015 18:55

Будет ли это нарушено, если байт содержит значение NULL (0)?

Joseph Lennox 22.09.2015 19:52

-1 Помимо того, что это происходит медленно из-за преобразования в строку, как указано @DougClutter, это не удастся, если массив байтов содержит данные, отличные от ASCII. Чтобы получить правильный результат, необходимо использовать iso-8859-1.

Joe 19.01.2017 13:33

Очень плохой подход. Хотя само сравнение действительно быстрое, чтобы добраться до него, вам нужно без нужды нагружать сборщик мусора еще одним распределением, а также выполнять само распределение, копировать данные (что в любом случае требует цикла, потому что каждый байт должен быть расширен до char. ), и результат даже не обрабатывает символы, выходящие за пределы диапазона ASCII! Так что нет, все плохо.

Etienne de Martel 02.06.2017 00:07

Compare(new byte[]{128}, new byte[]{ 255 }) == true совсем не глючит ...

CodesInChaos 03.02.2018 18:57

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

Я подтверждаю. Я тоже провел тесты. Это быстрее, чем ответ, использующий небезопасный вызов memcmp.

ujeenator 19.11.2015 20:31

@AmberdeBlack Вы уверены? Вы тестировали крошечные массивы?

Zar Shardan 31.03.2016 14:49

@ArekBulski Вы уверены, что это быстрее, чем memcmp, потому что мои тесты показывают обратное?

Zar Shardan 03.04.2016 03:39

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

Mike Marynowski 26.10.2018 16:46

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

    /// <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 итераций.

да, я заметил, что в комментарии к stackoverflow.com/a/1445280/4489, который показывает мое тестирование, это на самом деле немного медленнее, чем простой цикл for, который у меня был в исходном вопросе.

Hafthor 31.03.2016 02:25

уверены ли вы? По моему тестированию в 4 раза быстрее? Однако ничто не сравнится со старым добрым нативным кодом, даже с накладными расходами на маршалинг.

Zar Shardan 31.03.2016 14:43

Я разработал метод, который немного превосходит 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, может NETCORE:

Motlicek Petr 26.09.2016 13:55

Код дает сбой при сравнении двух массивов нулевой длины, потому что закрепление возвращает null.

Glenn Slayden 17.04.2017 15:53

memcmp - это не просто средство сравнения ценностей. Он предоставляет информацию о том, какой объект больше или меньше. Можете ли вы адаптировать свой алгоритм для этой цели и проверить его работоспособность?

nicolay.anykienko 24.01.2018 14:48

Это быстрее, чем Span и memcpy?

silkfire 09.03.2020 18:20

@silkfire На .NET core 3 и современном ЦП он должен быть в 2-3 раза быстрее для больших массивов.

Mr Anderson 09.03.2020 18:44

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

Добавим еще один!

Недавно Microsoft выпустила специальный пакет NuGet, System.Runtime.CompilerServices.Unsafe. Он особенный, потому что он написан на Иллинойс и обеспечивает низкоуровневую функциональность, недоступную напрямую в 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 |

Я также создал суть со всеми тестами.

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

Paulo Zemek 04.07.2018 00:36

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

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

Я провел некоторые измерения, используя прилагаемую программу .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);
            });
        }
    }
}

Я не видел здесь многих 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 03.02.2018 18:57

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

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

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

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

Приведенные ниже числа взяты из результатов, слегка отредактированы, чтобы удалить столбец «Ошибка».

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

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

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

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

Я никогда не думал, что буду использовать Span <T> или что-то похожее на то, что я делаю. Благодаря вам я теперь могу хвастаться этим своим коллегам.

jokab 20.02.2018 06:14

Реализован ли SequenceEqual как метод Span? Думал, что это всего лишь один из методов расширения IEnumerable.

Zastai 06.04.2018 12:26

@Zastai да, {ReadOnly,}Span<T> имеет свою собственную версию SequenceEqual (то же имя, потому что у него такой же контракт, что и у соответствующего метода расширения IEnumerable<T>, просто он быстрее). Обратите внимание, что {ReadOnly,}Span<T> не может использовать методы расширения IEnumerable<T> из-за ограничений на типы ref struct.

Joe Amenta 06.04.2018 13:33

Приятно знать. В настоящее время я все еще застрял на .NET 4.5, поэтому, к сожалению, пока не могу использовать Spans.

Zastai 06.04.2018 13:39

@Sentinel в пакете System.Memory есть "переносимые" / "медленные" реализации Span<T> для netstandard1.1 и выше (так что поиграйте с эта интерактивная диаграмма, чтобы узнать, какие из них). «Быстрый» Span<T> на данный момент доступен только в .NET Core 2.1, но обратите внимание, что для SequenceEqual<T>, в частности, должно быть очень мало различий между «быстрым» и «медленным» / «портативным» (хотя цели netstandard2.0 должны иметь небольшую улучшение, потому что они имеют векторизованный кодовый путь).

Joe Amenta 26.06.2018 13:14

@JoeAmenta Хорошо, спасибо за информацию. Я изучу это подробнее. Я много работаю, играя с протоколами, байтами и портирую довольно много вещей с golang, который, как я полагаю, имеет эти конструкции диапазона массивов. Span был бы прекрасным дополнением к многоплатформенной .NET.

Sentinel 26.06.2018 22:31

install-package system.memory

Chris Moschini 16.10.2018 17:15

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

Lance U. Matthews 27.04.2018 03:19

Урок здесь, дети: не берите нож в перестрелку

Richard Hauer 08.03.2019 17:04

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

Для тех из вас, кто заботится о порядке (т.е. хочет, чтобы ваш 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 в memcmp реализации CLR.

В дальнейшем это должен быть ваш метод сравнения байтовых массивов или байтовых диапазонов (как и использование 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

Другие вопросы по теме