Confronto di matrici a due byte in .NET


541

Come posso farlo velocemente?

Sicuro che posso farlo:

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

Ma sto cercando una funzione BCL o un modo provato altamente ottimizzato per farlo.

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

funziona bene, ma non sembra che funzionerebbe per x64.

Nota la mia risposta superveloce qui .


1
"Questo tipo di cose conta sul fatto che gli array iniziano qword allineati." È un grande se. Dovresti correggere il codice per riflettere quello.
Joe Chung,

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

Mi è piaciuta la risposta di @OhadSchneiderIStructuralEquatable
LCJ

Risposte:


613

È possibile utilizzare il metodo 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

Se non è possibile utilizzare .NET 3.5 per qualche motivo, il metodo è OK.
L'ambiente compilatore \ run-time ottimizzerà il tuo ciclo in modo da non doverti preoccupare delle prestazioni.


4
Ma SequenceEqual non richiede più tempo per l'elaborazione rispetto a un confronto non sicuro? Soprattutto quando stai facendo migliaia di confronti?
Cavi

90
Sì, questo è circa 50 volte più lento del confronto non sicuro.
Hafthor,

27
Questo sta davvero facendo risorgere i morti qui, ma lento è davvero una brutta parola da usare qui. 50 volte più lento suona male, ma non capita spesso di confrontare dati sufficienti per fare la differenza, e se lo sei, devi davvero fare un benchmark per il tuo caso, per una miriade di ragioni. Ad esempio, si noti che il creatore della risposta non sicura rileva una differenza di 7 volte più lenta, invece di 50 volte più lenta (la velocità del metodo non sicuro dipende anche dall'allineamento dei dati). Nei rari casi in cui questi numeri contano, P / Invoke è ancora più veloce.
Selali Adobor,

4
Quindi l'implementazione più lenta supera i 300 like? Suggerirei di collegare msvcrt.dll in quanto sarebbe il modo più veloce per portare a termine il lavoro.
TGarrett,

69
Il più veloce non è la cosa più importante per un'azienda. La manutenibilità è molto più "rapida" rispetto ai risparmi su questo codice nel 99% dei casi. Sto usando SequenceEqual e il mio intero codice è <1ms. Quei µs che stai salvando non sommeranno mai i 5 minuti di mancanza di leggibilità di P / Invoke.
PRMan,

236

I poteri P / Invoke si attivano!

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

48
P / Invoke yaay - questo si è rivelato di gran lunga il più veloce su bitmap almeno: stackoverflow.com/questions/2031217/…
Erik Forbes

25
Il pinning non è necessario in questo caso. Il marshaller esegue il pinning automatico quando chiama il codice nativo con PInvoke. Riferimento: stackoverflow.com/questions/2218444/…
Mark Glasgow

14
P / Invoke può suscitare fischi, ma è di gran lunga la più veloce di tutte le soluzioni presentate, inclusa un'implementazione che mi è venuta in mente che utilizza confronti non sicuri delle dimensioni di un puntatore. Esistono alcune ottimizzazioni che è possibile effettuare prima di chiamare il codice nativo, inclusa l'uguaglianza di riferimento e confrontare il primo e l'ultimo elemento.
Josh,

38
Perché il fischio? Poster voleva un'implementazione rapida e un confronto del linguaggio assembly ottimizzato non poteva essere battuto. Non so come ottenere un "REPE CMPSD" da .NET senza P / INVOKE.
Jason Goemaat,

14
Nitpick: MSVCR.dll non deve essere utilizzato dal codice utente. Per utilizzare MSVCR, è necessario distribuire il runtime utilizzando la versione distribuita. ( Msdn.microsoft.com/en-us/library/... e blogs.msdn.com/b/oldnewthing/archive/2014/04/11/10516280.aspx )
Mitch

160

C'è una nuova soluzione integrata per questo in .NET 4 - IStructuralEquatable

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

17
Secondo questo post del blog è in realtà molto lento.
Matt Johnson-Pint,

48
Pazzo lento. Circa 180 volte più lento del semplice ciclo.
Hafthor,

Funziona, ma non capisco perché. Un byte [] è un tipo primitivo che non implementa IStructuralEquatable, quindi perché puoi lanciarlo - e un cast implicito in quello! E quindi l'interfaccia "Equals" diventa magicamente disponibile ... da dove viene l'implementazione di quel metodo? Qualcuno può darmi un'idea?
Josh,

1
Perché non solo StructuralComparisons.StructuralEqualityComparer.Equals(a1, a2). NullReferenceExceptionQui no .
ta.speot.is

1
@ ta.speot.is Grazie, non posso litigare con una sola riga! La soluzione precedente era leggermente più efficiente poiché ha salvato il cast in IStructuralEquatable (un array è staticamente noto come IStructuralEquatable), ma in effetti i tuoi suggerimenti fanno funzionare il metodo anche per argomenti null.
Ohad Schneider,

76

L'utente gil ha suggerito un codice non sicuro che ha generato questa soluzione:

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

che esegue un confronto basato su 64 bit per il maggior numero possibile di array. Questo tipo di conta sul fatto che gli array iniziano qword allineati. Funzionerà se non qword allineato, ma non così veloce come se fosse.

Esegue circa sette timer più velocemente del semplice forloop. Utilizzo della libreria J # eseguita in modo equivalente al forloop originale . L'uso di .SequenceEqual viene eseguito circa sette volte più lentamente; Penso solo perché sta usando IEnumerator.MoveNext. Immagino che le soluzioni basate su LINQ siano almeno così lente o peggiori.


3
Bella soluzione. Ma un (piccolo) suggerimento: un confronto se i riferimenti a1 e a2 sono uguali può accelerare le cose se si dà lo stesso array per a1 e b1.
mmmmmmmm

12
Nuovi dati di test su .NET 4 x64: IStructualEquatable.equals ~ 180x più lento, SequenceEqual 15x più lento, SHA1 hash compare 11x più lento, bitconverter ~ stesso, non sicuro 7x più veloce, pinvoke 11x più veloce. Abbastanza fico che non sicuro è solo un po 'più lento di P / Invoke su memcmp.
Hafthor,

3
Questo link fornisce buoni dettagli sul perché l'allineamento della memoria è importante ibm.com/developerworks/library/pa-dalign - quindi, un'ottimizzazione potrebbe essere quella di verificare l'allineamento e se entrambi gli array non sono allineati dello stesso valore, fare un confronto dei byte fino a quando entrambi su un limite di qword.
Hafthor,

5
questo non darebbe falso quando sia a1 che a2 sono nulli?
nawfal,

2
@CristiDiaconescu Ho loopizzato la risposta di KevinDriedger. Quello che dovrei probabilmente fare è rendere disponibile la suite di test e i miei risultati su github e collegarmi ad esso sulla mia risposta.
Hafthor,

74

Span<T> offre un'alternativa estremamente competitiva senza dover gettare lanugine confuse e / o non portatili nella base di codice della propria applicazione:

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

L'implementazione (guts of the) a partire da .NET Core 3.1.0 è disponibile qui .

Ho rivisto l'essenza di @ EliArbel per aggiungere questo metodo come SpansEqual, eliminare la maggior parte degli artisti meno interessanti nei benchmark di altri, eseguirlo con diverse dimensioni di array, grafici di output e contrassegnare SpansEqualcome linea di base in modo che riporti come si confrontano i diversi metodi SpansEqual.

I numeri seguenti provengono dai risultati, leggermente modificati per rimuovere la colonna "Errore".

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

Sono stato sorpreso di vedere SpansEqualnon emergere in cima per i metodi di dimensione massima dell'array, ma la differenza è così piccola che non penso che avrà mai importanza.

Le mie informazioni di sistema:

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

Non avrei mai pensato di usare Span <T> o qualcosa di simile in tutto ciò che faccio. Grazie a te ora posso vantarmi di questo con i miei colleghi.
scherzo il

SequenceEqual è particolarmente implementato come metodo Span? Ho pensato che fosse solo uno dei metodi di estensione IEnumerable.
Zastai,

1
@Zastai sì, {ReadOnly,}Span<T>ha la sua versione di SequenceEqual(stesso nome perché ha lo stesso contratto del IEnumerable<T>metodo di estensione corrispondente , è solo più veloce). Si noti che {ReadOnly,}Span<T>non è possibile utilizzare i IEnumerable<T>metodi di estensione a causa delle restrizioni sui ref structtipi.
Joe Amenta,

1
@Sentinel il pacchetto System.Memory ha Span<T>implementazioni "portatili" / "lente" per netstandard1.1e oltre (quindi gioca con questo grafico interattivo per vedere quali sono). "Veloce" Span<T>è disponibile solo in .NET Core 2.1, in questo momento, ma nota che, nello SequenceEqual<T>specifico, dovrebbe esserci una minima differenza tra "veloce" e "lento" / "portatile" (sebbene gli netstandard2.0obiettivi dovrebbero vedere un leggero miglioramento perché avere il percorso del codice vettoriale).
Joe Amenta,

1
install-package system.memory
Chris Moschini,

30

Se non sei contrario, puoi importare l'assembly J # "vjslib.dll" e usare il suo metodo Arrays.equals (byte [], byte []) ...

Non incolparmi se qualcuno ti ride però ...


EDIT: Per quanto poco vale, ho usato Reflector per disassemblare il codice per questo, ed ecco come appare:

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

25

.NET 3.5 e versioni successive hanno un nuovo tipo pubblico, System.Data.Linq.Binaryche incapsula byte[]. Implementa IEquatable<Binary>che (in effetti) confronta due array di byte. Si noti che System.Data.Linq.Binaryha anche un operatore di conversione implicito da byte[].

Documentazione MSDN: System.Data.Linq.Binary

Decompilazione del riflettore del metodo 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;
}

La svolta interessante è che procedono al ciclo di confronto byte per byte solo se gli hash dei due oggetti binari sono uguali. Questo, tuttavia, ha il costo di calcolare l'hash nel costruttore di Binaryoggetti (attraversando l'array con forloop :-)).

L'implementazione di cui sopra significa che nel caso peggiore potrebbe essere necessario attraversare tre volte gli array: prima calcolare l'hash dell'array1, quindi calcolare l'hash dell'array2 e infine (poiché questo è lo scenario peggiore, lunghezza e hash uguali) per confrontare byte in array1 con byte in array 2.

Nel complesso, anche se System.Data.Linq.Binaryè integrato in BCL, non penso che sia il modo più veloce per confrontare array di due byte: - |.


20

Ho pubblicato una domanda simile sul controllo se byte [] è pieno di zero. (Il codice SIMD è stato battuto, quindi l'ho rimosso da questa risposta.) Ecco il codice più veloce dai miei confronti:

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

Misurato su due array di byte da 256 MB:

UnsafeCompare                           : 86,8784 ms
EqualBytesSimd                          : 71,5125 ms
EqualBytesSimdUnrolled                  : 73,1917 ms
EqualBytesLongUnrolled                  : 39,8623 ms

1
Confermo. Ho anche eseguito i test. Questo è più veloce della risposta che utilizza memcmp chiamata non sicura.
ujeenator,

1
@AmberdeBlack Sei sicuro? Hai provato con array di piccole dimensioni?
Zar Shardan,

@ArekBulski Sei sicuro che sia più veloce di memcmp, altrimenti i miei test mostreranno?
Zar Shardan,

Ho ottenuto prestazioni praticamente identiche tra questo e memcmp, quindi +1 per una soluzione completamente gestita.
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");
 }

1
Questo è quello che ho usato. Ma umm ... suona come un confronto sequenziale che altrimenti faresti usando un semplice loop, quindi non molto veloce. Sarebbe bello rifletterlo e vedere cosa sta realmente facendo. A giudicare dal nome, non è niente di speciale.
Sergey Akopov,

1
Sì, ma già menzionato nella risposta accettata. tra l'altro, è possibile rimuovere la specifica del tipo lì.
nawfal,

10

Aggiungiamo un altro!

Recentemente Microsoft ha rilasciato uno speciale pacchetto NuGet, System.Runtime.CompilerServices.Unsafe . È speciale perché è scritto in IL e offre funzionalità di basso livello non direttamente disponibili in C #.

Uno dei suoi metodi Unsafe.As<T>(object)consente di trasmettere qualsiasi tipo di riferimento a un altro tipo di riferimento, saltando eventuali controlli di sicurezza. Questo è di solito una molto cattiva idea, ma se entrambi i tipi hanno la stessa struttura, può funzionare. Quindi possiamo usarlo per trasmettere byte[]a a 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;
}

Si noti che long1.Lengthrestituirebbe comunque la lunghezza dell'array originale, poiché è memorizzato in un campo nella struttura di memoria dell'array.

Questo metodo non è così veloce come altri metodi dimostrati qui, ma è molto più veloce del metodo ingenuo, non utilizza codice non sicuro o P / Invoke o pinning e l'implementazione è piuttosto semplice (IMO). Ecco alcuni risultati di BenchmarkDotNet dalla mia macchina:

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 |

Ho anche creato una sintesi con tutti i test .


Non utilizza la parola chiave non sicura, ma chiama comunque un codice non sicuro utilizzando System.Runtime.CompilerServices.Unsafe
Paulo Zemek,

Ho aggiornato la mia NewMemCmprisposta per utilizzare AVX-2
Mr Anderson,

8

Ho sviluppato un metodo che batte leggermente memcmp()(la risposta del plinto) e batte molto leggermente EqualBytesLongUnrolled()(la risposta di Arek Bulski) sul mio PC. Fondamentalmente, si svolge il ciclo di 4 invece di 8.

Aggiornamento 30 marzo 2019 :

A partire da .NET core 3.0, abbiamo il supporto SIMD!

Questa soluzione è più veloce di un notevole margine sul mio 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;
}

Le mie misure differiscono per .NET 462 dal NETCORE:
Motlicek Petr

Il codice si arresta in modo anomalo quando si confrontano due matrici di lunghezza 0, perché viene restituito il blocco null.
Glenn Slayden,

memcmp non è solo un comparatore di azioni. Fornisce informazioni sull'oggetto più grande o più piccolo. Puoi adottare il tuo algoritmo per questo scopo e controllare le prestazioni?
nicolay.anykienko,

È più veloce di Spane memcpy?
Silkfire

@silkfire Su .NET core 3 e CPU moderna, dovrebbe essere 2-3 volte più veloce per array di grandi dimensioni.
Mr Anderson,

6

Vorrei utilizzare un codice non sicuro ed eseguire il forciclo confrontando i puntatori Int32.

Forse dovresti anche considerare di controllare gli array come non nulli.


5

Se guardi come .NET fa string.Equals, vedi che usa un metodo privato chiamato EqualsHelper che ha un'implementazione del puntatore "non sicura". .NET Reflector è tuo amico per vedere come vengono fatte le cose internamente.

Questo può essere usato come modello per il confronto di array di byte su cui ho fatto un'implementazione nel post sul blog Confronto di array di byte veloce in C # . Ho anche fatto alcuni benchmark rudimentali per vedere quando un'implementazione sicura è più veloce di quella non sicura.

Detto questo, a meno che tu non abbia davvero bisogno di prestazioni killer, farei un semplice confronto tra i loop.


3

Non sono riuscito a trovare una soluzione di cui sono completamente soddisfatto (prestazioni ragionevoli, ma nessun codice / pinvoke non sicuro), quindi ho trovato questo, niente di veramente originale, ma funziona:

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

Prestazioni rispetto ad alcune delle altre soluzioni in questa pagina:

Loop semplice: 19837 tick, 1,00

* BitConverter: 4886 tick, 4.06

UnsafeCompare: 1636 tick, 12.12

EqualBytesLongUnrolled: 637 tick, 31.09

P / Invoke memcmp: 369 tick, 53.67

Testato in linqpad, array identici da 1000000 byte (scenario peggiore), 500 iterazioni ciascuno.


sì, ho notato che nel commento di stackoverflow.com/a/1445280/4489 che i miei test mostrano che questo è in realtà un po 'più lento del semplice ciclo per loop che avevo nella domanda originale.
Hafthor,

sei sicuro? Nel mio test è 4 volte più veloce? Nulla batte comunque il buon vecchio codice nativo, anche con il marshalling.
Zar Shardan,

3

Sembra che EqualBytesLongUnrolled sia il migliore tra quelli suggeriti sopra.

I metodi ignorati (Enumerable.SequenceEqual, StructuralComparisons.StructuralEqualityComparer.Equals), non erano pazienti per lenti. Su array da 265 MB ho misurato questo:

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 |

Ho aggiornato la mia NewMemCmprisposta per utilizzare AVX-2
Mr Anderson,

3

Non ho visto molte soluzioni Linq qui.

Non sono sicuro delle implicazioni sulle prestazioni, tuttavia generalmente mi attengo a linqcome regola empirica e quindi ottimizzo in seguito, se necessario.

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

Si noti che funziona solo se hanno matrici della stessa dimensione. un'estensione potrebbe apparire così

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

L'intero punto della domanda è una soluzione più veloce che la funzione ha pubblicato nella domanda.
CodesInChaos

3

Ho fatto alcune misurazioni usando il programma allegato .net 4.7 build build senza il debugger collegato. Penso che le persone stiano usando la metrica sbagliata da quando ti preoccupi della velocità qui è quanto tempo ci vuole per capire se le matrici a due byte sono uguali. cioè throughput in byte.

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

Come puoi vedere, non c'è modo migliore di memcmped è più veloce degli ordini di grandezza. Un semplice forloop è la seconda opzione migliore. E mi fa ancora capire perché Microsoft non può semplicemente includere un Buffer.Comparemetodo.

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

2

Per confrontare array di byte corti il ​​seguente è un trucco interessante:

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

Quindi probabilmente caderei alla soluzione elencata nella domanda.

Sarebbe interessante fare un'analisi delle prestazioni di questo codice.


int i = 0; per (; i <a1.Length-7; i + = 8) if (BitConverter.ToInt64 (a1, i)! = BitConverter.ToInt64 (a2, i)) restituisce false; per (; i <a1.Length; i ++) if (a1 [i]! = a2 [i]) restituisce false; ritorno vero; // un po 'più lento di un semplice ciclo.
Hafthor,

2

Per quelli di voi che hanno a cuore l' ordine (cioè che vogliono memcmprestituire un prodotto intcome dovrebbe invece che niente), .NET Core 3.0 (e presumibilmente .NET Standard 2.1 aka .NET 5.0) includerà un Span.SequenceCompareTo(...)metodo di estensione (più un Span.SequenceEqualTo) che può essere utilizzato per confrontare due ReadOnlySpan<T>istanze ( where T: IComparable<T>).

Nella proposta originale di GitHub , la discussione includeva confronti di approccio con calcoli di jump table, lettura di un byte[]as long[], utilizzo SIMD e p / invoke alle implementazioni CLR memcmp.

Andando avanti, questo dovrebbe essere il tuo metodo di riferimento per confrontare le matrici o gli intervalli di byte (come dovrebbe usare Span<byte>invece delle byte[]API .NET Standard 2.1), ed è sufficientemente veloce da non doverti più preoccupare di ottimizzarlo (e no, nonostante le somiglianze nel nome non si comporta in modo abissale come l'orrido 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

1

Ho pensato ai metodi di accelerazione del trasferimento a blocchi integrati in molte schede grafiche. Ma poi dovresti copiare su tutti i dati byte-saggio, quindi questo non ti aiuta molto se non vuoi implementare un'intera porzione della tua logica in codice non gestito e dipendente dall'hardware ...

Un altro modo di ottimizzazione simile all'approccio mostrato sopra sarebbe quello di archiviare il maggior numero possibile di dati in un lungo [] anziché in un byte [] fin dall'inizio, ad esempio se li stai leggendo in sequenza da un file binario, o se si utilizza un file mappato in memoria, leggere i dati come long [] o singoli valori long. Quindi, il tuo ciclo di confronto avrà bisogno solo di 1/8 del numero di iterazioni che dovrebbe fare per un byte [] contenente la stessa quantità di dati. Si tratta di quando e con quale frequenza è necessario confrontare rispetto a quando e con quale frequenza è necessario accedere ai dati in modo byte per byte, ad esempio per usarli in una chiamata API come parametro in un metodo che prevede un byte []. Alla fine, puoi solo sapere se conosci davvero il caso d'uso ...


La risposta accettata riformula il buffer di byte come buffer lungo e lo confronta come descritto.
Hafthor,

1

Questo è quasi certamente molto più lento di qualsiasi altra versione fornita qui, ma è stato divertente scriverlo.

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

1

Ho optato per una soluzione ispirata al metodo EqualBytesLongUnrolled pubblicato da ArekBulski con un'ulteriore ottimizzazione. Nel mio caso, le differenze di matrice negli array tendono ad essere vicino alla coda degli array. Durante i test, ho scoperto che quando questo è il caso di array di grandi dimensioni, la possibilità di confrontare gli elementi dell'array in ordine inverso offre a questa soluzione un enorme vantaggio in termini di prestazioni rispetto alla soluzione basata su memcmp. Ecco quella soluzione:

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

0

Siamo spiacenti, se stai cercando un modo gestito, lo stai già facendo correttamente e per quanto ne so, nel BCL non esiste un metodo integrato per farlo.

È necessario aggiungere alcuni controlli null iniziali e quindi riutilizzarli come se fossero in BCL.


Avevi ragione quando hai scritto che, tuttavia nel 2010 (.NET 4.0) è arrivato un metodo BCL, vedi la risposta di Ohad Schneider. Al momento della domanda, .NET 3.5 aveva Linq (vedi la risposta di Aku).
Jeppe Stig Nielsen,

-1

Utilizzare SequenceEqualsper questo per il confronto.


-2

Se stai cercando un comparatore di uguaglianza di array di byte molto veloce, ti suggerisco di dare un'occhiata a questo articolo di STSdb ​​Labs: Comparatore di uguaglianza di array di byte. Presenta alcune delle implementazioni più veloci per il confronto di uguaglianza di array byte [], che vengono presentate, testate e riassunte in termini di prestazioni.

Puoi anche concentrarti su queste implementazioni:

BigEndianByteArrayComparer - Byte veloce [] array di confronto da sinistra a destra (BigEndian) BigEndianByteArrayEqualityComparer - - Byte veloce [] uguaglianza di confronto da sinistra a destra (BigEndian) LittleEndianByteArrayComparer - Byte veloce [] array di confronto da destra a sinistra (LittleEndian) LittleEndianByteArrayEqualityComparer - Byte veloce [] comparatore di uguaglianza da destra a sinistra (LittleEndian)


-2

La risposta breve è questa:

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

In tal modo è possibile utilizzare il confronto di stringhe .NET ottimizzato per confrontare un array di byte senza la necessità di scrivere codice non sicuro. Ecco come viene eseguito in background :

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

Nei miei test, la conversione in una stringa distrugge il vantaggio del confronto più veloce. Questo è stato circa 2,5 volte più lento di un semplice ciclo.
Doug Clutter,

Quando ho fatto lo stesso, il semplice per era circa 8 volte più lento. Puoi scrivere il tuo codice qui?
Alon,

1
Questo si interromperà se un byte contiene un valore null (0)?
Joseph Lennox,

-1 Oltre ad essere lento a causa della conversione in stringa come sottolineato da @DougClutter, questo fallirà se l'array di byte contiene dati non ASCII. Per ottenere il risultato giusto dovrebbe usare iso-8859-1.
Joe,

2
Compare(new byte[]{128}, new byte[]{ 255 }) == trueper niente buggy ...
CodesInChaos

-2

Dal momento che molte delle fantastiche soluzioni di cui sopra non funzionano con UWP e poiché amo Linq e gli approcci funzionali, ho insistito sulla mia versione di questo problema. Per sfuggire al confronto quando si verifica la prima differenza, ho scelto .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 perché questo codice è rotto e apparentemente non testato. Ciò genera IndexOutOfRangeExceptionquando si confrontano le matrici non vuoti, perché si sta accedendo elementi 1attraverso ba0.Lengthquando dovrebbe essere 0attraverso ba0.Length - 1. Se lo risolvi con Enumerable.Range(0, ba0.Length)esso ritorna ancora erroneamente trueper matrici di uguale lunghezza in cui solo i primi elementi differiscono perché non puoi distinguere tra i primi elementi soddisfacenti predicatee nessun elemento soddisfacente predicate; FirstOrDefault<int>()ritorna 0in entrambi i casi.
BACON,

La lezione qui per bambini: non portare un coltello in uno scontro a fuoco
Richard Hauer
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.