Domanda

Come posso farlo velocemente?

Certo 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 uno dei due BCL funzione o un modo collaudato altamente ottimizzato per farlo.

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

funziona bene, ma non sembra che funzioni per x64.

Nota la mia risposta super veloce Qui.

È stato utile?

Soluzione 4

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 la maggior parte possibile dell'array.Questo tipo di conta sul fatto che gli array iniziano con qword allineati.Funzionerà se non è allineato con qword, ma non così velocemente come se lo fosse.

Esegue circa sette timer più velocemente del semplice for ciclo continuo.L'utilizzo della libreria J# è stato eseguito in modo equivalente all'originale for ciclo continuo.L'utilizzo di .SequenceEqual viene eseguito circa sette volte più lentamente;Penso solo perché utilizza IEnumerator.MoveNext.Immagino che le soluzioni basate su LINQ siano almeno così lente o peggio.

Altri suggerimenti

Puoi usare Enumerable.SequenceEqual metodo.

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 per qualche motivo non puoi utilizzare .NET 3.5, il tuo metodo è OK.
L'ambiente compilatore untime ottimizzerà il tuo ciclo in modo da non doverti preoccupare delle prestazioni.

P/Invoca i poteri 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;
}

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

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

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

Non incolpare me se qualcuno ride di te però...


MODIFICARE:Per quel poco che vale, ho usato Reflector per disassemblare il codice, 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;
}

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

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

È possibile trovare l'implementazione (dell'interno) a partire da .NET Core 2.2.3 Qui.

Io ho rivisto L'essenza di @EliArbel per aggiungere questo metodo come SpansEqual, elimina la maggior parte degli artisti meno interessanti nei benchmark degli altri, eseguilo con dimensioni di array, grafici di output diversi e contrassegna SpansEqual come riferimento in modo da segnalare il confronto tra 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.813 ns |         0.0043 ns |  1.00 |
|  LongPointers |         15 |           4.768 ns |         0.0081 ns |  1.25 |
|      Unrolled |         15 |          17.763 ns |         0.0319 ns |  4.66 |
| PInvokeMemcmp |         15 |          12.280 ns |         0.0221 ns |  3.22 |
|               |            |                    |                   |       |
|    SpansEqual |       1026 |          29.181 ns |         0.0461 ns |  1.00 |
|  LongPointers |       1026 |          63.050 ns |         0.0785 ns |  2.16 |
|      Unrolled |       1026 |          39.070 ns |         0.0412 ns |  1.34 |
| PInvokeMemcmp |       1026 |          44.531 ns |         0.0581 ns |  1.53 |
|               |            |                    |                   |       |
|    SpansEqual |    1048585 |      43,838.865 ns |        56.7144 ns |  1.00 |
|  LongPointers |    1048585 |      59,629.381 ns |       194.0304 ns |  1.36 |
|      Unrolled |    1048585 |      54,765.863 ns |        34.2403 ns |  1.25 |
| PInvokeMemcmp |    1048585 |      55,250.573 ns |        49.3965 ns |  1.26 |
|               |            |                    |                   |       |
|    SpansEqual | 2147483591 | 247,237,201.379 ns | 2,734,143.0863 ns |  1.00 |
|  LongPointers | 2147483591 | 241,535,134.852 ns | 2,720,870.8915 ns |  0.98 |
|      Unrolled | 2147483591 | 240,170,750.054 ns | 2,729,935.0576 ns |  0.97 |
| PInvokeMemcmp | 2147483591 | 238,953,916.032 ns | 2,692,490.7016 ns |  0.97 |

Sono stato sorpreso di vedere SpansEqual non risulta migliore per i metodi con dimensione massima dell'array, ma la differenza è così minima che non credo che avrà mai importanza.

Informazioni sul mio sistema:

BenchmarkDotNet=v0.11.5, OS=Windows 10.0.17134.706 (1803/April2018Update/Redstone4)
Intel Core i7-6850K CPU 3.60GHz (Skylake), 1 CPU, 12 logical and 6 physical cores
Frequency=3515626 Hz, Resolution=284.4444 ns, Timer=TSC
.NET Core SDK=2.2.202
  [Host]     : .NET Core 2.2.3 (CoreCLR 4.6.27414.05, CoreFX 4.6.27414.05), 64bit RyuJIT
  DefaultJob : .NET Core 2.2.3 (CoreCLR 4.6.27414.05, CoreFX 4.6.27414.05), 64bit RyuJIT

.NET 3.5 e versioni successive hanno un nuovo tipo pubblico, System.Data.Linq.Binary che incapsula byte[].Implementa IEquatable<Binary> che (in effetti) confronta due array di byte.Notare che System.Data.Linq.Binary ha anche l'operatore di conversione implicito from 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 gli stessi.Ciò, tuttavia, ha il costo di calcolare l'hash nel costruttore di Binary oggetti (attraversando l'array con for ciclo continuo :-) ).

L'implementazione di cui sopra significa che nel peggiore dei casi potresti dover attraversare gli array tre volte:prima per calcolare l'hash dell'array1, quindi per calcolare l'hash dell'array2 e infine (poiché questo è lo scenario peggiore, lunghezze e hash uguali) per confrontare i byte nell'array1 con i byte nell'array 2.

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

ho pubblicato una domanda simile sul controllo se byte[] è pieno di zeri.(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
 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");
 }

Aggiungiamone un altro!

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

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

Notare che long1.Length restituirebbe comunque la lunghezza dell'array originale, poiché è archiviata in un campo nella struttura di memoria dell'array.

Questo metodo non è veloce come altri metodi qui dimostrati, ma è molto più veloce del metodo ingenuo, non utilizza codice non sicuro o P/Invoke o blocco e l'implementazione è abbastanza semplice (IMO).Eccotene alcune BenchmarkDotNet risultati 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 un file riassumere tutti i test.

Ho sviluppato un metodo che batte leggermente memcmp() (risposta di Plinth) e batte molto leggermente EqualBytesLongUnrolled() (risposta di Arek Bulski) sul mio PC.Fondamentalmente, srotola il loop di 4 anziché di 8.

Aggiornamento 30 marzo.2019:

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

Questa soluzione è più veloce con un margine considerevole 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;
}

Vorrei utilizzare codice non sicuro ed eseguire il file for ciclo che confronta i puntatori Int32.

Forse dovresti anche considerare di controllare che gli array siano diversi da null.

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

Questo può essere utilizzato come modello per il confronto degli array di byte di cui ho eseguito un'implementazione nel post del blog Confronto rapido degli array di byte in C#.Ho anche eseguito alcuni benchmark rudimentali per vedere quando un'implementazione sicura è più veloce di un'implementazione non sicura.

Detto questo, a meno che tu non abbia davvero bisogno di prestazioni eccezionali, opterei per un semplice confronto in loop fr.

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:

Ciclo semplice:19837 tick, 1,00

*Convertitore Bit:4886 tick, 4.06

Non sicuroConfronta:1636 tick, 12.12

EqualBytesLongUnrolled:637 tick, 31.09

P/Invoca memcmp:369 tick, 53,67

Testato su linqpad, array identici da 1.000.000 byte (scenario peggiore), 500 iterazioni ciascuno.

Sembra che EqualBytesLongUnrolled è il migliore tra quelli suggeriti sopra.

I metodi saltati (Enumerable.SequenceEqual,StructuralComparisons.StructuralEqualityComparer.Equals) non erano pazienti per lentezza.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 |

Non ho visto molte soluzioni Linq qui.

Non sono sicuro delle implicazioni sulle prestazioni, tuttavia generalmente mi attengo linq come regola generale e poi ottimizzare in seguito, se necessario.

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

Tieni presente che funziona solo se hanno array 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();
 }

Ho eseguito alcune misurazioni utilizzando la build di rilascio .net 4.7 del programma allegato senza il debugger collegato.Penso che le persone abbiano utilizzato la metrica sbagliata poiché quello che ti interessa se ti interessa la velocità qui è quanto tempo ci vuole per capire se due array di 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 memcmp ed è molto più veloce di ordini di grandezza.Un semplice for loop è la seconda migliore opzione.E mi stupisce ancora il motivo per cui Microsoft non possa semplicemente includere un file Buffer.Compare metodo.

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

Per confrontare array di byte brevi, 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 opterei per la soluzione elencata nella domanda.

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

Ho pensato ai metodi di accelerazione del trasferimento a blocchi integrati in molte schede grafiche.Ma poi dovresti copiare tutti i dati in termini di byte, quindi questo non ti aiuta molto se non vuoi implementare un'intera parte 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 quanti più dati possibile in un long[] anziché in un byte[] fin dall'inizio, ad esempio se li stai leggendo in sequenza da un file binario, oppure, se si utilizza un file mappato in memoria, leggere i dati come long[] o singoli valori long.Quindi, il tuo ciclo di confronto richiederà solo 1/8 del numero di iterazioni che dovrebbe fare per un byte[] contenente la stessa quantità di dati.È una questione di quando e quanto spesso è necessario confrontare rispetto aquando e quanto spesso è necessario accedere ai dati byte per byte, ad es.per utilizzarlo in una chiamata API come parametro in un metodo che prevede un byte[].Alla fine, puoi dire solo se conosci davvero il caso d'uso...

Questa è quasi certamente molto più lenta di qualsiasi altra versione qui fornita, ma è stato divertente scriverla.

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

Ho optato per una soluzione ispirata al metodo EqualBytesLongUnrolled pubblicato da ArekBulski con un'ulteriore ottimizzazione.Nel mio caso, le differenze di array negli array tendono ad essere vicine 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 miglioramento delle prestazioni rispetto alla soluzione basata su memcmp.Ecco la 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;
}

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

Dovresti aggiungere alcuni controlli nulli iniziali e quindi riutilizzarli come se fossero in BCL.

Utilizzo SequenceEquals per questo confronto.

La risposta breve è questa:

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

In questo 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 si fa nel sfondo:

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

Poiché molte delle soluzioni fantasiose di cui sopra non funzionano con UWP e poiché amo Linq e gli approcci funzionali, ti presento la 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);

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: Confronto dell'uguaglianza dell'array di byte. Presenta alcune delle implementazioni più veloci per il confronto dell'uguaglianza degli array di byte[], che vengono presentate, testate sulle prestazioni e riepilogate.

Puoi anche concentrarti su queste implementazioni:

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

Se hai un enorme array di byte, puoi confrontarli convertendoli in stringhe.

Puoi usare qualcosa del genere

byte[] b1 = // Your array
byte[] b2 = // Your array
string s1 = Encoding.Default.GetString( b1 );
string s2 = Encoding.Default.GetString( b2 );

L'ho usato e ho visto un enorme impatto sulle prestazioni.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top