Comparaison de tableaux à deux octets dans .NET

541

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 .

Hafthor
la source
1
"Cela compte un peu sur le fait que les tableaux commencent par qword alignés." C'est un gros si. Vous devez corriger le code pour refléter cela.
Joe Chung
4
renvoie a1.Length == a2.Length &&! a1.Where ((t, i) => t! = a2 [i]). Any ();
alerya
J'ai aimé @OhadSchneider répondre à propos deIStructuralEquatable
LCJ

Réponses:

613

Vous pouvez utiliser la méthode 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

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.

aku
la source
4
Mais SequenceEqual ne prend-il pas plus de temps à traiter qu'une comparaison dangereuse? Surtout quand vous faites des milliers de comparaisons?
tcables
90
Oui, cela s'exécute environ 50 fois plus lentement que la comparaison dangereuse.
Hafthor
27
C'est vraiment ressusciter les morts ici, mais lent est vraiment un mauvais mot à utiliser ici. 50x plus lent semble mauvais, mais ce n'est pas souvent que vous comparez suffisamment de données pour que cela fasse une différence, et si vous l'êtes, vous devez vraiment le comparer à votre propre cas, pour une myriade de raisons. Par exemple, notez que le créateur de la réponse non sécurisée note une différence de 7x lente, par opposition à 50x plus lente (la vitesse de la méthode non sécurisée dépend également de l'alignement des données). Dans les rares cas où ces chiffres sont importants, P / Invoke est encore plus rapide.
Selali Adobor
4
Donc, l'implémentation plus lente obtient plus de 300 likes? Je suggère d'accrocher le msvcrt.dll car ce serait le moyen le plus rapide pour faire le travail.
TGarrett
69
Le plus rapide n'est pas la chose la plus importante pour une entreprise. La maintenabilité est beaucoup "plus rapide" que les économies réalisées sur ce code ne seront de 99%. J'utilise SequenceEqual et mon code entier est <1 ms. Les µs que vous enregistrez ne s'ajouteront jamais aux 5 minutes de manque de lisibilité de P / Invoke.
PRMan
236

Les pouvoirs P / Invoke s'activent!

[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;
}
socle
la source
48
P / Invoke yaay - cela s'est avéré être de loin le plus rapide sur les bitmaps: stackoverflow.com/questions/2031217/…
Erik Forbes
25
L'épinglage n'est pas nécessaire dans ce cas. Le marshaller effectue un épinglage automatique lors de l'appel de code natif avec PInvoke. Référence: stackoverflow.com/questions/2218444/…
Mark Glasgow
14
P / Invoke peut provoquer des huées, mais c'est de loin la plus rapide de toutes les solutions présentées, y compris une implémentation que j'ai imaginée qui utilise des comparaisons de taille de pointeur dangereuses. Il y a quelques optimisations que vous pouvez faire avant d'appeler le code natif, y compris l'égalité de référence et la comparaison des premier et dernier éléments.
Josh
38
Pourquoi le boo? Poster souhaitait une implémentation rapide et un langage d'assemblage optimisé, la comparaison est imbattable. Je ne sais pas comment obtenir un "REPE CMPSD" hors de .NET sans P / INVOKE.
Jason Goemaat
14
Nitpick: MSVCR.dll n'est pas censé être utilisé par le code utilisateur. Pour utiliser le MSVCR, vous devez distribuer le runtime en utilisant la version que vous distribuez. ( msdn.microsoft.com/en-us/library/… et blogs.msdn.com/b/oldnewthing/archive/2014/04/11/10516280.aspx )
Mitch
160

Il existe une nouvelle solution intégrée pour cela dans .NET 4 - IStructuralEquatable

static bool ByteArrayCompare(byte[] a1, byte[] a2) 
{
    return StructuralComparisons.StructuralEqualityComparer.Equals(a1, a2);
}
Ohad Schneider
la source
17
Selon ce billet de blog, c'est très lent.
Matt Johnson-Pint
48
Fou lent. Environ 180x plus lent que la boucle simple.
Hafthor
Cela fonctionne, mais je ne comprends pas pourquoi. Un octet [] est un type primitif qui n'implémente pas IStructuralEquatable, alors pourquoi pouvez-vous le convertir - et une conversion implicite à cela! Et puis la méthode d'interface "Equals" devient comme par magie disponible ... d'où vient l'implémentation de cette méthode? Quelqu'un peut-il me donner un indice?
Josh
1
Pourquoi pas juste StructuralComparisons.StructuralEqualityComparer.Equals(a1, a2). Non NullReferenceExceptionici.
ta.speot.is
1
@ ta.speot.is Merci, je ne peux pas discuter avec un seul paquebot! La solution précédente était légèrement plus efficace car elle a enregistré la conversion dans IStructuralEquatable (un tableau est statiquement connu pour être IStructuralEquatable), mais en effet, vos suggestions font que la méthode fonctionne également pour les arguments nuls.
Ohad Schneider
76

L'utilisateur gil a suggéré un code dangereux qui a engendré cette solution:

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

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 forboucle simple . Utilisation de la bibliothèque J # effectuée de manière équivalente à la forboucle 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.

Hafthor
la source
3
Belle solution. Mais un (petit) indice: une comparaison si les références a1 et a2 sont égales peut accélérer les choses si on donne le même tableau pour a1 et b1.
mmmmmmmm
12
Nouvelles données de test sur la version .NET 4 x64: IStructualEquatable.equals ~ 180x plus lent, SequenceEqual 15x plus lent, SHA1 hash compare 11x plus lent, bitconverter ~ même, dangereux 7x plus rapide, pinvoke 11x plus rapide. Assez cool que dangereux n'est qu'un peu plus lent que P / Invoke sur memcmp.
Hafthor
3
Ce lien explique en détail pourquoi l'alignement de la mémoire est important ibm.com/developerworks/library/pa-dalign - donc, une optimisation pourrait être de vérifier l'alignement et si les deux tableaux sont désalignés de la même quantité, effectuez des comparaisons d'octets jusqu'à ce qu'ils soient tous les deux sur une frontière qword.
Hafthor
5
cela ne donnerait-il pas faux lorsque a1 et a2 sont nuls?
nawfal
2
@CristiDiaconescu J'ai bouclé la réponse de KevinDriedger. Ce que je devrais probablement faire, c'est rendre la suite de tests et mes résultats disponibles sur github et les lier à ma réponse.
Hafthor
74

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:

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

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 marquez SpansEqualcomme 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".

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

J'ai été surpris de SpansEqualne 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:

BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18362
Intel Core i7-6850K CPU 3.60GHz (Skylake), 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=3.1.100
  [Host]     : .NET Core 3.1.0 (CoreCLR 4.700.19.56402, CoreFX 4.700.19.56404), X64 RyuJIT
  DefaultJob : .NET Core 3.1.0 (CoreCLR 4.700.19.56402, CoreFX 4.700.19.56404), X64 RyuJIT
Joe Amenta
la source
Je n'avais jamais pensé utiliser Span <T> ou quelque chose de proche dans tout ce que je fais. Grâce à vous, je peux maintenant m'en vanter auprès de mes collègues.
jokab
SequenceEqual est-il spécialement implémenté comme méthode Span? Je pensais que ce n'était qu'une des méthodes d'extension IEnumerable.
Zastai
1
@Zastai oui, {ReadOnly,}Span<T>a sa propre version de SequenceEqual(même nom car il a le même contrat que la IEnumerable<T>méthode d'extension correspondante , c'est juste plus rapide). Notez que {ReadOnly,}Span<T>vous ne pouvez pas utiliser de IEnumerable<T>méthodes d'extension en raison des restrictions sur les ref structtypes.
Joe Amenta
1
@Sentinel le package System.Memory a des Span<T>implémentations "portables" / "lentes" pour netstandard1.1et 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 pour SequenceEqual<T>spécifiquement, il devrait y avoir très peu de différence entre "fast" et "slow" / "portable" (bien que les netstandard2.0cibles devraient voir une légère amélioration car elles avoir le chemin du code vectorisé).
Joe Amenta
1
install-package system.memory
Chris Moschini
30

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:

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;
}
Jason Bunting
la source
25

.NET 3.5 et plus récent ont un nouveau type public, System.Data.Linq.Binaryqui encapsule byte[]. Il implémente IEquatable<Binary>que (en effet) compare deux tableaux d'octets. Notez que System.Data.Linq.Binarypossède également l'opérateur de conversion implicite de byte[].

Documentation MSDN: System.Data.Linq.Binary

Décompilation du réflecteur de la méthode 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;
}

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' Binaryobjets (en parcourant le tableau avec forboucle :-)).

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.Binaryest intégré à BCL, je ne pense pas que ce soit le moyen le plus rapide de comparer des tableaux à deux octets: - |.

Milan Gardian
la source
20

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:

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

Mesuré sur deux baies de 256 Mo:

UnsafeCompare                           : 86,8784 ms
EqualBytesSimd                          : 71,5125 ms
EqualBytesSimdUnrolled                  : 73,1917 ms
EqualBytesLongUnrolled                  : 39,8623 ms
ArekBulski
la source
1
Je confirme. J'ai également effectué les tests. C'est plus rapide que la réponse qui utilise l'appel non sécurisé memcmp.
ujeenator
1
@AmberdeBlack Êtes-vous sûr? Avez-vous testé avec de petits tableaux?
Zar Shardan
@ArekBulski Êtes-vous sûr que c'est plus rapide que memcmp, car mes tests montrent le contraire?
Zar Shardan
J'ai obtenu des performances pratiquement identiques entre ceci et memcmp, donc +1 pour une solution entièrement gérée.
Mike Marynowski
10
 using System.Linq; //SequenceEqual

 byte[] ByteArray1 = null;
 byte[] ByteArray2 = null;

 ByteArray1 = MyFunct1();
 ByteArray2 = MyFunct2();

 if (ByteArray1.SequenceEqual<byte>(ByteArray2) == true)
 {
    MessageBox.Show("Match");
 }
 else
 {
   MessageBox.Show("Don't match");
 }
user565710
la source
1
C'est ce que j'utilise. Mais ça euh ... sonne comme une comparaison séquentielle que vous feriez autrement en utilisant une simple boucle, donc pas très rapide. Ce serait bien de réfléchir et de voir ce qui se passe réellement. À en juger par le nom, ce n'est rien d'extraordinaire.
Sergey Akopov
1
Oui, mais déjà mentionné dans la réponse acceptée. btw, vous pouvez y supprimer la spécification de type.
nawfal
10

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 un byte[]vers un 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;
}

Notez que long1.Lengthcela 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:

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 |

J'ai également créé un résumé avec tous les tests .

Eli Arbel
la source
Il n'utilise pas le mot-clé unsafe, mais il appelle de toute façon du code dangereux en utilisant System.Runtime.CompilerServices.Unsafe
Paulo Zemek
J'ai mis à jour ma NewMemCmpréponse pour utiliser AVX-2
M. Anderson
8

J'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:

#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;
}
Mr Anderson
la source
Mes mesures diffèrent pour .NET 462 peut le NETCORE:
Motlicek Petr
Le code se bloque lors de la comparaison de deux tableaux de longueur 0, car l'épinglage revient null.
Glenn Slayden
memcmp n'est pas seulement un comparateur d'actions. Il fournit des informations sur quel objet est plus grand ou plus petit. Pouvez-vous adopter votre algorithme à cet effet et vérifier les performances?
nicolay.anykienko
Est-ce plus rapide que Spanet memcpy?
Silkfire
@silkfire Sur .NET core 3 et le processeur moderne, il devrait être 2 à 3 fois plus rapide pour les grandes baies.
Mr Anderson
6

J'utiliserais du code dangereux et exécuterais la forboucle en comparant les pointeurs Int32.

Vous devriez peut-être également envisager de vérifier que les tableaux ne sont pas nuls.

gil
la source
5

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.

Mikael Svenson
la source
3

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:

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

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.

Zar Shardan
la source
oui, j'ai remarqué que dans le commentaire de stackoverflow.com/a/1445280/4489 que mes tests montrent que c'est en fait un peu plus lent que la boucle for simple que j'avais dans la question d'origine.
Hafthor
êtes-vous sûr? Dans mes tests, c'est 4 fois plus rapide? Cependant, rien ne vaut le bon vieux code natif, même avec une surcharge de marshaling.
Zar Shardan
3

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:

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 |
Motlicek Petr
la source
J'ai mis à jour ma NewMemCmpréponse pour utiliser AVX-2
M. Anderson
3

Je 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 à linqla règle générale, puis j'optimise plus tard si nécessaire.

public bool CompareTwoArrays(byte[] array1, byte[] array2)
 {
   return !array1.Where((t, i) => t != array2[i]).Any();
 }

Veuillez noter que cela ne fonctionne que si les tableaux sont de la même taille. une extension pourrait ressembler à

public bool CompareTwoArrays(byte[] array1, byte[] array2)
 {
   if (array1.Length != array2.Length) return false;
   return !array1.Where((t, i) => t != array2[i]).Any();
 }
Zapnologica
la source
L'intérêt de la question est une solution plus rapide que la fonction publiée dans la question.
CodesInChaos
3

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.

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

Comme vous pouvez le voir, il n'y a pas de meilleur moyen que memcmpcela et ses ordres de grandeur sont plus rapides. Une simple forboucle est la deuxième meilleure option. Et cela m'empêche de comprendre pourquoi Microsoft ne peut pas simplement inclure une Buffer.Compareméthode.

[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);
            });
        }
    }
}
John Leidegren
la source
2

Pour comparer les tableaux d'octets courts, voici un hack intéressant:

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

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.

Kevin Driedger
la source
int i = 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; return true; // un peu plus lent que la boucle simple.
Hafthor
2

Pour ceux d'entre vous qui se soucient de l'ordre (c'est-à-dire que vous souhaitez que votre memcmpretourne un intcomme il le devrait au lieu de rien), .NET Core 3.0 (et probablement .NET Standard 2.1 aka .NET 5.0) inclura une Span.SequenceCompareTo(...)méthode d'extension (plus a Span.SequenceEqualTo) qui peut être utilisé pour comparer deux ReadOnlySpan<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[]as long[], l'utilisation de SIMD et p / invoke à l'implémentation CLR memcmp.

À 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 de byte[]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'horrible 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
Mahmoud Al-Qudsi
la source
1

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

Mirko Klemm
la source
La réponse acceptée refond le tampon d'octets comme un long tampon et le compare comme vous le décrivez.
Hafthor
1

C'est presque certainement beaucoup plus lent que n'importe quelle autre version donnée ici, mais c'était amusant à écrire.

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

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:

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;
}
Casey Chester
la source
0

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.

Markus Olsson
la source
Vous aviez raison quand vous avez écrit que, cependant, en 2010 (.NET 4.0) une méthode BCL est venue, voir la réponse d'Ohad Schneider. Au moment de la question, .NET 3.5 avait Linq (voir la réponse d'Aku).
Jeppe Stig Nielsen
-1

Utilisez-le SequenceEqualspour comparer.

API_Base
la source
-2

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)

Kris
la source
-2

La réponse courte est la suivante:

    public bool Compare(byte[] b1, byte[] b2)
    {
        return Encoding.ASCII.GetString(b1) == Encoding.ASCII.GetString(b2);
    }

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 :

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);
    }
}
Alon
la source
Dans mes tests, la conversion en chaîne détruit l'avantage d'une comparaison plus rapide. C'était environ 2,5 fois plus lent qu'une boucle simple.
Doug Clutter
Quand j'ai fait la même chose, le simple était environ 8 fois plus lent. Pouvez-vous écrire votre code ici?
Alon
1
Est-ce que cela se cassera si un octet contient une valeur nulle (0)?
Joseph Lennox
-1 En plus d'être lent en raison de la conversion en chaîne comme indiqué par @DougClutter, cela échouera si le tableau d'octets contient des données non ASCII. Pour obtenir le bon résultat, il faudrait utiliser l'iso-8859-1.
Joe
2
Compare(new byte[]{128}, new byte[]{ 255 }) == truepas du tout
bogué
-2

É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 ()

public static bool CompareByteArrays(byte[] ba0, byte[] ba1) =>
    !(ba0.Length != ba1.Length || Enumerable.Range(1,ba0.Length)
        .FirstOrDefault(n => ba0[n] != ba1[n]) > 0);
Raymond Osterbrink
la source
-1 car ce code est cassé et apparemment non testé. Cela jette un IndexOutOfRangeExceptionlorsque l'on compare les tableaux non vides parce que vous avez accès à des éléments à 1travers ba0.Lengthquand il devrait être à 0travers ba0.Length - 1. Si vous corrigez cela, Enumerable.Range(0, ba0.Length)il renvoie toujours de manière incorrecte truedes 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 satisfaisants predicateet aucun élément satisfaisant predicate; FirstOrDefault<int>()renvoie 0dans les deux cas.
BACON
La leçon ici, les enfants: n'apportez pas de couteau à une fusillade
Richard Hauer