.NETでの2つのバイト配列の比較


541

どうすればこれを速くできますか?

確かに私はこれを行うことができます:

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

しかし、私はBCL関数、またはこれを行うための高度に最適化された実証済みの方法を探しています。

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

うまく動作しますが、x64で動作するようには見えません。

ここで私の超高速の答えに注意してください


1
「これは、配列がqwordアラインメントを開始するという事実に頼っています。」それは大きなことです。それを反映するようにコードを修正する必要があります。
ジョーチョン

4
return a1.Length == a2.Length &&!a1.Where((t、i)=> t!= a2 [i])。Any();
alerya 2012

私は約@OhadSchneider答えを言っていますIStructuralEquatable
LCJ

回答:


613

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

何らかの理由で.NET 3.5を使用できない場合、メソッドは問題ありません。
コンパイラー\ランタイム環境はループを最適化するため、パフォーマンスについて心配する必要はありません。


4
しかし、SequenceEqualは、安全でない比較よりも処理に時間がかかりませんか?特に、1000回の比較を行う場合はどうでしょうか。
ケーブルケーブル2011年

90
はい、これは安全でない比較よりも約50倍遅く実行されます。
Hafthor、2011

27
これは本当にここで死者を発生させていますが、遅いことはここで使用するのは本当に悪い言葉です。50倍遅いと悪いように聞こえますが、それが違いを生むのに十分なデータを比較していることはあまりありません。もしそうであれば、無数の理由で、実際にこれをベンチマークする必要があります。たとえば、安全でない回答の作成者は、50倍遅いのとは対照的に、7倍遅いと記しています(安全でないメソッドの速度もデータの配置に依存します)。これらの数値が重要なまれなケースでは、P / Invokeはさらに高速です。
Selali Adob​​or 2014

4
遅い実装は300以上のいいね!を取得しますか?msvcrt.dllをフックすることをお勧めします。
TGarrett

69
最速はビジネスにとって最も重要なことではありません。保守性は、このコードの節約が99%の場合に相当するよりも「速く」なります。私はSequenceEqualを使用しており、コード全体は1ミリ秒未満です。保存しているµsが、P / Invokeの5分間の読みやすさに欠けることはありません。
PRMan

236

P / Invokeパワーがアクティブになります!

[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 /呼び出しyaay -この少なくともビットマップ上でこれまでで最速であることが証明された:stackoverflow.com/questions/2031217/...
エリック・フォーブス

25
この場合、固定は必要ありません。マーシャラーは、PInvokeでネイティブコードを呼び出すときに自動ピン留めを実行します。参考:stackoverflow.com/questions/2218444/...
マーク・グラスゴー

14
P / Invokeはブーイングを誘発する可能性がありますが、安全ではないポインタサイズの比較を使用する私が思いついた実装を含め、提示されたすべてのソリューションの中で最速です。ネイティブコードを呼び出す前に、参照の等価性や最初と最後の要素の比較など、いくつかの最適化を行うことができます。
Josh

38
なんでブー?ポスターは高速な実装を望んでおり、最適化されたアセンブリ言語の比較に勝るものはありません。P / INVOKEなしで.NETから「REPE CMPSD」を取得する方法がわかりません。
Jason Goemaat、2011年

14
Nitpick:MSVCR.dllは、ユーザーコードで使用するためのものではありません。MSVCRを使用するには、配布するバージョンを使用してランタイムを配布する必要があります。(msdn.microsoft.com/en-us/library/...blogs.msdn.com/b/oldnewthing/archive/2014/04/11/10516280.aspx
ミッチ

160

.NET 4には、このための新しい組み込みソリューションがあります-IStructuralEquatable

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

17
このブログの投稿によるとそれは実際には非常に遅いです。
Matt Johnson-Pint

48
クレイジースロー。単純なforループよりも約180倍遅くなります。
Hafthor

これは機能しますが、理由はわかりません。byte []は、IStructuralEquatableを実装しないプリミティブ型なので、なぜそれをキャストできるのか、そして暗黙的なキャストです!そして、インターフェース「Equals」メソッドが魔法のように利用可能になります...そのメソッドの実装はどこから来ていますか?誰かが私を手がかりにできますか?
Josh

1
なぜだけではないのかStructuralComparisons.StructuralEqualityComparer.Equals(a1, a2)NullReferenceExceptionここにはありません。
ta.speot.is 2014年

1
@ ta.speot.isありがとう、ワンライナーで議論することはできません!以前のソリューションは、キャストをIStructuralEquatable(配列は静的にIStructuralEquatableであることがわかっている)に保存したため、少し効率的でしたが、実際には、提案によりメソッドはnull引数に対しても機能します。
Ohad Schneider 14年

76

ユーザーgilは、このソリューションを生み出した安全でないコードを提案しました:

// 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ビットベースの比較を行います。この種のことは、配列がqwordに整列して開始するという事実に依存しています。qwordに対応していなくても機能しますが、速度が遅くなります。

これは、単純なforループよりも約7つのタイマーが高速に実行されます。J#ライブラリの使用は、元のforループと同等に実行されます。.SequenceEqualを使用すると、約7倍遅くなります。IEnumerator.MoveNextを使用しているからと思います。LINQベースのソリューションは、少なくともそれほど遅いか悪いと思います。


3
素晴らしい解決策。しかし、1つの(小さい)ヒント:参照a1とa2が等しい場合の比較は、a1とb1に同じ配列を与える場合、速度を上げる可能性があります。
mmmmmmmm

12
.NET 4 x64リリースでの新しいテストデータ:IStructualEquatable.equalsが180倍遅く、SequenceEqualが15倍遅く、SHA1ハッシュ比較が11倍遅く、ビットコンバーターが同じ、安全でない7倍速く、ピンボークが11倍速くなりました。unsafeがmemcmpのP / Invokeよりも少しだけ遅いことはかなりクールです。
Hafthor

3
このリンクは、メモリアライメントが重要な理由について良い詳細与えibm.com/developerworks/library/pa-dalignを -彼らは両方ともになるまでそう、最適化がアライメントをチェックすることができ、両方の配列が同じ量だけアライメントオフになっている場合、バイトは比較しますqword境界で。
Hafthor 2013年

5
a1とa2の両方がnullの場合、これはfalseにならないでしょうか?
nawfal 2013

2
@CristiDiaconescu私はKevinDriedgerの答えをループ化しました。私がおそらくすべきことは、テストスイートと私の結果をgithubで利用可能にし、私の回答でそれにリンクすることです。
Hafthor 2013年

74

Span<T> 独自のアプリケーションのコードベースに混乱や移植性のない綿毛を投げかけることなく、非常に競争力のある代替手段を提供します。

// 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以降の(根性のある)実装は、ここにあります

私がしまし改訂として、このメソッドを追加するEliArbelの要旨@ SpansEqual異なる配列サイズ、出力、グラフ、およびマークでそれを実行して、他人のベンチマークではあまり興味深い演奏のほとんどを落とし、SpansEqualそれは別の方法がと比較する方法を報告するように、ベースラインとしてSpansEqual

以下の数値は結果からのもので、「エラー」列を削除するために軽く編集されています。

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

SpansEqualmax-array-sizeメソッドが上位に出ていないことに驚きましたが、その違いはごくわずかなので、問題になることはないと思います。

私のシステム情報:

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

Span <T>やそれに近いものをすべての作業で使用することになるとは思いもしませんでした。おかげで、同僚に自慢できるようになりました。
jokab

SequenceEqualは特にSpanメソッドとして実装されていますか?IEnumerable拡張メソッドの1つにすぎないと考えました。
Zastai

1
@Zastaiはい、{ReadOnly,}Span<T>独自のバージョンがありますSequenceEqual(対応するIEnumerable<T>拡張メソッドと同じコントラクトを持っているため、同じ名前で、より高速です)。型の制限のため、拡張メソッドを{ReadOnly,}Span<T>使用できないことに注意してください。IEnumerable<T>ref struct
ジョーAmenta

1
@SentinelのSystem.Memoryパッケージには、「ポータブル」/「低速」のSpan<T>実装が実装されnetstandard1.1ています(そのため、このインタラクティブなチャートを試して、どれが実装されているかを確認してください)。現在、「高速」Span<T>は.NET Core 2.1でのみ使用できますが、SequenceEqual<T>特に「高速」と「低速」/「移植性」の違いはほとんどないnetstandard2.0はずです(ただし、ターゲットはわずかに改善されるはずですが、ベクトル化されたコードパスを持っている)。
Joe Amenta

1
install-package system.memory
Chris Moschini、2018年

30

それを行うことに反対していない場合は、J#アセンブリ「vjslib.dll」をインポートして、そのArrays.equals(byte []、byte [])メソッドを使用できます...

誰かがあなたを笑っても私を責めないでください...


編集:それが少し価値があるもののために、私はReflectorを使用してそのためのコードを逆アセンブルしました、そしてここにそれがどのように見えるかです:

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以降には、System.Data.Linq.Binaryをカプセル化する新しいパブリックタイプがありますbyte[]IEquatable<Binary>(事実上)2つのバイト配列を比較することを実装します。System.Data.Linq.Binaryからの暗黙の変換演算子もあることに注意してくださいbyte[]

MSDNドキュメント:System.Data.Linq.Binary

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

興味深いのは、2つのBinaryオブジェクトのハッシュが同じ場合にのみ、バイト単位の比較ループに進むことです。ただし、これには、Binaryオブジェクトのコンストラクターでハッシュを計算するという犠牲が伴います(forループを使用して配列をトラバースする:-))。

上記の実装は、最悪の場合、配列を3回トラバースする必要があることを意味します。最初にarray1のハッシュを計算し、次にarray2のハッシュを計算し、最後に(これは最悪のシナリオ、長さとハッシュが等しいため)比較します。 array1のバイトとarray 2のバイト。

全体的には、System.Data.Linq.BinaryBCLに組み込まれていますが、2つのバイト配列を比較する最速の方法ではないと思います:-|。


20

byte []がゼロでいっぱいかどうかを確認することについて、同様の質問を投稿しました。(SIMDコードが無効になったため、この回答から削除しました。)以下は、私の比較で最速のコードです。

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

2つの256MBバイトアレイで測定:

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

1
私が確認する。テストも実行しました。これは、memcmp unsafe callを使用する回答よりも高速です。
ujeenator 2015年

1
@AmberdeBlackよろしいですか?小さな配列でテストしましたか?
Zar Shardan

@ArekBulskiこれはmemcmpよりも高速ですか?
Zar Shardan

これとmemcmpの間で実質的に同じパフォーマンスが得られたため、完全に管理されたソリューションでは+1となりました。
マイクマリナウスキー

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
それは私が使ってきたものです。しかし、それはうーん...単純なループを使用する場合とは異なり、逐次比較のように聞こえるため、それほど高速ではありません。それを反映し、実際に何が行われているのかを確認するとよいでしょう。名前から判断すると、それは派手なことではありません。
Sergey Akopov、2011年

1
はい。ただし、受け入れられた回答ですでに言及されています。ところで、そこで型指定を削除することができます。
nawfal 2013年

10

もう一つ追加しましょう!

最近、Microsoftは特別なNuGetパッケージSystem.Runtime.CompilerServices.Unsafeをリリースしました。ILで記述されているため特別であり、C#では直接利用できない低レベルの機能を提供します。

そのメソッドの1つは、Unsafe.As<T>(object)任意の参照タイプを別の参照タイプにキャストして、安全性チェックをスキップできるようにします。これは通常非常に悪い考えですが、両方のタイプが同じ構造を持っている場合は、機能します。だから我々は、キャストするためにこれを使用することができますbyte[]します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.Length配列のメモリ構造のフィールドに格納されているため、元の配列の長さが返されることに注意してください。

この方法は、ここで説明する他の方法ほど高速ではありませんが、ナイーブな方法よりもはるかに高速で、安全でないコードやP / Invokeや固定を使用せず、実装は非常に簡単です(IMO)。ここに私のマシンからのいくつかのBenchmarkDotNet結果があります:

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 |

すべてのテストの要点も作成しました。


unsafeキーワードは使用しませんが、それでもSystem.Runtime.CompilerServices.Unsafeを使用して安全でないコードを呼び出します
Paulo Zemek

NewMemCmpAVX-2を使用するように回答を更新しました
アンダーソン氏

8

私は自分のPCで、わずかに打つmemcmp()(素朴な答え)と非常にゆっくりと打つEqualBytesLongUnrolled()(アレクブルスキの答え)方法を開発しました。基本的に、ループを8ではなく4ずつ展開します。

2019年3月30日更新

.NETコア3.0以降、SIMDをサポートしています。

このソリューションは私の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;
}

.NET 462のための私の測定結果が異なることができNETCORE:
Motlicekペトル

2つの長さ0の配列を比較すると、ピンが返されるため、コードがクラッシュしますnull
Glenn Slayden 2017

memcmpは、単なる資本比較ではありません。それは、どのオブジェクトが大きいか小さいかの情報を提供します。この目的でアルゴリズムを採用してパフォーマンスを確認できますか?
nicolay.anykienko 2018年

それはより速くありSpanmemcpy
シルクファイア

@silkfire .NETコア3および最新のCPUでは、大規模なアレイの場合は2〜3倍高速になります。
アンダーソン氏

6

安全でないコードを使用forして、Int32ポインターを比較するループを実行します。

また、配列がnullでないことを確認することも検討する必要があります。


5

.NETがstring.Equalsを実行する方法を見ると、「安全でない」ポインタ実装を持つEqualsHelperと呼ばれるプライベートメソッドを使用していることがわかります。.NET Reflectorは、内部でどのように処理が行われているかを確認するための友達です。

これは、C#での高速バイト配列比較のブログ投稿で実装したバイト配列比較のテンプレートとして使用できます。安全な実装が安全でない実装よりも高速である場合を確認するために、いくつかの基本的なベンチマークも行いました。

とはいえ、本当にキラーなパフォーマンスが必要でない限り、単純なfrループ比較を行います。


3

私が完全に満足しているソリューションを見つけることができなかったため(妥当なパフォーマンスですが、安全でないコード/ピンボークはありません)、これを思いつきました。

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

このページの他のソリューションのいくつかと比較したパフォーマンス:

単純なループ:19837ティック、1.00

* BitConverter:4886ティック、4.06

UnsafeCompare:1636ティック、12.12

EqualBytesLongUnrolled:637ティック、31.09

P / memcmpを呼び出す:369ティック、53.67

linqpadでテスト、1000000バイトの同一配列(最悪のシナリオ)、それぞれ500反復。


ええ、私はstackoverflow.com/a/1445280/4489のコメントで、これが実際には元の質問で持っていた単純なforループよりも少し遅いことを示していることを指摘しました
Hafthor 2016年

本気ですか?私のテストでは4倍高速ですか?マーシャリングのオーバーヘッドがあっても、古き良きネイティブコードに勝るものはありません。
Zar Shardan

3

それはそのようだEqualBytesLongUnrolled上記の提案から最高です。

スキップされたメソッド(Enumerable.SequenceEqual、StructuralComparisons.StructuralEqualityComparer.Equals)は、遅い患者ではありませんでした。265MBのアレイでこれを測定しました:

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を使用するように回答を更新しました
アンダーソン氏

3

ここでは、多くのlinqソリューションを見たことがありません。

パフォーマンスへの影響はわかりませんが、一般的にlinqは経験則に固執し、必要に応じて後で最適化します。

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

これらが同じサイズの配列である場合にのみ機能することに注意してください。拡張機能は次のようになります

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

質問の要点は、関数が質問に投稿したより速いソリューションです。
CodesInChaos 2018

3

デバッガーを接続せずに、添付プログラム.net 4.7リリースビルドを使用していくつかの測定を行いました。ここで速度を気にした場合、2つのバイト配列が等しいかどうかを判断するのにどのくらい時間がかかるかを考えると、人々は間違ったメトリックを使用していると思います。つまり、バイト単位のスループット。

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

ご覧のとおり、これよりも優れた方法はなくmemcmp、桁違いに高速です。単純なforループは2番目に最適なオプションです。そして、MicrosoftがBuffer.Compareメソッドを単純に含めることができない理由は、依然として私の心を揺さぶります。

[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

短いバイト配列を比較する場合、以下は興味深いハックです。

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

それから私はおそらく質問にリストされた解決策に落ちるでしょう。

このコードのパフォーマンス分析を行うのは興味深いでしょう。


int i = 0; for(; i <a1.Length-7; i + = 8)if(BitConverter.ToInt64(a1、i)!= BitConverter.ToInt64(a2、i))return false; for(; i <a1.Length; i ++)if(a1 [i]!= a2 [i])はfalseを返します。trueを返します。//単純なforループよりも少し遅い。
Hafthor

2

順番を気に(つまりは、あなたが望むことをあなたのそれらのためにmemcmp返すようにint、それは代わりに何もすべきであるように)、.NETのコア3.0(および.NET 5.0別名おそらく.NET標準2.1)が含まれますSpan.SequenceCompareTo(...)拡張メソッド(プラスSpan.SequenceEqualTo)は、CAN 2つのReadOnlySpan<T>インスタンスを比較するために使用されます(where T: IComparable<T>)。

、元のGitHubの提案、議論は読書、ジャンプテーブルの計算とアプローチの比較を含めbyte[]long[]CLRの実装のために呼び出す/ SIMDの使用状況、およびP、memcmp

今後、これはバイト配列またはバイト範囲を比較するための主要な方法であり(.NET Standard 2.1 APIのSpan<byte>代わりにbyte[]を使用する必要があります)、十分に高速であるため、最適化について気にする必要はありません(そしていいえ、名前が類似しているにもかかわらず、恐ろしいほどには機能しません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

多くのグラフィックスカードに組み込まれているブロック転送アクセラレーション方式について考えました。しかし、すべてのデータをバイト単位でコピーする必要があるため、管理されていないハードウェア依存のコードでロジックの全体を実装したくない場合、これはあまり役に立ちません...

上記のアプローチと同様の最適化の別の方法は、最初からバイト[]ではなくlong []にできるだけ多くのデータを格納することです。たとえば、バイナリファイルから順次読み取る場合、または、メモリマップファイルを使用する場合は、データをlong []または単一のlong値として読み込みます。次に、比較ループは、同じ量のデータを含むbyte []に​​必要な反復回数の1/8のみを必要とします。いつ、どのくらいの頻度で比較する必要があるか、どのくらいの頻度で、バイトごとにデータにアクセスする必要があるかなどの問題です。 byte []。結局、ユースケースを本当に知っているかどうかしかわからない...


受け入れられた回答は、バイトバッファをロングバッファとして再キャストし、説明どおりに比較します。
Hafthor

1

これはほぼ確実に、ここに記載されている他のバージョンよりはるかに遅いですが、書くのは楽しかったです。

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

1

ArekBulskiによって投稿されたEqualBytesLongUnrolledメソッドに触発されたソリューションを追加の最適化で解決しました。私の場合、配列の配列の違いは配列の末尾に近い傾向があります。テストの結果、これが大規模な配列の場合、配列要素を逆の順序で比較できるため、このソリューションではmemcmpベースのソリューションよりもパフォーマンスが大幅に向上することがわかりました。これがその解決策です:

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

申し訳ありませんが、管理された方法を探している場合は、既に正しく実行されており、私の知る限り、これを行うためのBCLに組み込みのメソッドはありません。

いくつかの初期nullチェックを追加し、BCL内の場合と同様に再利用する必要があります。


あなたがそれを書いたときあなたは正しかった、しかし2010(.NET 4.0)でBCLメソッドが来た、Ohad Schneiderの答えを見なさい。質問の時点で、.NET 3.5にはLinqがありました(akuの回答を参照)。
Jeppe Stig Nielsen


-2

非常に高速なバイト配列の等値比較子を探している場合は、STSdb Labsの記事「バイト配列の等値比較子」をご覧になることをお勧めしますこれは、byte []配列の等価性比較のための最速の実装のいくつかを特徴としており、パフォーマンスのテストと要約が行われています。

これらの実装に集中することもできます。

BigEndianByteArrayComparer-左から右への高速byte []配列比較(BigEndian)BigEndianByteArrayEqualityComparer--左から右への 高速byte []等価比較(BigEndian)LittleEndianByteArrayComparer-右から左への 配列比較(LittleEndian) LittleEndianByteArrayEqualityComparer-高速バイト[]右から左への等値比較子(LittleEndian)


-2

短い答えはこれです:

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

このような方法で、最適化された.NET文字列比較を使用して、安全でないコードを記述する必要なしにバイト配列比較を行うことができます。これは、バックグラウンドで実行される方法です。

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

私のテストでは、文字列への変換は、より高速な比較の利点を破壊します。これは、単純なforループよりも約2.5倍遅くなりました。
Doug Clutter 2015

私が同じことをしたとき、単純なのは約8倍遅くなりました。ここにコードを記述できますか?
アロン2015

1
バイトにnull(0)値が含まれている場合、これは壊れますか?
ジョセフレノックス

-1 @DougClutterで指摘されているように文字列への変換のために遅いだけでなく、バ​​イト配列に非ASCIIデータが含まれている場合、これは失敗します。正しい結果を得るには、iso-8859-1を使用する必要があります。
ジョー

2
Compare(new byte[]{128}, new byte[]{ 255 }) == trueまったくバグがない...
CodesInChaos

-2

上記の優れたソリューションの多くはUWPでは機能しないため、Linqと機能的アプローチが大好きなため、この問題に対する私のバージョンを強調します。最初の違いが発生したときに比較をエスケープするために、.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。これはスローIndexOutOfRangeExceptionあなたが要素にアクセスしているので、非空の配列を比較するとき1を通じてba0.Lengthそれがあるべきとき0通じba0.Length - 1。あなたが持つことが修正した場合Enumerable.Range(0, ba0.Length)、それはまだ間違って返すtrueあなたが満足する最初の要素とを区別することができないので、最初の要素が異なる同じ長さの配列のためのpredicate及び一切の要素は満足しませんpredicate。どちらの場合もFirstOrDefault<int>()戻ります0
BACON、2018

ここでの子供たちの教訓:銃撃戦にナイフを持ち込まないでください
Richard Hauer
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.