Comment puis-je faire ça rapidement?
Bien sûr, je peux le faire:
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;
}
Mais je recherche soit une fonction BCL , soit une méthode éprouvée hautement optimisée pour le faire.
java.util.Arrays.equals((sbyte[])(Array)a1, (sbyte[])(Array)a2);
fonctionne bien, mais il ne semble pas que cela fonctionnerait pour x64.
Notez ma réponse super rapide ici .
IStructuralEquatable
Réponses:
Vous pouvez utiliser la méthode Enumerable.SequenceEqual .
Si vous ne pouvez pas utiliser .NET 3.5 pour une raison quelconque, votre méthode est OK.
L'environnement compilateur \ run-time optimisera votre boucle afin que vous n'ayez pas à vous soucier des performances.
la source
Les pouvoirs P / Invoke s'activent!
la source
Il existe une nouvelle solution intégrée pour cela dans .NET 4 - IStructuralEquatable
la source
StructuralComparisons.StructuralEqualityComparer.Equals(a1, a2)
. NonNullReferenceException
ici.L'utilisateur gil a suggéré un code dangereux qui a engendré cette solution:
qui effectue une comparaison basée sur 64 bits pour autant de tableau que possible. Ce genre de compte sur le fait que les tableaux commencent qword alignés. Cela fonctionnera s'il n'est pas aligné sur qword, mais pas aussi vite que s'il l'était.
Il effectue environ sept temporisations plus rapidement que la
for
boucle simple . Utilisation de la bibliothèque J # effectuée de manière équivalente à lafor
boucle d' origine . L'utilisation de .SequenceEqual s'exécute environ sept fois plus lentement; Je pense juste parce qu'il utilise IEnumerator.MoveNext. J'imagine que les solutions basées sur LINQ sont au moins aussi lentes ou pires.la source
Span<T>
offre une alternative extrêmement compétitive sans avoir à jeter de peluches déroutantes et / ou non portables dans la base de code de votre propre application:Les (entrailles de) l'implémentation de .NET Core 3.1.0 peuvent être trouvées ici .
J'ai révisé l'essentiel de @ EliArbel pour ajouter cette méthode
SpansEqual
, supprimez la plupart des artistes les moins intéressants dans les benchmarks des autres, exécutez-le avec différentes tailles de tableau, graphiques de sortie et marquezSpansEqual
comme ligne de base afin qu'il indique comment les différentes méthodes se comparent àSpansEqual
.Les chiffres ci-dessous proviennent des résultats, légèrement modifiés pour supprimer la colonne "Erreur".
J'ai été surpris de
SpansEqual
ne pas sortir en tête pour les méthodes de taille maximale du tableau, mais la différence est si minime que je ne pense pas que cela importera jamais.Mes informations système:
la source
{ReadOnly,}Span<T>
a sa propre version deSequenceEqual
(même nom car il a le même contrat que laIEnumerable<T>
méthode d'extension correspondante , c'est juste plus rapide). Notez que{ReadOnly,}Span<T>
vous ne pouvez pas utiliser deIEnumerable<T>
méthodes d'extension en raison des restrictions sur lesref struct
types.Span<T>
implémentations "portables" / "lentes" pournetstandard1.1
et au-dessus (alors jouez avec ce graphique interactif pour voir lesquelles). "Fast"Span<T>
est uniquement disponible dans .NET Core 2.1, pour le moment, mais notez que pourSequenceEqual<T>
spécifiquement, il devrait y avoir très peu de différence entre "fast" et "slow" / "portable" (bien que lesnetstandard2.0
cibles devraient voir une légère amélioration car elles avoir le chemin du code vectorisé).Si vous ne vous y opposez pas, vous pouvez importer l'assembly J # "vjslib.dll" et utiliser sa méthode Arrays.equals (byte [], byte []) ...
Ne me blâmez pas si quelqu'un se moque de vous ...
EDIT: Pour le peu que cela vaut, j'ai utilisé Reflector pour démonter le code pour cela, et voici à quoi il ressemble:
la source
.NET 3.5 et plus récent ont un nouveau type public,
System.Data.Linq.Binary
qui encapsulebyte[]
. Il implémenteIEquatable<Binary>
que (en effet) compare deux tableaux d'octets. Notez queSystem.Data.Linq.Binary
possède également l'opérateur de conversion implicite debyte[]
.Documentation MSDN: System.Data.Linq.Binary
Décompilation du réflecteur de la méthode Equals:
Une torsion intéressante est qu'ils ne procèdent à la boucle de comparaison octet par octet que si les hachages des deux objets binaires sont identiques. Ceci, cependant, se fait au détriment du calcul du hachage dans le constructeur d'
Binary
objets (en parcourant le tableau avecfor
boucle :-)).L'implémentation ci-dessus signifie que dans le pire des cas, vous devrez peut-être parcourir les tableaux trois fois: d'abord pour calculer le hachage de array1, puis pour calculer le hachage de array2 et enfin (car c'est le pire des cas, les longueurs et les hachages sont égaux) pour comparer octets dans le tableau 1 avec octets dans le tableau 2.
Dans l'ensemble, même s'il
System.Data.Linq.Binary
est intégré à BCL, je ne pense pas que ce soit le moyen le plus rapide de comparer des tableaux à deux octets: - |.la source
J'ai posté une question similaire sur la vérification si l'octet [] est plein de zéros. (Le code SIMD a été battu, je l'ai donc supprimé de cette réponse.) Voici le code le plus rapide de mes comparaisons:
Mesuré sur deux baies de 256 Mo:
la source
la source
Ajoutons-en un de plus!
Récemment, Microsoft a publié un package NuGet spécial, System.Runtime.CompilerServices.Unsafe . C'est spécial car il est écrit en IL et fournit des fonctionnalités de bas niveau qui ne sont pas directement disponibles en C #.
L'une de ses méthodes
Unsafe.As<T>(object)
permet de convertir n'importe quel type de référence en un autre type de référence, en évitant tout contrôle de sécurité. C'est généralement une très mauvaise idée, mais si les deux types ont la même structure, cela peut fonctionner. Nous pouvons donc l'utiliser pour lancer unbyte[]
vers unlong[]
:Notez que
long1.Length
cela retournerait toujours la longueur du tableau d'origine, car il est stocké dans un champ dans la structure de mémoire du tableau.Cette méthode n'est pas aussi rapide que les autres méthodes présentées ici, mais elle est beaucoup plus rapide que la méthode naïve, n'utilise pas de code dangereux ou P / Invoke ou épinglant, et l'implémentation est assez simple (IMO). Voici quelques résultats BenchmarkDotNet de ma machine:
J'ai également créé un résumé avec tous les tests .
la source
NewMemCmp
réponse pour utiliser AVX-2J'ai développé une méthode qui bat légèrement
memcmp()
(réponse du socle) et bat très légèrementEqualBytesLongUnrolled()
(réponse d'Arek Bulski) sur mon PC. Fondamentalement, il déroule la boucle de 4 au lieu de 8.Mise à jour 30 mars 2019 :
À partir de .NET core 3.0, nous avons le support SIMD!
Cette solution est la plus rapide d'une marge considérable sur mon PC:
la source
null
.Span
etmemcpy
?J'utiliserais du code dangereux et exécuterais la
for
boucle en comparant les pointeurs Int32.Vous devriez peut-être également envisager de vérifier que les tableaux ne sont pas nuls.
la source
Si vous regardez comment .NET fait string.Equals, vous voyez qu'il utilise une méthode privée appelée EqualsHelper qui a une implémentation de pointeur "non sûre". Réflecteur .NET est votre ami pour voir comment les choses se font en interne.
Cela peut être utilisé comme modèle de comparaison de tableaux d'octets sur lequel j'ai fait une implémentation dans un article de blog Comparaison rapide de tableaux d'octets en C # . J'ai également fait quelques repères rudimentaires pour voir quand une implémentation sûre est plus rapide que l'insécurité.
Cela dit, à moins que vous n'ayez vraiment besoin de performances exceptionnelles, j'opterais pour une simple comparaison de boucle fr.
la source
Impossible de trouver une solution dont je suis complètement satisfait (performances raisonnables, mais pas de code / pinvoke dangereux), donc j'ai trouvé cela, rien de vraiment original, mais ça marche:
Performances par rapport à certaines des autres solutions de cette page:
Boucle simple: 19837 ticks, 1,00
* BitConverter: 4886 ticks, 4,06
UnsafeCompare: 1636 ticks, 12.12
EqualBytesLongUnrolled: 637 ticks, 31.09
P / Invoke memcmp: 369 ticks, 53.67
Testé dans linqpad, tableaux identiques de 1000000 octets (pire scénario), 500 itérations chacun.
la source
Il semble que EqualBytesLongUnrolled soit le meilleur parmi ceux suggérés ci-dessus.
Les méthodes ignorées (Enumerable.SequenceEqual, StructuralComparisons.StructuralEqualityComparer.Equals) n'étaient pas patientes pour lent. Sur des baies de 265 Mo, j'ai mesuré ceci:
la source
NewMemCmp
réponse pour utiliser AVX-2Je n'ai pas vu beaucoup de solutions linq ici.
Je ne suis pas sûr des implications en termes de performances, mais je m'en tiens généralement à
linq
la règle générale, puis j'optimise plus tard si nécessaire.Veuillez noter que cela ne fonctionne que si les tableaux sont de la même taille. une extension pourrait ressembler à
la source
J'ai fait quelques mesures en utilisant le programme attaché .net 4.7 build build sans le débogueur attaché. Je pense que les gens ont utilisé la mauvaise métrique, car ce qui vous intéresse si vous vous souciez de la vitesse, c'est le temps qu'il faut pour déterminer si deux tableaux d'octets sont égaux. c'est-à-dire le débit en octets.
Comme vous pouvez le voir, il n'y a pas de meilleur moyen que
memcmp
cela et ses ordres de grandeur sont plus rapides. Une simplefor
boucle est la deuxième meilleure option. Et cela m'empêche de comprendre pourquoi Microsoft ne peut pas simplement inclure uneBuffer.Compare
méthode.[Program.cs]:
la source
Pour comparer les tableaux d'octets courts, voici un hack intéressant:
Je tomberais alors probablement dans la solution indiquée dans la question.
Il serait intéressant de faire une analyse des performances de ce code.
la source
Pour ceux d'entre vous qui se soucient de l'ordre (c'est-à-dire que vous souhaitez que votre
memcmp
retourne unint
comme il le devrait au lieu de rien), .NET Core 3.0 (et probablement .NET Standard 2.1 aka .NET 5.0) inclura uneSpan.SequenceCompareTo(...)
méthode d'extension (plus aSpan.SequenceEqualTo
) qui peut être utilisé pour comparer deuxReadOnlySpan<T>
instances (where T: IComparable<T>
).Dans la proposition originale de GitHub , la discussion comprenait des comparaisons d'approche avec des calculs de table de sauts, la lecture d'un
byte[]
aslong[]
, l'utilisation de SIMD et p / invoke à l'implémentation CLRmemcmp
.À l'avenir, cela devrait être votre méthode de référence pour comparer les tableaux d'octets ou les plages d'octets (comme cela devrait être le cas à la
Span<byte>
place debyte[]
vos API .NET Standard 2.1), et il est suffisamment rapide pour que vous ne vous souciez plus de l'optimiser (et non, malgré les similitudes de nom, il ne fonctionne pas aussi horriblement que l'horribleEnumerable.SequenceEqual
).la source
J'ai pensé aux méthodes d'accélération de transfert de blocs intégrées à de nombreuses cartes graphiques. Mais alors vous devrez copier toutes les données par octet, donc cela ne vous aide pas beaucoup si vous ne voulez pas implémenter toute une partie de votre logique dans du code non géré et dépendant du matériel ...
Une autre méthode d'optimisation similaire à l'approche illustrée ci-dessus serait de stocker autant de données que possible dans un long [] plutôt qu'un octet [] dès le début, par exemple si vous les lisez séquentiellement à partir d'un fichier binaire, ou si vous utilisez un fichier mappé en mémoire, lisez les données en tant que valeurs longues [] ou simples longues. Ensuite, votre boucle de comparaison n'aura besoin que du 1 / 8ème du nombre d'itérations qu'elle devra faire pour un octet [] contenant la même quantité de données. Il s'agit de savoir quand et à quelle fréquence vous devez comparer et quand et à quelle fréquence vous devez accéder aux données octet par octet, par exemple pour les utiliser dans un appel API comme paramètre d'une méthode qui attend un octet []. En fin de compte, vous ne pouvez dire que si vous connaissez vraiment le cas d'utilisation ...
la source
C'est presque certainement beaucoup plus lent que n'importe quelle autre version donnée ici, mais c'était amusant à écrire.
la source
J'ai opté pour une solution inspirée de la méthode EqualBytesLongUnrolled publiée par ArekBulski avec une optimisation supplémentaire. Dans mon cas, les différences de tableau dans les tableaux ont tendance à être près de la queue des tableaux. Lors des tests, j'ai constaté que lorsque c'est le cas pour les grands tableaux, le fait de pouvoir comparer les éléments du tableau dans l'ordre inverse donne à cette solution un énorme gain de performances par rapport à la solution basée sur memcmp. Voici cette solution:
la source
Désolé, si vous cherchez un moyen géré, vous le faites déjà correctement et à ma connaissance, il n'y a pas de méthode intégrée dans la BCL pour le faire.
Vous devez ajouter quelques vérifications nulles initiales, puis simplement les réutiliser comme si elles se trouvaient dans BCL.
la source
Utilisez-le
SequenceEquals
pour comparer.la source
Si vous recherchez un comparateur d'égalité de tableau d'octets très rapide, je vous suggère de jeter un œil à cet article de STSdb Labs: comparateur d'égalité de tableau d'octets.Il présente certaines des implémentations les plus rapides pour la comparaison d'égalité de tableau d'octets [], qui sont présentées, testées et résumées.
Vous pouvez également vous concentrer sur ces implémentations:
BigEndianByteArrayComparer - comparateur tableau d' octets rapide [] de gauche à droite (bigEndian) BigEndianByteArrayEqualityComparer - - comparateur égalité octet rapide [] de gauche à droite (bigEndian) LittleEndianByteArrayComparer - tableau d' octets rapide [] comparateur de droite à gauche (littleEndian) LittleEndianByteArrayEqualityComparer - octet rapide [] comparateur d'égalité de droite à gauche (LittleEndian)
la source
La réponse courte est la suivante:
De cette manière, vous pouvez utiliser la comparaison de chaîne .NET optimisée pour comparer un tableau d'octets sans avoir à écrire de code non sécurisé. Voici comment cela se fait en arrière - plan :
la source
Compare(new byte[]{128}, new byte[]{ 255 }) == true
pas du toutÉtant donné que de nombreuses solutions sophistiquées ci-dessus ne fonctionnent pas avec UWP et parce que j'aime Linq et les approches fonctionnelles, je vous presse ma version à ce problème. Pour échapper à la comparaison lorsque la première différence se produit, j'ai choisi .FirstOrDefault ()
la source
IndexOutOfRangeException
lorsque l'on compare les tableaux non vides parce que vous avez accès à des éléments à1
traversba0.Length
quand il devrait être à0
traversba0.Length - 1
. Si vous corrigez cela,Enumerable.Range(0, ba0.Length)
il renvoie toujours de manière incorrectetrue
des tableaux de longueur égale où seuls les premiers éléments diffèrent car vous ne pouvez pas faire la distinction entre les premiers éléments satisfaisantspredicate
et aucun élément satisfaisantpredicate
;FirstOrDefault<int>()
renvoie0
dans les deux cas.