Как я могу сделать это быстро?
Конечно, я могу это сделать:
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.
Обратите внимание на мой сверхбыстрый ответ здесь.
return a1.Length == a2.Length &&! a1.Where ((t, i) => t! = a2 [i]). Any ();





Я бы использовал небезопасный код и запустил цикл for, сравнивая указатели Int32.
Возможно, вам также следует подумать о проверке массивов на ненулевые.
Извините, если вы ищете управляемый способ, вы уже делаете это правильно, и, насколько мне известно, в BCL нет встроенного метода для этого.
Вы должны добавить несколько начальных нулевых проверок, а затем просто повторно использовать его, как если бы он находился в BCL.
Вы были правы, когда написали это, однако в 2010 году (.NET 4.0) появился метод BCL, см. Ответ Охада Шнайдера. На момент вопроса в .NET 3.5 был Linq (см. Ответ aku).
Если вы не против этого, вы можете импортировать сборку 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 не занимает больше времени, чем небезопасное сравнение? Особенно, когда вы проводите тысячи сравнений?
Да, это примерно в 50 раз медленнее, чем небезопасное сравнение.
Спасибо за сравнение производительности - для меня это проясняет решение - SequenceEqual безопасен, но в этом случае медленный.
Здесь действительно воскрешают мертвых, но «медленно» - плохое слово здесь. В 50 раз медленнее звуки - плохо, но не часто вы сравниваете достаточно данных, чтобы иметь значение, и если да, вам действительно нужно сравнить это для вашего собственного случая по множеству причин. Например, обратите внимание, что создатель небезопасного ответа отмечает разницу в 7 раз медленнее, чем в 50 раз (скорость небезопасного метода также зависит от выравнивания данных). В редких случаях, когда эти числа имеют значение, P / Invoke работает еще быстрее.
Значит, более медленная реализация набирает более 300 лайков? Я бы посоветовал подключить msvcrt.dll, поскольку это был бы самый быстрый способ выполнить работу.
Самый быстрый - не самое главное для бизнеса. Ремонтопригодность намного «быстрее», чем экономия на этом коде в 99% случаев. Я использую SequenceEqual, и весь мой код составляет <1 мс. Те мкс, которые вы сохраняете, никогда не добавят 5 минут отсутствия удобочитаемости P / Invoke.
@PRMan Я понимаю вашу точку зрения, но не полностью согласен с ней. Вы редко возвращаетесь к той части кода, в которой уверены, что она работает, и снова пытаетесь ее понять. То же и с вашими коллегами. Комментирования такой части кода с помощью чего-то вроде / * очень хорошо протестированного и быстрого метода сравнения массивов * / в значительной степени достаточно, и это не повредит вашему бизнесу. с другой стороны, попытка оптимизировать приложение, в котором программист проявлял небрежность на протяжении всей разработки ... он в основном просматривает все части кода;)
Скорость также действительно зависит от размера данных и от того, как часто вы это делаете. Я сравниваю байтовый массив с примерно 10 байтами в нем вне цикла. Если бы я пытался сравнить растровые изображения размером 10 МБ, возможно, стоило бы изучить решение p / invoke. Преждевременная оптимизация и микрооптимизация могут быть убийственными
Очевидно, что метод P / Invoke также не является переносимым; это. Время, которое я потратил на написание этого предложения, намного превышает экономию времени при использовании P / Invoke для этой цели в нашем приложении, не говоря уже о переносимости.
Как этот метод сравнивается с производительностью цикла for? Медленнее, чем pinvoke, будет дано, но если он быстрее, чем цикл for, это не победа
.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 []. В конце концов, вы можете сказать только, действительно ли знаете сценарий использования ...
Принятый ответ преобразует байтовый буфер в длинный буфер и сравнивает его, как вы описали.
Если вы посмотрите, как .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.
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/…
Хорошее решение, но если вы не закрепите массивы байтов на месте, вы будете просить о больших проблемах со временем.
В этом случае закрепление не требуется. Маршаллер выполняет автоматическое закрепление при вызове собственного кода с помощью PInvoke. Ссылка: stackoverflow.com/questions/2218444/…
P / Invoke может вызывать крик, но это, безусловно, самое быстрое из всех представленных решений, включая реализацию, которую я придумал, которая использует небезопасные сравнения размера указателя. Однако есть несколько оптимизаций, которые вы можете сделать перед вызовом собственного кода, включая эталонное равенство и сравнение первого и последнего элементов.
Какого черта? Компания Poster хотела быструю реализацию, а оптимизированное сравнение языка ассемблера не могло быть лучше. Я не знаю, как получить REPE CMPSD из .NET без P / INVOKE.
Еще одним преимуществом решения memcmp является то, что его можно расширить для реализации полного упорядочения байтовых массивов, а не только для сравнения на равенство. Большой +1.
У меня были проблемы с точной реализацией этого метода; решил их с информацией, найденной здесь: pinvoke.net/default.aspx/msvcrt.memcmp
примерно в 11 раз быстрее, чем простой цикл for. Примерно на 50% быстрее, чем небезопасный код .NET.
Это быстро, но может отличаться от использования цикла for. Эта реализация будет сравнивать память вместо использования сравнения Equals. В случае byte [] результат должен быть таким же, но это решение не может быть обобщено на произвольные массивы.
Это гениально. Поскольку это встроенные функции компилятора, я никогда не думал, что вы можете использовать P / Invoke для их вызова, более того, ускорение связано с оптимизацией суперскалярности / векторизации. Трудно найти где-нибудь еще ... отличная находка!
Nitpick: MSVCR.dll не должен использоваться пользовательским кодом. Чтобы использовать MSVCR, вам нужно будет распространять среду выполнения, используя версию, которую вы распространяете. (msdn.microsoft.com/en-us/library/… и blogs.msdn.com/b/oldnewthing/archive/2014/04/11/10516280.asp x)
Обратите внимание, что последний параметр memcmp - это IntPtr, а не long, потому что, если программа работает на 32-битном уровне, это 32-битный параметр, в 64-битном - 64-битный. Понятно тогда надо memcmp(b1, b2, (IntPtr)b1.Length)
Функция должна называться ByteArrayEquals. Методы Equals возвращают true / false. Методы сравнения возвращают <0, 0,> 0 для значений меньше, равно или больше.
Блин, я люблю P / Invoke.
@fabspro Не нужно ничего распространять. stackoverflow.com/questions/10166412/…
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");
}
Вот что я использовал. Но это ммм ... звучит как последовательное сравнение, которое в противном случае вы бы использовали с помощью простого цикла, следовательно, не очень быстро. Было бы неплохо отразить это и посмотреть, что на самом деле происходит. Судя по названию, ничего особенного.
Да, но уже упоминалось в принятом ответе. кстати, вы можете удалить там спецификацию типа.
В .NET 4 для этого есть новое встроенное решение - IСтруктурный
static bool ByteArrayCompare(byte[] a1, byte[] a2)
{
return StructuralComparisons.StructuralEqualityComparer.Equals(a1, a2);
}
Согласно это сообщение в блоге, на самом деле это очень медленно.
Безумно медленно. Примерно в 180 раз медленнее, чем простой цикл for.
Почему не просто StructuralComparisons.StructuralEqualityComparer.Equals(a1, a2). Здесь нет NullReferenceException.
@ ta.speot.is Спасибо, не могу поспорить с одним лайнером! Предыдущее решение было немного более эффективным, поскольку оно сохраняло приведение в IStructuralEquatable (массив статически известен как IStructuralEquatable), но на самом деле ваши предложения заставляют метод работать и для нулевых аргументов.
Пользователь гил предложил небезопасный код, который породил это решение:
// 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.
BTW: пытался выполнить приведение к Guid для 128-битного сравнения, но из-за этого он работал медленнее.
Новые тестовые данные для версии .NET 4 x64: IStructualEquatable.equals примерно на 180 раз медленнее, SequenceEqual в 15 раз медленнее, сравнение хэшей SHA1 в 11 раз медленнее, бит-конвертер ~ то же самое, небезопасно в 7 раз быстрее, pinvoke в 11 раз быстрее. Довольно круто, что unsafe лишь немного медленнее, чем P / Invoke на memcmp.
Я понимаю ваш код, но не ваш комментарий по поводу «выровнено по qword». Что это такое и почему это влияет на производительность?
Эта ссылка дает хорошую информацию о том, почему выравнивание памяти имеет значение ibm.com/developerworks/library/pa-dalign - таким образом, оптимизацией может быть проверка выравнивания, и если оба массива не выровнены на одинаковую величину, сравнивайте байты, пока они оба не будут на границе qword.
@Hafthor: интересная статья. Однако он довольно устарел (февраль 2005 г.). Интересно, как обстоят дела сейчас (например, если некоторые процессоры все еще просят ОС позаботиться о выравнивании).
Разве это не даст ложь, если и a1, и a2 равны нулю?
@Hafthor Вы должны интегрировать свои «новые тестовые данные» в свой ответ для лучшей видимости. Я думаю, что это действительно дает перспективу!
@Hafthor Также, как вы используете Bitconverter для сравнения массивов?
@CristiDiaconescu Я зациклил ответ Кевина Дриджера. Что мне, вероятно, следует сделать, так это сделать набор тестов и мои результаты доступными на github и дать ссылку на него в моем ответе.
Идея @ RüdigerStevens добавить простой if (a1 == a2) return true; в начало также сделает этот возврат истинным, если оба значения равны нулю, что устраняет озабоченность @nawfal о том, что значения NULL не равны.
@Hafthor unsafe может быть быстрее, чем pinvoke с разворачиванием цикла. Смотрите мой ответ.
Что касается предположения, что закрепленные объекты GC должны быть выровнены по 64-битной системе, это весьма вероятно, но знаете ли вы какие-либо официальные упоминания или гарантии? Это могло быть просто следствием выравнивания кучи GC, которое было бы надежным агентом, на который можно было бы делать ставки для строгого соблюдения. Случайно х86 под нагрузкой проверяли? Кроме того, небольшой момент в отношении вашего утверждения о том, что рассогласование будет происходить «но не так быстро». Чтобы быть справедливым, вы можете заметить, что, постоянно настаивая на неправильном доступе, ваш код увековечивает довольно извращенное наказание в этом (полностью исключенном) случае, возможно, даже хуже, чем байтовый доступ.
@ RüdigerStevens С полным и должным уважением к исходному плакату (мы, конечно, можем отменить), я добавил предложенное вами изменение. Как вы заметили, это добавляет эталонное равенство в качестве истинно-положительного для отказа от сравнения (в данном случае полностью избегая закрепления), формализует новое предположение о корректности null==null, а также позволяет избежать случаев NullReferenceException. Не изменившийся до сих пор, null никогда не равен какому-либо byte[0].
Если вам нужен очень быстрый компаратор на равенство байтовых массивов, я предлагаю вам взглянуть на эту статью лаборатории 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.
Когда я сделал то же самое, простое упражнение было примерно в 8 раз медленнее. Вы можете написать здесь свой код?
Будет ли это нарушено, если байт содержит значение NULL (0)?
-1 Помимо того, что это происходит медленно из-за преобразования в строку, как указано @DougClutter, это не удастся, если массив байтов содержит данные, отличные от ASCII. Чтобы получить правильный результат, необходимо использовать iso-8859-1.
Очень плохой подход. Хотя само сравнение действительно быстрое, чтобы добраться до него, вам нужно без нужды нагружать сборщик мусора еще одним распределением, а также выполнять само распределение, копировать данные (что в любом случае требует цикла, потому что каждый байт должен быть расширен до char. ), и результат даже не обрабатывает символы, выходящие за пределы диапазона ASCII! Так что нет, все плохо.
Compare(new byte[]{128}, new byte[]{ 255 }) == true совсем не глючит ...
Я отправил аналогичный вопрос о проверке, заполнен ли 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.
@AmberdeBlack Вы уверены? Вы тестировали крошечные массивы?
@ArekBulski Вы уверены, что это быстрее, чем memcmp, потому что мои тесты показывают обратное?
Я получил практически одинаковую производительность между этим и memcmp, так что +1 для полностью управляемого решения.
Не удалось найти решение, которым я полностью доволен (разумная производительность, но без небезопасного кода / пинвока), поэтому я придумал это, ничего действительно оригинального, но работает:
/// <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, который у меня был в исходном вопросе.
уверены ли вы? По моему тестированию в 4 раза быстрее? Однако ничто не сравнится со старым добрым нативным кодом, даже с накладными расходами на маршалинг.
Я разработал метод, который немного превосходит 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:
Код дает сбой при сравнении двух массивов нулевой длины, потому что закрепление возвращает null.
memcmp - это не просто средство сравнения ценностей. Он предоставляет информацию о том, какой объект больше или меньше. Можете ли вы адаптировать свой алгоритм для этой цели и проверить его работоспособность?
Это быстрее, чем Span и memcpy?
@silkfire На .NET core 3 и современном ЦП он должен быть в 2-3 раза быстрее для больших массивов.
Кажется, что EqualBytesLongUnrolled - лучший из предложенных выше.
Пропущенные методы (Enumerable.SequenceEqual, StructuralComparisons.StructuralEqualityComparer.Equals) не были терпеливыми для медленных. На 265-мегабайтных массивах я измерил это:
Host Process Environment Information:
BenchmarkDotNet.Core=v0.9.9.0
OS=Microsoft Windows NT 6.2.9200.0
Processor=Intel(R) Core(TM) i7-3770 CPU 3.40GHz, ProcessorCount=8
Frequency=3323582 ticks, Resolution=300.8802 ns, Timer=TSC
CLR=MS.NET 4.0.30319.42000, Arch=64-bit RELEASE [RyuJIT]
GC=Concurrent Workstation
JitModules=clrjit-v4.6.1590.0
Type=CompareMemoriesBenchmarks Mode=Throughput
Method | Median | StdDev | Scaled | Scaled-SD |
----------------------- |------------ |---------- |------- |---------- |
NewMemCopy | 30.0443 ms | 1.1880 ms | 1.00 | 0.00 |
EqualBytesLongUnrolled | 29.9917 ms | 0.7480 ms | 0.99 | 0.04 |
msvcrt_memcmp | 30.0930 ms | 0.2964 ms | 1.00 | 0.03 |
UnsafeCompare | 31.0520 ms | 0.7072 ms | 1.03 | 0.04 |
ByteArrayCompare | 212.9980 ms | 2.0776 ms | 7.06 | 0.25 |
OS=Windows
Processor=?, ProcessorCount=8
Frequency=3323582 ticks, Resolution=300.8802 ns, Timer=TSC
CLR=CORE, Arch=64-bit ? [RyuJIT]
GC=Concurrent Workstation
dotnet cli version: 1.0.0-preview2-003131
Type=CompareMemoriesBenchmarks Mode=Throughput
Method | Median | StdDev | Scaled | Scaled-SD |
----------------------- |------------ |---------- |------- |---------- |
NewMemCopy | 30.1789 ms | 0.0437 ms | 1.00 | 0.00 |
EqualBytesLongUnrolled | 30.1985 ms | 0.1782 ms | 1.00 | 0.01 |
msvcrt_memcmp | 30.1084 ms | 0.0660 ms | 1.00 | 0.00 |
UnsafeCompare | 31.1845 ms | 0.4051 ms | 1.03 | 0.01 |
ByteArrayCompare | 212.0213 ms | 0.1694 ms | 7.03 | 0.01 |
Добавим еще один!
Недавно 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
Это почти наверняка намного медленнее, чем любая другая приведенная здесь версия, но писать ее было интересно.
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();
}
Вся суть вопроса - это более быстрое решение, которое функция опубликовала в вопросе.
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> или что-то похожее на то, что я делаю. Благодаря вам я теперь могу хвастаться этим своим коллегам.
Реализован ли SequenceEqual как метод Span? Думал, что это всего лишь один из методов расширения IEnumerable.
@Zastai да, {ReadOnly,}Span<T> имеет свою собственную версию SequenceEqual (то же имя, потому что у него такой же контракт, что и у соответствующего метода расширения IEnumerable<T>, просто он быстрее). Обратите внимание, что {ReadOnly,}Span<T> не может использовать методы расширения IEnumerable<T> из-за ограничений на типы ref struct.
Приятно знать. В настоящее время я все еще застрял на .NET 4.5, поэтому, к сожалению, пока не могу использовать Spans.
@Sentinel в пакете System.Memory есть "переносимые" / "медленные" реализации Span<T> для netstandard1.1 и выше (так что поиграйте с эта интерактивная диаграмма, чтобы узнать, какие из них). «Быстрый» Span<T> на данный момент доступен только в .NET Core 2.1, но обратите внимание, что для SequenceEqual<T>, в частности, должно быть очень мало различий между «быстрым» и «медленным» / «портативным» (хотя цели netstandard2.0 должны иметь небольшую улучшение, потому что они имеют векторизованный кодовый путь).
@JoeAmenta Хорошо, спасибо за информацию. Я изучу это подробнее. Я много работаю, играя с протоколами, байтами и портирую довольно много вещей с golang, который, как я полагаю, имеет эти конструкции диапазона массивов. Span был бы прекрасным дополнением к многоплатформенной .NET.
install-package system.memory
Поскольку многие из вышеперечисленных причудливых решений не работают с 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 в обоих случаях.
Урок здесь, дети: не берите нож в перестрелку
Я остановился на решении, вдохновленном методом 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
«Это вроде как рассчитывается на тот факт, что массивы начинают выровнены по qword». Это большое «если». Вы должны исправить код, чтобы отразить это.