.NET'te iki bayt dizisini karşılaştırma


541

Bunu nasıl hızlı yapabilirim?

Tabii bunu yapabilirim:

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

Ama ya bir BCL işlevi ya da bunu yapmak için son derece optimize edilmiş kanıtlanmış bir yol arıyorum .

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

güzel çalışıyor, ancak x64 için işe yaramayacak gibi görünmüyor.

Benim süper hızlı cevap Not burada .


1
"Bu, dizilerin kelime dizgisine göre başlamasına güveniyor." Bu büyükse. Kodu yansıtacak şekilde düzeltmelisiniz.
Joe Chung

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

@OhadSchneider hakkında cevap verdimIStructuralEquatable
LCJ

Yanıtlar:


613

Enumerable.SequenceEqual yöntemini kullanabilirsiniz .

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

Herhangi bir nedenle .NET 3.5'i kullanamıyorsanız, yönteminiz sorun yok.
Derleyici \ çalışma zamanı ortamı, döngünüzü optimize eder, böylece performans konusunda endişelenmenize gerek kalmaz.


4
Ancak SequenceEqual'in işlenmesi güvenli olmayan bir karşılaştırmadan daha uzun sürmez mi? Özellikle 1000 karşılaştırma yaparken?
tcables

90
Evet, güvenli olmayan karşılaştırmadan yaklaşık 50 kat daha yavaş çalışır.
Hafthor

27
Bu gerçekten burada ölüleri yükseltiyor, ama yavaş burada kullanmak için gerçekten kötü bir kelime. 50x daha yavaş sesler kötüdür, ancak genellikle bir fark yaratmak için yeterli veriyi karşılaştırmazsınız ve eğer öyleyse, bunu çok sayıda nedenden ötürü gerçekten kendi durumunuz için karşılaştırmanız gerekir. Örneğin, güvensiz cevabın yaratıcısının 50x yavaşlamanın aksine 7x yavaş fark olduğuna dikkat edin (güvenli olmayan yöntemin hızı da verilerin hizalanmasına bağlıdır). Bu sayıların önemli olduğu nadir durumlarda P / Invoke daha da hızlıdır.
Selali Adobor

4
Yavaş uygulama 300'den fazla beğeni mi alıyor? Bu işi halletmek için en hızlı yolu olacağını msvcrt.dll çengel öneririz.
TGarrett

69
Bir işletme için en hızlı şey en önemli şey değildir. Sürdürülebilirlik, bu koddaki tasarrufların% 99 oranında olacağından çok daha hızlıdır. SequenceEqual kullanıyorum ve tüm kod <1 ms. Kaydettiğiniz bu µs asla P / Invoke'un okunabilirliğinin 5 dakikasına kadar eklemeyecektir.
PRMan

236

P / Invoke yetkileri etkin!

[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 - bunun en azından bitmap'lerde en hızlı olduğu kanıtlandı: stackoverflow.com/questions/2031217/…
Erik Forbes

25
Bu durumda sabitleme gerekli değildir. Mareşaller, PInvoke ile yerel kodu çağırırken otomatik sabitleme gerçekleştirir. Referans: stackoverflow.com/questions/2218444/…
Mark Glasgow

14
P / Invoke, boos ortaya çıkarabilir, ancak güvensiz işaretçi boyutu karşılaştırmaları kullanan bir uygulama da dahil olmak üzere sunulan tüm çözümlerin en hızlısıdır. Referans eşitliği ve ilk ve son öğeleri karşılaştırma gibi yerel koda çağrı yapmadan önce yapabileceğiniz birkaç optimizasyon vardır.
Josh

38
Neden boo? Poster hızlı bir uygulama istedi ve optimize edilmiş bir montaj dili karşılaştırması yenemez. P / INVOKE olmadan .NET dışında bir "REPE CMPSD" almak nasıl bilmiyorum.
Jason Goemaat

14
Nitpick: MSVCR.dll kullanıcı kodu tarafından kullanılmaz. MSVCR'yi kullanmak için, dağıttığınız sürümü kullanarak çalışma zamanını dağıtmanız gerekir. ( msdn.microsoft.com/en-us/library/… ve blogs.msdn.com/b/oldnewthing/archive/2014/04/11/10516280.aspx )
Mitch

160

.NET 4'te bunun için yeni bir yerleşik çözüm var - IStructuralEquatable

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

17
Bu blog yazısına göre bu aslında çok yavaş.
Matt Johnson-Pint

48
Çok yavaş. Döngü için basit olandan yaklaşık 180 kat daha yavaş.
Hafthor

Bu işe yarıyor, ama nedenini anlamıyorum. Bir bayt [], IStructuralEquatable'ı uygulamayan ilkel bir türdür, bu yüzden neden onu yayınlayabilirsiniz - ve buna dolaylı bir döküm! Ve sonra arayüz "Eşittir" yöntemi sihirli bir şekilde kullanılabilir hale gelir ... bu yöntemin uygulanması nereden geliyor? Birisi beni arayabilir mi?
Josh

1
Neden sadece StructuralComparisons.StructuralEqualityComparer.Equals(a1, a2). NullReferenceExceptionBurada yok .
ta.speot.

1
@ ta.speot.is Teşekkürler, bir astarla tartışamazsınız! Önceki çözüm, cast'ı IStructuralEquatable'a (bir dizi statik olarak IStructuralEquatable olarak bilinir) kaydettiği için biraz daha verimliydi, ancak aslında önerileriniz de yöntemi null argümanlar için çalıştırıyor.
Ohad Schneider

76

Kullanıcı gil , bu çözümü ortaya çıkaran güvenli olmayan bir kod önerdi:

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

64-bit tabanlı karşılaştırmayı mümkün olduğunca dizi için yapar. Bu tür, dizilerin qword ile hizalanmış olmasına güvenir. Eğer qword hizalanmamışsa, o kadar hızlı olmaz.

Basit fordöngüden yaklaşık yedi zamanlayıcı daha hızlı çalışır . Orijinal fordöngüye eşdeğer olarak gerçekleştirilen J # kitaplığını kullanma . .SequenceEqual kullanımı yaklaşık yedi kat daha yavaştır; Ben sadece IEnumerator.MoveNext kullanıyor çünkü düşünüyorum. LINQ tabanlı çözümlerin en azından bu kadar yavaş veya daha kötü olduğunu düşünüyorum.


3
Güzel çözüm. Ancak bir (küçük) ipucu: a1 ve a2'nin eşit olması durumunda karşılaştırma, a1 ve b1 için aynı diziyi verirse işleri hızlandırabilir.
mmmmmmmm

12
.NET 4 x64 sürümündeki yeni test verileri: IStructualEquatable.equals ~ 180x daha yavaş, SequenceEqual 15x daha yavaş, SHA1 karma 11x daha yavaş, bitconverter ~ aynı, 7x daha hızlı güvensiz, 11 kat daha hızlı pinvoke. Güvensiz olanın memcmp'ta P / Invoke'dan biraz daha yavaş olması çok güzel.
Hafthor

3
Bu bağlantı, bellek hizalamasının ibm.com/developerworks/library/pa-dalign ile neden önemli olduğu konusunda iyi ayrıntılar verir; bu nedenle, hizalamayı kontrol etmek için bir optimizasyon olabilir ve her iki dizi de aynı miktarda hizalama yapmıyorsa, bayt karşılaştırma bir qword sınırında.
Hafthor

5
Hem a1 hem de a2 boş olduğunda bu yanlış olmaz mı?
nawfal

2
@CristiDiaconescu KevinDriedger'in cevabını işaretledim. Muhtemelen yapmam gereken test paketini ve sonuçlarımı github'da kullanılabilir hale getirmek ve cevabımda ona bağlanmak.
Hafthor

74

Span<T> kendi uygulamanızın kod tabanına kafa karıştırıcı ve / veya taşınabilir olmayan tüy atmak zorunda kalmadan son derece rekabetçi bir alternatif sunar:

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

.NET Core 3.1.0'dan itibaren (cesaret) uygulaması burada bulunabilir .

Ben ettik revize bu şekilde ekleme yöntemine EliArbel en özünden @ SpansEqual, farklı dizi boyutları, çıkış grafikleri ile çalıştırın, başkalarının kriterler daha az ilginç sanatçıların çoğu bırakın ve iz SpansEqualfarklı yöntemler nasıl karşılaştırılacağı bildiriyor böylece temel olarak SpansEqual.

Aşağıdaki sayılar, "Hata" sütununu kaldırmak için hafifçe düzenlenmiş sonuçlardan alınmıştır.

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

Ben SpansEqualmaksimum dizi boyutu yöntemleri için üst çıkmıyor görmek şaşırdı , ama fark o kadar küçük ki hiç önemli olacağını sanmıyorum.

Sistem bilgilerim:

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

Yaptığım tüm şeylerde Span <T> ya da ona yakın bir şey kullanacağımı hiç düşünmemiştim. Sayende şimdi iş arkadaşlarıma bu konuda övünebilirim.
jokab

SequenceEqual özellikle Span yöntemi olarak uygulandı mı? Bunun IEnumerable genişletme yöntemlerinden sadece biri olduğunu düşündüm.
Zastai

1
@Zastai evet, {ReadOnly,}Span<T>kendi sürümüne sahiptir SequenceEqual(karşılık gelen IEnumerable<T>uzantı yöntemiyle aynı sözleşmeye sahip olduğu için aynı ad , sadece daha hızlıdır). Türlerdeki kısıtlamalar nedeniyle uzantı yöntemlerini {ReadOnly,}Span<T>kullanamayacağını unutmayın . IEnumerable<T>ref struct
Joe Amenta

1
@Sentinel the System.Memory paketinde ve üstünde "taşınabilir" / "yavaş" Span<T>uygulamalar vardır netstandard1.1( bunların hangileri olduğunu görmek için bu etkileşimli grafikle oynayın ). "Hızlı" Span<T>şu anda yalnızca .NET Core 2.1'de mevcuttur, ancak SequenceEqual<T>özellikle "hızlı" ve "yavaş" / "taşınabilir" arasında çok az fark olması gerektiğini unutmayın (ancak netstandard2.0hedefler, vectorized kod yoluna sahip).
Joe Amenta

1
install-package system.memory
Chris Moschini

30

Bunu yapmak istemiyorsanız, J # assembly "vjslib.dll" dosyasını içe aktarabilir ve Arrays.equals (bayt [], bayt []) yöntemini kullanabilirsiniz ...

Biri sana gülerse beni suçlama ...


DÜZENLEME: Ne kadar az değer için, bunun için kodu sökmek için Reflektör kullandım ve işte şöyle görünüyor:

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 ve daha yeni sürümlerde System.Data.Linq.Binary, kapsül oluşturan yeni bir genel tür vardır byte[]. Bu uygular IEquatable<Binary>(etki), iki bayt dizileri karşılaştırır. System.Data.Linq.BinaryAyrıca örtük dönüşüm operatörü olduğunu unutmayın byte[].

MSDN belgeleri: System.Data.Linq.Binary

Eşittir yönteminin reflektör kodası:

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

İlginç bir bükülme, sadece iki İkili nesnenin hash'leri aynı olduğunda bayt-byte-byte karşılaştırma döngüsüne geçmeleridir. Bununla birlikte, bu, Binarynesnelerin yapıcısındaki karma değerini hesaplama maliyetiyle gelir (diziyi fordöngü ile gezerek :-)).

Yukarıdaki uygulama, en kötü durumda dizileri üç kez geçmeniz gerekebileceği anlamına gelir: önce dizi1 karma değerini hesaplamak, sonra dizi2 karma değerini hesaplamak ve son olarak (çünkü bu en kötü durum senaryosu, uzunluklar ve karma değerleri eşittir) dizi1'deki baytlar, dizi 2'deki baytlarla.

Genel olarak, System.Data.Linq.BinaryBCL'de yerleşik olmasına rağmen , iki bayt dizisini karşılaştırmanın en hızlı yolu olduğunu düşünmüyorum: - |


20

Bayt [] 'ın sıfırlarla dolu olup olmadığını kontrol etme hakkında benzer bir soru yayınladım . (SIMD kodu dövüldü bu yüzden bu cevap kaldırıldı.) İşte benim karşılaştırmaları en hızlı kodu:

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

İki 256 MB bayt dizisinde ölçülmüştür:

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

1
Onaylıyorum. Testleri de yaptım. Bu memcmp güvensiz arama kullanan cevaptan daha hızlıdır.
ujeenator

1
@AmberdeBlack Emin misin? Küçük dizilerle test ettiniz mi?
Zar Shardan

@ArekBulski Bunun memcmp'den daha hızlı olduğundan emin misiniz, testlerim başka türlü gösteriliyor mu?
Zar Shardan

Bu ve memcmp arasında neredeyse aynı performans aldım, bu yüzden tam olarak yönetilen bir çözüm için +1.
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
Ben de bunu kullanıyorum. Ama umm ... basit bir döngü kullanarak yapacağınız sıralı bir karşılaştırma gibi geliyor, bu yüzden çok hızlı değil. Onu yansıtmak ve gerçekte ne yaptığını görmek güzel olurdu. İsme bakılırsa, fantezi bir şey değil.
Sergey Akopov

1
Evet, ancak kabul edilen cevapta zaten belirtilmiş. btw, burada tür belirtimini kaldırabilirsiniz.
nawfal

10

Bir tane daha ekleyelim!

Microsoft son zamanlarda System.Runtime.CompilerServices.Unsafe adlı özel bir NuGet paketi yayınladı . Özeldir çünkü IL'de yazılmıştır ve doğrudan C # 'da bulunmayan düşük seviyeli işlevsellik sağlar.

Yöntemlerinden biri, Unsafe.As<T>(object)herhangi bir referans türünün başka bir referans türüne dökülmesine izin verir ve güvenlik kontrollerini atlar. Bu genellikle çok kötü bir fikirdir, ancak her iki tip de aynı yapıya sahipse işe yarayabilir. Bu yüzden, a'yı byte[]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;
}

long1.LengthDizinin bellek yapısındaki bir alanda saklandığından, orijinal dizinin uzunluğunu döndüreceğini unutmayın .

Bu yöntem, burada gösterilen diğer yöntemler kadar hızlı değildir, ancak saf yöntemden çok daha hızlıdır, güvenli olmayan kod veya P / Invoke veya sabitleme kullanmaz ve uygulama oldukça basittir (IMO). Makinemden bazı BenchmarkDotNet sonuçları:

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 |

Ayrıca tüm testlerle bir özgeçmiş oluşturdum .


Güvenli olmayan anahtar sözcüğü kullanmaz, ancak yine de System.Runtime.CompilerServices.Unsafe kullanarak güvenli olmayan kodu çağırır
Paulo Zemek

NewMemCmpAVX-2 kullanmak için cevabımı güncelledim
Mr Anderson

8

Bilgisayarımda hafifçe memcmp()(kaidenin cevabı) ve çok EqualBytesLongUnrolled()kaygan bir şekilde (Arek Bulski'nin cevabı) atan bir yöntem geliştirdim . Temel olarak, döngüyü 8 yerine 4 açar.

Güncelleme 30 Mart 2019 :

.NET core 3.0'dan başlayarak, SIMD desteğimiz var!

Bu çözüm, bilgisayarımdaki önemli bir farkla en hızlı:

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

Ölçümlerim .NET 462 için farklı olabilir NETCORE:
Motlicek Petr

Sabitleme döndürdüğü için, kod iki 0 uzunluklu diziyi karşılaştırırken çöküyor null.
Glenn Slayden

memcmp sadece bir özkaynak karşılaştırıcısı değildir. Hangi nesnenin daha büyük veya daha küçük olduğu bilgisini verir. Bu amaçla algoritmanızı benimseyebilir ve performansı kontrol edebilir misiniz?
nicolay.anykienko

Daha hızlı daha mı Spanve memcpy?
Silkfire

@silkfire .NET core 3 ve modern CPU'da büyük diziler için 2-3 kat daha hızlı olmalıdır.
Bay Anderson

6

Güvenli olmayan kod kullanın ve forInt32 işaretçiler karşılaştırarak döngü çalıştırın .

Belki de dizilerin boş olmadığını kontrol etmeyi düşünmelisiniz.


5

.NET'in string.Equals nasıl yaptığını bakarsanız, "güvenli olmayan" bir işaretçi uygulaması olan EqualsHelper adlı özel bir yöntem kullandığını görürsünüz. .NET Reflector , işlerin dahili olarak nasıl yapıldığını görmek için arkadaşınızdır.

Bu blog yazısında bir uygulama yaptığım bayt dizi karşılaştırma için bir şablon olarak kullanılabilir C # hızlı bayt dizi karşılaştırma . Ayrıca, güvenli bir uygulamanın güvensiz olandan daha hızlı olduğunu görmek için bazı temel ölçütler yaptım.

Yani, gerçekten katil performansa ihtiyacınız olmadıkça, basit bir fr döngü karşılaştırması yapacağım.


3

Tamamen memnun olduğum bir çözüm bulamadım (makul performans, ancak güvenli olmayan kod / pinvoke) bu yüzden bu ile geldim, gerçekten orijinal bir şey yok, ama çalışıyor:

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

Bu sayfadaki diğer bazı çözümlerle karşılaştırıldığında performans:

Basit Döngü: 19837 keneler, 1.00

* BitConverter: 4886 keneler, 4.06

1636 keneler, 12.12

EqualBytesLongUrolled: 637 keneler, 31.09

P / Invoke memcmp: 369 keneler, 53.67

Linqpad'de test edildi, 1000000 bayt özdeş diziler (en kötü senaryo), her biri 500 yineleme.


evet, stackoverflow.com/a/1445280/4489 yorumunda testimin bunun orijinal sorudaki basit döngüden biraz daha yavaş olduğunu gösterdiğini belirttim.
Mart'ta Hafthor

Emin misiniz? Testlerimde 4 kat daha hızlı mı? Hiçbir şey havai marshaling bile olsa, iyi eski yerel kodu yenemez.
Zar Shardan

3

EqualBytesLongUnrolled , yukarıda önerilenlerden en iyisi gibi görünüyor .

Atlanan yöntemler (Enumerable.SequenceEqual, StructuralComparisons.StructuralEqualityComparer.Equals), yavaş için sabırlı değildi. 265MB dizilerde bunu ölçtüm:

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 |

NewMemCmpAVX-2 kullanmak için cevabımı güncelledim
Mr Anderson

3

Burada birçok linq çözümü görmedim.

Performansın sonuçlarından emin değilim, ancak genellikle linqkural olarak sadık kalırım ve daha sonra gerekirse daha sonra optimize ederim .

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

Lütfen bunun yalnızca aynı boyuttaki diziler için geçerli olduğunu unutmayın. bir uzantı böyle görünebilir

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

Sorunun bütün mesele, soruya gönderilen fonksiyonun daha hızlı bir çözümdür.
CodesInChaos

3

Ben hata ayıklama bağlı olmadan ekli program .net 4.7 sürüm derleme kullanarak bazı ölçümler yaptım. Bence insanlar yanlış metriği kullanıyorlar, çünkü burada hıza önem veriyorsanız, iki baytlık dizilerin eşit olup olmadığını anlamak ne kadar zaman alıyor. yani bayt cinsinden verim.

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

Gördüğünüz gibi, bundan daha iyi bir yol yoktur memcmpve büyüklük sırası daha hızlıdır. Basit bir fordöngü ikinci en iyi seçenektir. Ve hala Microsoft'un neden bir Buffer.Compareyöntem ekleyemediğini aklımdan çıkarmıyor .

[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

Kısa bayt dizilerini karşılaştırmak için aşağıdakiler ilginç bir saldırıdır:

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

O zaman muhtemelen soruda listelenen çözüme düşerim.

Bu kodun performans analizini yapmak ilginç olurdu.


int i = 0; (; i <a1.Length-7; i + = 8) için (BitConverter.ToInt64 (a1, i)! = BitConverter.ToInt64 (a2, i)) false değerini döndürürse; (; i <a1.Length; i ++) için (a1 [i]! = a2 [i]) yanlış döndürürse; geri dönüş; // döngü için basit olandan biraz daha yavaş.
Hafthor

2

Sipariş önemseyen (yani hiçbir şey yerine memcmpbir intbenzeri gibi dönmek istiyorum), .NET Core 3.0 (ve muhtemelen .NET Standard 2.1 aka .NET 5.0) bir Span.SequenceCompareTo(...)eklenti yöntemi (artı a Span.SequenceEqualTo) içerecektir iki ReadOnlySpan<T>örneği karşılaştırmak için kullanılabilir ( where T: IComparable<T>).

Gelen orijinal GitHub önerisi , tartışma, atlama masası hesaplamalar yaklaşım karşılaştırmaları dahil okuma byte[]olarak long[], SIMD kullanımı, ve p / CLR uygulaması en etmek çağırmak memcmp.

İleriye dönük olarak, bu, bayt dizilerini veya bayt aralıklarını karşılaştırmak için ( .NET Standard 2.1 API'leriniz Span<byte>yerine kullanmanız gerektiği gibi) gitme yönteminiz olmalı byte[]ve artık onu optimize etmemeye yetmeyecek kadar hızlıdır (ve Hayır, adındaki benzerliklere rağmen, korku kadar uçsuz bucaksız performans göstermez 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

Birçok grafik kartında yerleşik blok transfer hızlandırma yöntemlerini düşündüm. Ama sonra tüm veri byte-wise üzerinden kopyalamak zorunda kalacaksınız, bu yüzden mantığınızın bir kısmını yönetilmeyen ve donanıma bağlı kodda uygulamak istemiyorsanız bu size pek yardımcı olmaz ...

Yukarıda gösterilen yaklaşıma benzer bir başka optimizasyon yolu da verilerinizin mümkün olduğunca çoğunu bir bayt [] yerine uzun bir [] yerine, örneğin ikili bir dosyadan sırayla okuyorsanız, veya bellek eşlemeli bir dosya kullanıyorsanız, verileri uzun [] veya tek uzun değerler olarak okuyun. Ardından, karşılaştırma döngünüzün aynı miktarda veri içeren bir bayt [] için yapması gereken yineleme sayısının 1 / 8'ine ihtiyacı olacaktır. Verilere bayt bayt olarak ne zaman ve ne sıklıkta ve ne zaman ve ne sıklıkta erişmeniz gerektiğiyle, örneğin bir API çağrısında, beklenen bir yöntemde parametre olarak kullanmakla ilgilidir. bir bayt []. Sonunda, sadece kullanım durumunu gerçekten bilip bilmediğinizi anlayabilirsiniz ...


Kabul edilen cevap bayt arabelleğini uzun bir tampon olarak yeniden düzenler ve tanımladığınız şekilde karşılaştırır.
Aralık'ta Hafthor

1

Bu, burada verilen diğer sürümlerden neredeyse kesinlikle daha yavaş, ancak yazmak eğlenceliydi.

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

1

Ek bir optimizasyon ile ArekBulski tarafından yayınlanan EqualBytesLongUnrolled yönteminden esinlenen bir çözüme karar verdim. Benim durumumda, dizilerdeki dizi farklılıkları dizilerin kuyruğuna yakın olma eğilimindedir. Testte, büyük diziler için durum söz konusu olduğunda, dizi öğelerini ters sırayla karşılaştırabilmenin, bu çözüme memcmp tabanlı çözüm üzerinde büyük bir performans kazancı sağladığını buldum. İşte bu çözüm:

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

Maalesef, zaten doğru şekilde yaptığınız yönetilen bir yol arıyorsanız ve bence BCL'de bunu yapmak için yerleşik bir yöntem yok.

Bazı başlangıç ​​null kontrolleri eklemeli ve daha sonra BCL'de olduğu gibi tekrar kullanmalısınız.


Bunu yazarken haklıydınız, ancak 2010'da (.NET 4.0) bir BCL yöntemi geldi, Ohad Schneider'in cevabına bakın. Soru sırasında .NET 3.5'te Linq vardı (aku'nun cevabına bakınız).
Jeppe Stig Nielsen

-1

SequenceEqualsKarşılaştırma yapmak için bunu kullanın .


-2

Çok hızlı bir bayt dizi eşitlik karşılaştırıcısı arıyorsanız, bu STSdb ​​Labs makalesine bir göz atmanızı öneririz: Bayt dizi eşitlik karşılaştırıcısı. Sunulan, test edilen ve özetlenen bayt [] dizi eşitliği karşılaştırması için en hızlı uygulamalardan bazılarına sahiptir.

Ayrıca şu uygulamalara da odaklanabilirsiniz:

BigEndianByteArrayComparer - hızlı bayt [] sağa (bigEndian) soldan dizi karşılaştırıcısı BigEndianByteArrayEqualityComparer - - hızlı byte [] eşitlik karşılaştırıcısı sağa (bigEndian) soldan LittleEndianByteArrayComparer - hızlı bayt sol (littleEndian) sağdan [] dizi karşılaştırıcısı LittleEndianByteArrayEqualityComparer - hızlı bayt [] sağdan sola eşitlik karşılaştırıcısı (LittleEndian)


-2

Kısa cevap şudur:

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

Bu şekilde, güvenli olmayan kod yazmanıza gerek kalmadan bir bayt dizisi karşılaştırması yapmak için optimize edilmiş .NET dize karşılaştırmasını kullanabilirsiniz. Arka planda şu şekilde yapılır :

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

Testlerimde, bir dizeye dönüşüm, daha hızlı karşılaştırmanın avantajını yok eder. Bu, basit bir döngüden yaklaşık 2,5 kat daha yavaştı.
Doug Clutter

Aynı yaptığım için basit yaklaşık 8 kat daha yavaş oldu. Kodunuzu buraya yazabilir misiniz?
Alon

1
Bir bayt null (0) değeri içeriyorsa bu kesilir mi?
Joseph Lennox

-1 @DougClutter tarafından belirtildiği gibi dizeye dönüşüm nedeniyle yavaş olmasının yanı sıra, bayt dizisi ASCII olmayan veriler içeriyorsa bu başarısız olur. Doğru sonucu elde etmek için iso-8859-1'i kullanması gerekir.
Joe

2
Compare(new byte[]{128}, new byte[]{ 255 }) == truedeğil buggy hiç ...
CodesInChaos

-2

Yukarıdaki süslü çözümlerin çoğu UWP ile çalışmadığından ve Linq ve fonksiyonel yaklaşımları sevdiğim için, versiyonumu bu soruna zorladım. İlk fark ortaya çıktığında karşılaştırmadan kaçmak için .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 çünkü bu kod bozuk ve görünüşte denenmemiş. Bu atar IndexOutOfRangeExceptionelemanları erişen çünkü boş olmayan diziler karşılaştırırken 1yoluyla ba0.Lengtholması gerektiği zaman 0içinden ba0.Length - 1. Düzeltmek Eğer bu konuda Enumerable.Range(0, ba0.Length)hala hatalı olarak döner true, sadece birinci elemanlar Eğer tatmin edici birinci elemanlar arasında ayrım yapamaz, çünkü farklı eşit uzunlukta diziler için predicateve herhangi bir tatmin edici elemanlar predicate; her iki durumda da FirstOrDefault<int>()geri döner 0.
BACON

Buradaki ders çocuklar: silah dövüşüne bıçak getirme
Richard Hauer
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.