Jak mogę to zrobić szybko?
Jasne, że mogę to zrobić:
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;
}
Ale szukam albo funkcji BCL, albo jakiegoś wysoce zoptymalizowanego, sprawdzonego sposobu na zrobienie tego.
java.util.Arrays.equals((sbyte[])(Array)a1, (sbyte[])(Array)a2);
działa ładnie, ale nie wygląda na to, że działałoby to dla x64.
Zwróć uwagę na moją superszybką odpowiedź tutaj .
IStructuralEquatable
Odpowiedzi:
Możesz użyć metody Enumerable.SequenceEqual .
Jeśli z jakiegoś powodu nie możesz użyć .NET 3.5, twoja metoda jest OK.
Środowisko wykonawcze kompilatora zoptymalizuje pętlę, abyś nie musiał się martwić wydajnością.
źródło
Moce P / Invoke aktywują się!
źródło
Istnieje nowe wbudowane rozwiązanie dla tego w .NET 4 - IStructuralEquatable
źródło
StructuralComparisons.StructuralEqualityComparer.Equals(a1, a2)
. Nie maNullReferenceException
tutaj.Użytkownik gil zasugerował niebezpieczny kod, który zrodził to rozwiązanie:
który wykonuje 64-bitowe porównanie dla jak największej liczby tablic. Ten rodzaj polega na tym, że tablice rozpoczynają wyrównanie qworda. Będzie działać, jeśli nie dopasuje qword, tylko nie tak szybko, jakby to było.
Wykonuje około siedem timerów szybciej niż prosta
for
pętla. Korzystanie z biblioteki J # wykonywane równorzędnie z oryginalnąfor
pętlą. Korzystanie z .SequenceEqual działa około siedem razy wolniej; Myślę, że tylko dlatego, że używa IEnumerator.MoveNext. Wyobrażam sobie, że rozwiązania oparte na LINQ są co najmniej tak wolne lub gorsze.źródło
Span<T>
oferuje niezwykle konkurencyjną alternatywę bez konieczności wrzucania mylących i / lub nieprzenośnych puchów w bazę kodu własnej aplikacji:Implementację (wnętrzności) od .NET Core 3.1.0 można znaleźć tutaj .
Mam poprawione @ GIST EliArbel by dodać tę metodę jako
SpansEqual
, spadek większości mniej ciekawych wykonawców w benchmarkach innych, uruchom go z różnych rozmiarach tablic, wykresów wyjściowych i oznaczSpansEqual
jako bazowej tak, że informuje, w jaki sposób różne metody w porównaniu doSpansEqual
.Poniższe liczby pochodzą z wyników, lekko zmodyfikowanych w celu usunięcia kolumny „Błąd”.
Byłem zaskoczony widząc
SpansEqual
nie wyszedłem na szczyt w przypadku metod maksymalnego rozmiaru macierzy, ale różnica jest tak niewielka, że nie sądzę, żeby to kiedykolwiek miało znaczenie.Informacje o moim systemie:
źródło
{ReadOnly,}Span<T>
ma swoją własną wersjęSequenceEqual
(ta sama nazwa, ponieważ ma taką samą umowę jak odpowiedniaIEnumerable<T>
metoda rozszerzenia, jest po prostu szybsza). Zauważ, że{ReadOnly,}Span<T>
nie można używaćIEnumerable<T>
metod rozszerzeń z powodu ograniczeńref struct
typów.Span<T>
implementacje „przenośne” / „powolne” dlanetstandard1.1
i powyżej (więc graj z tym interaktywnym wykresem, aby zobaczyć, które to są).Span<T>
W tej chwili opcja „Szybka” jest dostępna tylko w .NET Core 2.1, ale zauważ, że wSequenceEqual<T>
szczególności różnica między „szybką” a „wolną” / „przenośną”netstandard2.0
powinna być niewielka (choć cele powinny nieznacznie się poprawić, ponieważ mieć wektoryzowaną ścieżkę kodu).Jeśli nie masz nic przeciwko temu, możesz zaimportować zestaw J # „vjslib.dll” i użyć jego metody Arrays.equals (byte [], byte []) ...
Nie obwiniaj mnie, jeśli ktoś się z ciebie śmieje ...
EDYCJA: Za ile to jest warte, użyłem Reflectora, aby zdemontować kod, a oto jak to wygląda:
źródło
.NET 3.5 i nowsze mają nowy typ publiczny,
System.Data.Linq.Binary
który zawierabyte[]
. Implementuje,IEquatable<Binary>
że (w efekcie) porównuje dwie tablice bajtów. Zauważ, żeSystem.Data.Linq.Binary
ma także domyślny operator konwersji zbyte[]
.Dokumentacja MSDN: System.Data.Linq.Binary
Dekompilacja odbłyśnika metody Equals:
Ciekawe jest to, że przechodzą do pętli porównywania bajt po bajcie tylko wtedy, gdy skróty dwóch obiektów binarnych są takie same. Jest to jednak kosztem obliczenia skrótu w konstruktorze
Binary
obiektów (przez przemierzanie tablicy za pomocąfor
pętli :-)).Powyższa implementacja oznacza, że w najgorszym przypadku może być konieczne trzykrotne przejrzenie tablic: najpierw oblicza się skrót tablicy 1, następnie oblicza skrót tablicy 2, a na koniec (ponieważ jest to najgorszy scenariusz, długości i skróty są równe) w celu porównania bajty w tablicy 1 z bajtami w tablicy 2.
Ogólnie rzecz biorąc, mimo że
System.Data.Linq.Binary
jest wbudowany w BCL, nie sądzę, że jest to najszybszy sposób na porównanie dwóch bajtów: - |źródło
Zadałem podobne pytanie o sprawdzenie, czy bajt [] jest pełny zer. (Kod SIMD został pobity, więc usunąłem go z tej odpowiedzi.) Oto najszybszy kod z moich porównań:
Mierzone na dwóch tablicach 256 MB:
źródło
źródło
Dodajmy jeszcze jeden!
Ostatnio Microsoft wydał specjalny pakiet NuGet, System.Runtime.CompilerServices.Unsafe . Jest wyjątkowy, ponieważ jest napisany w IL i zapewnia funkcje niskiego poziomu, które nie są bezpośrednio dostępne w języku C #.
Jedna z jego metod
Unsafe.As<T>(object)
pozwala na rzutowanie dowolnego typu odniesienia na inny typ odniesienia, pomijając wszelkie kontrole bezpieczeństwa. Jest to zwykle bardzo zły pomysł, ale jeśli oba typy mają tę samą strukturę, może działać. Możemy więc użyć tego dobyte[]
przesłanialong[]
:Zauważ, że
long1.Length
że nadal zwróci oryginalną długość tablicy, ponieważ jest ona przechowywana w polu w strukturze pamięci tablicy.Ta metoda nie jest tak szybka, jak inne metody tutaj pokazane, ale jest znacznie szybsza niż metoda naiwna, nie używa niebezpiecznego kodu, P / Invoke ani przypinania, a implementacja jest dość prosta (IMO). Oto niektóre wyniki BenchmarkDotNet z mojej maszyny:
Stworzyłem również istotę wszystkich testów .
źródło
NewMemCmp
odpowiedź, aby używać AVX-2Opracowałem metodę, która lekko bije
memcmp()
(odpowiedź cokołu) i bardzo lekko bije (odpowiedź ArkaEqualBytesLongUnrolled()
Bulskiego) na moim komputerze. Zasadniczo rozwija pętlę o 4 zamiast 8.Aktualizacja 30 marca 2019 r . :
Począwszy od .NET core 3.0, mamy obsługę SIMD!
To rozwiązanie jest najszybsze dzięki znacznemu marginesowi na moim komputerze:
źródło
null
.Span
imemcpy
?Chciałbym użyć niebezpiecznego kodu i uruchomić
for
pętlę porównującą wskaźniki Int32.Być może powinieneś również rozważyć sprawdzenie tablic jako niepustych.
źródło
Jeśli spojrzysz na to, jak .NET robi string.Equals, zobaczysz, że używa prywatnej metody o nazwie EqualsHelper, która ma „niebezpieczną” implementację wskaźnika. .NET Reflector jest Twoim przyjacielem, aby zobaczyć, jak rzeczy są wykonywane wewnętrznie.
Może to być użyte jako szablon do porównania tablicy bajtów, którą wykonałem na blogu. Szybkie porównanie tablicy bajtów w C # . Zrobiłem również podstawowe testy porównawcze, aby zobaczyć, kiedy bezpieczna implementacja jest szybsza niż niebezpieczna.
To powiedziawszy, jeśli naprawdę nie potrzebujesz zabójczej wydajności, wybrałbym proste porównanie pętli fr.
źródło
Nie mogłem znaleźć rozwiązania, z którego jestem całkowicie zadowolony (rozsądna wydajność, ale brak niebezpiecznego kodu / pinvoke), więc wymyśliłem to, nic naprawdę oryginalnego, ale działa:
Wydajność w porównaniu z niektórymi innymi rozwiązaniami na tej stronie:
Prosta pętla: 19837 tyknięć, 1,00
* BitConverter: 4886 tyknięć, 4.06
Niebezpieczne Porównaj: 1636 tyknięć, 12.12
EqualBytesLongUnrolled: 637 tyknięć, 31.09
P / Invoke memcmp: 369 tyknięć, 53,67
Testowane na Linqpad, 1000000 bajtów identycznych tablic (najgorszy scenariusz), 500 iteracji każda.
źródło
Wygląda na to, że EqualBytesLongUnrolled jest najlepszy z powyższych sugerowanych.
Pominięte metody (Enumerable.SequenceEqual, StructuralComparisons.StructuralEqualityComparer.Equals) nie były zbyt powolne. Na tablicach 265 MB zmierzyłem to:
źródło
NewMemCmp
odpowiedź, aby używać AVX-2Nie widziałem tutaj wielu rozwiązań linq.
Nie jestem pewien wpływu na wydajność, jednak ogólnie trzymam się
linq
zasady i w razie potrzeby optymalizuję później.Należy pamiętać, że działa to tylko wtedy, gdy są tablicami tego samego rozmiaru. rozszerzenie może tak wyglądać
źródło
Zrobiłem kilka pomiarów przy użyciu dołączonej wersji programu .net 4.7 bez dołączonego debugera. Myślę, że ludzie używają niewłaściwych danych, ponieważ zależy ci na szybkości, jak długo zajmuje ustalenie, czy dwie tablice bajtów są równe. tzn. przepływność w bajtach.
Jak widać, nie ma lepszego sposobu
memcmp
i jest o rząd wielkości szybszy. Prostafor
pętla jest drugą najlepszą opcją. I wciąż zastanawia mnie, dlaczego Microsoft nie może po prostu włączyćBuffer.Compare
metody.[Program.cs]:
źródło
Dla porównania tablic krótkich bajtów interesujący jest hack:
Wtedy prawdopodobnie wpadłbym na rozwiązanie wymienione w pytaniu.
Interesujące byłoby wykonanie analizy wydajności tego kodu.
źródło
Dla tych, którzy dbają o porządek (tj. Chcą,
memcmp
aby zwracany byłint
tak, jak powinien, zamiast niczego), .NET Core 3.0 (i prawdopodobnie .NET Standard 2.1 aka .NET 5.0) będzie zawieraćSpan.SequenceCompareTo(...)
metodę rozszerzenia (plus aSpan.SequenceEqualTo
), która może być używane do porównywania dwóchReadOnlySpan<T>
instancji (where T: IComparable<T>
).W pierwotnym wniosku GitHub dyskusja zawarte porównań podejścia z obliczeń stołowych skok, czytanie
byte[]
jaklong[]
, SIMD użytkowania, a P / Invoke do tych realizacji CLRmemcmp
.W przyszłości powinna to być twoja metoda porównywania tablic bajtów lub zakresów bajtów (co powinno być używane
Span<byte>
zamiastbyte[]
interfejsów API .NET Standard 2.1) i jest wystarczająco szybka, abyś nie przejmował się optymalizacją (i nie, pomimo podobieństw w nazwie nie działa tak bezdennie jak okropnyEnumerable.SequenceEqual
).źródło
Myślałem o metodach przyspieszania transferu bloków wbudowanych w wiele kart graficznych. Ale wtedy musiałbyś skopiować wszystkie dane bajtowo, więc to ci nie pomoże, jeśli nie chcesz zaimplementować całej logiki w niezarządzanym i zależnym od sprzętu kodzie ...
Innym sposobem optymalizacji podobnym do podejścia pokazanego powyżej byłoby przechowywanie jak największej ilości danych w długim [], a nie w bajcie [] od samego początku, na przykład, jeśli czytasz je sekwencyjnie z pliku binarnego, lub jeśli używasz pliku odwzorowanego w pamięci, wczytaj dane jako długie [] lub pojedyncze długie wartości. Wtedy twoja pętla porównawcza będzie potrzebować tylko 1/8 liczby iteracji, które musiałby wykonać dla bajtu [] zawierającego tę samą ilość danych. To, kiedy i jak często trzeba porównywać, kiedy i jak często trzeba uzyskiwać dostęp do danych bajt po bajcie, np. Aby użyć ich w wywołaniu API jako parametru w metodzie, która oczekuje bajt []. W końcu możesz stwierdzić tylko, czy naprawdę znasz przypadek użycia ...
źródło
Jest to prawie na pewno znacznie wolniejsze niż jakakolwiek inna wersja tutaj podana, ale pisanie było fajne.
źródło
Postawiłem na rozwiązanie inspirowane metodą EqualBytesLongUnrolled opublikowaną przez ArekBulski z dodatkową optymalizacją. W moim przypadku różnice między tablicami w tablicach wydają się być blisko ogona tablic. Podczas testowania odkryłem, że w przypadku dużych tablic możliwość porównania elementów tablicy w odwrotnej kolejności daje temu rozwiązaniu ogromny wzrost wydajności w porównaniu z rozwiązaniem opartym na memcmp. Oto to rozwiązanie:
źródło
Przepraszamy, jeśli szukasz sposobu zarządzania, to już robisz to poprawnie i, o ile wiem, nie ma wbudowanej metody w BCL.
Powinieneś dodać kilka początkowych kontroli zerowych, a następnie ponownie użyć go tak, jakby to było w BCL.
źródło
Użyj
SequenceEquals
tego do porównania.źródło
Jeśli szukasz bardzo szybkiego narzędzia do porównywania równości w tablicy bajtów, sugeruję zajrzeć do tego artykułu STSdb Labs: Narzędzie do porównywania równości w bajtach.Zawiera niektóre z najszybszych implementacji do porównywania równości macierzy bajtów [], które są prezentowane, testowane i podsumowywane pod kątem wydajności.
Możesz także skupić się na tych implementacjach:
BigEndianByteArrayComparer - szybka byte [] comparer tablica od strony lewej do prawej (bigEndian) BigEndianByteArrayEqualityComparer - - szybki byte [] comparer równość od lewej do prawej (bigEndian) LittleEndianByteArrayComparer - szybki byte [] comparer tablicy od prawej do lewej (littleEndian) LittleEndianByteArrayEqualityComparer - szybka bajt [] porównywarka równości od prawej do lewej (LittleEndian)
źródło
Krótka odpowiedź brzmi:
W ten sposób możesz użyć zoptymalizowanego ciągu .NET, aby porównać tablicę bajtów bez potrzeby pisania niebezpiecznego kodu. Oto jak to się robi w tle :
źródło
Compare(new byte[]{128}, new byte[]{ 255 }) == true
wcale nie jest wadliwy ...Ponieważ wiele powyższych fantazyjnych rozwiązań nie działa z UWP, a ponieważ kocham Linq i podejście funkcjonalne, przedstawiam wam moją wersję tego problemu. Aby uniknąć porównania, gdy pojawi się pierwsza różnica, wybrałem .FirstOrDefault ()
źródło
IndexOutOfRangeException
przy porównywaniu niepuste tablice ponieważ masz dostęp do elementów1
poprzezba0.Length
kiedy powinien być0
przezba0.Length - 1
. Jeśli to naprawisz,Enumerable.Range(0, ba0.Length)
nadal niepoprawnie zwracatrue
tablice o równej długości, w których różnią się tylko pierwsze elementy, ponieważ nie możesz rozróżnić między pierwszymi elementami spełniającymipredicate
a żadnymi elementami spełniającymipredicate
;FirstOrDefault<int>()
zwraca0
w obu przypadkach.