So sánh hai mảng byte trong .NET


541

Làm thế nào tôi có thể làm điều này nhanh chóng?

Chắc chắn tôi có thể làm điều này:

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

Nhưng tôi đang tìm kiếm một chức năng BCL hoặc một số cách đã được chứng minh tối ưu hóa cao để làm điều này.

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

hoạt động độc đáo, nhưng có vẻ như nó sẽ không hoạt động cho x64.

Lưu ý câu trả lời siêu nhanh của tôi ở đây .


1
"Điều này dựa trên thực tế là các mảng bắt đầu liên kết qword." Đó là một nếu lớn. Bạn nên sửa mã để phản ánh điều đó.
Joe Chung

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

Tôi thích câu trả lời của @OhadSchneider vềIStructuralEquatable
LCJ

Câu trả lời:


613

Bạn có thể sử dụng phương thức 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

Nếu bạn không thể sử dụng .NET 3.5 vì một số lý do, phương pháp của bạn vẫn ổn.
Trình biên dịch \ môi trường thời gian chạy sẽ tối ưu hóa vòng lặp của bạn, do đó bạn không cần phải lo lắng về hiệu suất.


4
Nhưng SequenceEqual không mất nhiều thời gian để xử lý hơn so với so sánh không an toàn? Đặc biệt là khi bạn làm 1000 so sánh?
tcables

90
Có, điều này chạy chậm hơn khoảng 50 lần so với so sánh không an toàn.
Hafthor

27
Điều này thực sự gây ra cái chết ở đây, nhưng chậm thực sự là một từ xấu để sử dụng ở đây. Chậm hơn 50 lần nghe có vẻ tệ, nhưng thường thì bạn không so sánh đủ dữ liệu để tạo ra sự khác biệt và nếu là bạn, bạn thực sự cần phải đánh giá điều này cho trường hợp của riêng bạn, vì vô số lý do. Ví dụ: lưu ý người tạo câu trả lời không an toàn ghi nhận sự khác biệt chậm 7 lần, trái ngược với chậm hơn 50 lần (tốc độ của phương pháp không an toàn cũng phụ thuộc vào việc căn chỉnh dữ liệu). Trong những trường hợp hiếm hoi khi những con số này quan trọng, P / Gọi thậm chí còn nhanh hơn.
Selali Adobor

4
Vì vậy, việc thực hiện chậm hơn được hơn 300 lượt thích? Tôi sẽ đề nghị móc msvcrt.dll vì đó sẽ là cách nhanh nhất để hoàn thành công việc.
TGarrett

69
Nhanh nhất không phải là điều quan trọng nhất đối với một doanh nghiệp. Khả năng bảo trì "nhanh" hơn nhiều so với khoản tiết kiệm trong mã này sẽ lên tới 99% trường hợp. Tôi đang sử dụng SequenceEqual và toàn bộ mã của tôi là <1ms. Những người bạn đang lưu sẽ không bao giờ thêm tối đa 5 phút thiếu khả năng đọc P / Gọi.
PRman

236

P / Gọi quyền hạn kích hoạt!

[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 - điều này được chứng minh là nhanh nhất cho đến nay trên bitmap ít nhất: stackoverflow.com/questions/2031217/...
Erik Forbes

25
Ghim là không cần thiết trong trường hợp này. Trình soái ca thực hiện ghim tự động khi gọi mã gốc bằng PInvoke. Tham khảo: stackoverflow.com/questions/2218444/
Mark Glasgow

14
P / Gọi có thể gợi ra những tiếng la ó nhưng cho đến nay là nhanh nhất trong tất cả các giải pháp được trình bày, bao gồm cả việc triển khai mà tôi đã đưa ra bằng cách sử dụng các so sánh có kích thước con trỏ không an toàn. Có một vài tối ưu hóa bạn có thể thực hiện trước khi gọi ra mã gốc bao gồm cả đẳng thức tham chiếu và so sánh các yếu tố đầu tiên và cuối cùng.
Josh

38
Tại sao lại là boo? Poster muốn có một thực hiện nhanh chóng và một ngôn ngữ lắp ráp tối ưu so sánh không thể đánh bại. Tôi không biết làm cách nào để "REPE CMPSD" ra khỏi .NET mà không cần P / INVOKE.
Jason Goemaat

14
Nitpick: MSVCR.dll không được sử dụng bởi mã người dùng. Để sử dụng MSVCR, bạn sẽ phải phân phối thời gian chạy sử dụng phiên bản bạn phân phối. ( Msdn.microsoft.com/en-us/library/...blogs.msdn.com/b/oldnewthing/archive/2014/04/11/10516280.aspx )
Mitch

160

Có một giải pháp tích hợp mới cho điều này trong .NET 4 - IStructuralEquitable

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

17
Theo bài đăng blog đó là thực sự rất chậm.
Matt Johnson-Pint

48
Điên chậm. Khoảng 180x chậm hơn đơn giản cho vòng lặp.
Hafthor

Điều này hoạt động, nhưng tôi không hiểu tại sao. Một byte [] là một kiểu nguyên thủy không triển khai IStructuralEquitable, vậy tại sao bạn có thể truyền nó - và một diễn viên ngầm ở đó! Và sau đó là giao diện "bằng" phương pháp kỳ diệu trở nên có sẵn ... nơi đang thực hiện phương pháp đó đến từ đâu? Ai đó có thể đầu mối cho tôi?
Josh

1
Tại sao không chỉ StructuralComparisons.StructuralEqualityComparer.Equals(a1, a2). Không có NullReferenceExceptionở đây.
ta.speot.is

1
@ ta.speot.is Cảm ơn, Không thể tranh luận với một lớp lót! Giải pháp trước đây hiệu quả hơn một chút vì nó đã lưu cast vào IStructuralEquitable (một mảng được biết đến là IStructuralEquitable), nhưng thực sự các đề xuất của bạn cũng làm cho phương thức hoạt động với các đối số null.
Ohad Schneider

76

Người dùng gil đề xuất mã không an toàn đã sinh ra giải pháp này:

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

trong đó so sánh dựa trên 64 bit cho càng nhiều mảng càng tốt. Đây là loại tội trên thực tế là các mảng bắt đầu qword thẳng hàng. Nó sẽ hoạt động nếu không được căn chỉnh qword, chỉ là không nhanh như thể nó được.

Nó thực hiện khoảng bảy giờ nhanh hơn forvòng lặp đơn giản . Sử dụng thư viện J # được thực hiện tương đương với forvòng lặp ban đầu . Sử dụng .SequenceEqual chạy chậm hơn khoảng bảy lần; Tôi nghĩ chỉ vì nó đang sử dụng IEnumerator.MoveNext. Tôi tưởng tượng các giải pháp dựa trên LINQ ít nhất là chậm hoặc tệ hơn.


3
Giải pháp tốt đẹp. Nhưng một gợi ý (nhỏ): Một so sánh nếu tham chiếu a1 và a2 bằng nhau có thể tăng tốc mọi thứ nếu một người đưa ra cùng một mảng cho a1 và b1.
mmmmmmmm

12
Dữ liệu thử nghiệm mới trên phiên bản .NET 4 x64: IStructualEquitable.equals ~ 180x chậm hơn, SequenceEqual 15x chậm hơn, băm SHA1 so sánh chậm hơn 11 lần, bitconverter ~ tương tự, nhanh hơn 7 lần không an toàn, nhanh hơn 11 lần. Khá tuyệt vời là không an toàn chỉ chậm hơn một chút so với P / Gọi trên memcmp.
Hafthor

3
Liên kết này cung cấp chi tiết tốt về lý do tại sao căn chỉnh bộ nhớ lại quan trọng ibm.com/developerworks/l Library / pa - dalign - vì vậy, tối ưu hóa có thể là kiểm tra căn chỉnh và nếu cả hai mảng bị lệch khỏi cùng một lượng, hãy so sánh byte cho đến khi cả hai trên một ranh giới qword.
Hafthor

5
Điều này sẽ không sai khi cả a1 và a2 đều null?
nawfal

2
@CristiDiaconescu Tôi đã lặp lại câu trả lời của KevinDriedger. Những gì tôi có lẽ nên làm là làm cho bộ kiểm tra và kết quả của tôi có sẵn trên github và liên kết với nó trên câu trả lời của tôi.
Hafthor

74

Span<T> Mời một sự thay thế cực kỳ cạnh tranh mà không cần phải vứt lộn xộn và / hoặc khuẩn không di động vào cơ sở mã ứng dụng của riêng bạn:

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

Việc triển khai (can đảm của) kể từ .NET Core 3.1.0 có thể được tìm thấy ở đây .

Tôi đã sửa đổi ý chính của @ EliArbel để thêm phương thức này SpansEqual, bỏ hầu hết các trình diễn ít thú vị hơn trong các điểm chuẩn của người khác, chạy nó với các kích thước mảng khác nhau, biểu đồ đầu ra và đánh dấu SpansEquallàm đường cơ sở để báo cáo cách các phương thức khác nhau so sánh với SpansEqual.

Các số dưới đây là từ các kết quả, được chỉnh sửa nhẹ để xóa cột "Lỗi".

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

Tôi đã rất ngạc nhiên khi thấy rằng SpansEqualkhông đi đầu trong các phương pháp kích thước mảng tối đa, nhưng sự khác biệt là rất nhỏ đến nỗi tôi không nghĩ nó sẽ trở nên quan trọng.

Thông tin hệ thống của tôi:

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

Tôi chưa bao giờ nghĩ rằng tôi sẽ sử dụng Span <T> hoặc thứ gì đó gần với nó trong tất cả những thứ tôi làm. Nhờ có bạn mà giờ tôi có thể khoe khoang về điều này với đồng nghiệp của mình.
jokab

SequenceEqual có đặc biệt được triển khai như một phương pháp Span không? Nghĩ rằng nó chỉ là một trong những phương pháp mở rộng IEnumerable.
Zastai

1
@Zastai có, {ReadOnly,}Span<T>có phiên bản riêng của nó SequenceEqual(cùng tên vì nó có cùng hợp đồng với IEnumerable<T>phương thức mở rộng tương ứng , nó chỉ nhanh hơn). Lưu ý rằng {ReadOnly,}Span<T>không thể sử dụng IEnumerable<T>các phương thức mở rộng vì các hạn chế về ref structloại.
Joe Amenta

1
@Sentinel gói System.Memory có các triển khai "di động" / "chậm" Span<T>cho netstandard1.1và ở trên (vì vậy hãy chơi với biểu đồ tương tác này để xem đó là những gì). "Nhanh" Span<T>chỉ có sẵn trong .NET Core 2.1, tại thời điểm này, nhưng lưu ý rằng SequenceEqual<T>cụ thể, có rất ít sự khác biệt giữa "nhanh" và "chậm" / "di động" (mặc dù netstandard2.0các mục tiêu sẽ thấy một sự cải thiện nhẹ vì chúng có đường dẫn mã vector).
Joe Amenta

1
cài đặt gói system.memory
Chris Moschini

30

Nếu bạn không phải là trái ngược với làm việc đó, bạn có thể nhập J # lắp ráp "vjslib.dll" và sử dụng nó Arrays.equals phương pháp (byte [], byte []) ...

Đừng trách tôi nếu ai đó cười bạn ...


EDIT: Đối với những gì nó đáng giá, tôi đã sử dụng Reflector để tháo rời mã cho điều đó, và đây là những gì nó trông giống như:

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 và mới hơn có loại công khai mới, được System.Data.Linq.Binarygói gọn byte[]. Nó thực hiện IEquatable<Binary>rằng (trong thực tế) so sánh hai mảng byte. Lưu ý rằng System.Data.Linq.Binarycũng có toán tử chuyển đổi ngầm định từ byte[].

Tài liệu MSDN: System.Data.Linq.Binary

Phản xạ dịch ngược của phương thức 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;
}

Điều thú vị là họ chỉ tiến hành vòng lặp so sánh từng byte nếu băm của hai đối tượng nhị phân là như nhau. Tuy nhiên, điều này có chi phí tính toán hàm băm trong hàm tạo của Binarycác đối tượng (bằng cách duyệt qua mảng với forvòng lặp :-)).

Việc triển khai ở trên có nghĩa là trong trường hợp xấu nhất bạn có thể phải duyệt qua các mảng ba lần: đầu tiên để tính toán hàm băm của mảng1, sau đó tính toán hàm băm của mảng2 và cuối cùng (vì đây là trường hợp xấu nhất, độ dài và giá trị băm bằng nhau) để so sánh byte trong mảng1 với byte trong mảng 2.

Nhìn chung, mặc dù System.Data.Linq.Binaryđược tích hợp vào BCL, tôi không nghĩ rằng đó là cách nhanh nhất để so sánh hai mảng byte: - |.


20

Tôi đã đăng một câu hỏi tương tự về việc kiểm tra xem byte [] có đầy đủ không. (Mã SIMD đã bị đánh bại nên tôi đã xóa nó khỏi câu trả lời này.) Đây là mã nhanh nhất từ ​​các so sánh của tôi:

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

Đo trên hai mảng byte 256 MB:

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

1
Tôi xác nhận. Tôi cũng đã chạy thử nghiệm. Điều này nhanh hơn câu trả lời sử dụng cuộc gọi không an toàn memcmp.
ujeenator

1
@AmberdeBlack Bạn có chắc không? Bạn đã thử nghiệm với các mảng nhỏ?
Zar Shardan

@ArekBulski Bạn có chắc là cái này nhanh hơn memcmp không, vì thử nghiệm của tôi cho thấy khác?
Zar Shardan

Tôi có hiệu suất gần như giống hệt nhau giữa cái này và memcmp, vì vậy +1 cho một giải pháp được quản lý hoàn toàn.
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
Đó là những gì tôi đã và đang sử dụng. Nhưng nó umm ... nghe có vẻ như một so sánh tuần tự mà bạn thường sử dụng một vòng lặp đơn giản, do đó không nhanh lắm. Thật tuyệt khi phản ánh nó và xem những gì thực sự đang làm. Đánh giá theo tên, nó không có gì lạ mắt.
Serge Akopov

1
Có, nhưng đã được đề cập trong câu trả lời được chấp nhận. btw, bạn có thể loại bỏ các đặc điểm kỹ thuật loại đó.
nawfal

10

Hãy thêm một cái nữa!

Gần đây, Microsoft đã phát hành gói NuGet đặc biệt, System.R nb.CompilerService.Unsafe . Nó đặc biệt bởi vì nó được viết bằng IL và cung cấp chức năng cấp thấp không có sẵn trực tiếp trong C #.

Một trong những phương pháp của nó, Unsafe.As<T>(object)cho phép truyền bất kỳ loại tham chiếu nào sang loại tham chiếu khác, bỏ qua mọi kiểm tra an toàn. Đây thường là một ý tưởng rất xấu, nhưng nếu cả hai loại có cùng cấu trúc, nó có thể hoạt động. Vì vậy, chúng ta có thể sử dụng điều này để byte[]chuyển sang 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;
}

Lưu ý rằng long1.Lengthvẫn sẽ trả về độ dài của mảng ban đầu, vì nó được lưu trữ trong một trường trong cấu trúc bộ nhớ của mảng.

Phương pháp này không hoàn toàn nhanh như các phương pháp khác được trình bày ở đây, nhưng nó nhanh hơn rất nhiều so với phương pháp ngây thơ, không sử dụng mã không an toàn hoặc P / Gọi hoặc ghim và việc triển khai khá đơn giản (IMO). Dưới đây là một số kết quả BenchmarkDotNet từ máy của tôi:

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 |

Tôi cũng đã tạo ra một ý chính với tất cả các bài kiểm tra .


Nó không sử dụng từ khóa không an toàn, nhưng nó gọi mã không an toàn bằng mọi cách bằng cách sử dụng System.R
nb.CompilerService.Unsafe

Tôi đã cập nhật NewMemCmpcâu trả lời của mình để sử dụng AVX-2
Mr Anderson

8

Tôi đã phát triển một phương pháp hơi đập memcmp()(câu trả lời của plinth) và nhịp rất nhanh EqualBytesLongUnrolled()(câu trả lời của Arek Bulski) trên PC của tôi. Về cơ bản, nó hủy bỏ vòng lặp bằng 4 thay vì 8.

Cập nhật ngày 30 tháng 3 năm 2019 :

Bắt đầu từ .NET core 3.0, chúng tôi có hỗ trợ SIMD!

Giải pháp này là nhanh nhất bởi một tỷ lệ đáng kể trên PC của tôi:

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

Các phép đo của tôi khác với .NET 462 có thể NETCORE:
Motlicek Petr

Mã gặp sự cố khi so sánh hai mảng có độ dài 0, vì ghim trả về null.
Glenn Slayden

memcmp không chỉ là một so sánh công bằng. Nó cung cấp thông tin mà đối tượng lớn hơn hoặc nhỏ hơn. Bạn có thể áp dụng thuật toán của bạn cho mục đích này và kiểm tra hiệu suất?
nicolay.anykienko

Có nhanh hơn Spanmemcpykhông?
tằm lửa

@silkfire Trên .NET core 3 và CPU hiện đại, cần nhanh hơn 2-3 lần cho các mảng lớn.
Ông Anderson

6

Tôi sẽ sử dụng mã không an toàn và chạy forvòng lặp so sánh con trỏ Int32.

Có lẽ bạn cũng nên xem xét việc kiểm tra các mảng là không null.


5

Nếu bạn nhìn vào cách .NET thực hiện chuỗi.Equals, bạn sẽ thấy rằng nó sử dụng một phương thức riêng có tên là EqualsHelper có triển khai con trỏ "không an toàn". .NET Reflector là bạn của bạn để xem mọi thứ được thực hiện như thế nào trong nội bộ.

Điều này có thể được sử dụng như một khuôn mẫu để so sánh mảng byte mà tôi đã thực hiện trên bài đăng trên blog So sánh mảng byte nhanh trong C # . Tôi cũng đã thực hiện một số điểm chuẩn thô sơ để xem khi nào việc thực hiện an toàn nhanh hơn mức không an toàn.

Điều đó nói rằng, trừ khi bạn thực sự cần hiệu suất sát thủ, tôi sẽ đi so sánh vòng lặp fr đơn giản.


3

Không thể tìm thấy giải pháp tôi hoàn toàn hài lòng với (hiệu suất hợp lý, nhưng không có mã / pinvoke không an toàn) vì vậy tôi đã đưa ra giải pháp này, không có gì thực sự độc đáo, nhưng hoạt động:

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

Hiệu suất so với một số giải pháp khác trên trang này:

Vòng lặp đơn giản: 19837 tick, 1,00

* BitConverter: 4886 tick, 4.06

Không an toànCompare: 1636 tick, 12.12

EqualBytesLongUnrolled: 637 tick, 31,09

P / Gọi memcmp: 369 tick, 53,67

Đã thử nghiệm trong linqpad, 1000000 byte giống hệt nhau (trường hợp xấu nhất), mỗi lần lặp 500 lần.


vâng, tôi đã lưu ý rằng trong nhận xét của stackoverflow.com/a/1445280/4361 rằng thử nghiệm của tôi cho thấy điều này thực sự chậm hơn một chút so với vòng lặp đơn giản mà tôi có trong câu hỏi ban đầu.
Hafthor

bạn có chắc không? Trong thử nghiệm của tôi nó nhanh hơn 4 lần? Không có gì đánh bại mã bản địa cũ tốt, ngay cả với việc sắp xếp lại trên đầu.
Zar Shardan

3

Có vẻ như EqualBytesLongUnrolled là tốt nhất từ ​​đề xuất trên.

Các phương thức bị bỏ qua (Enumerable.SequenceEqual, ArchitecturalComparisons.SturationuralEqualityComparer.Equals), không phải là bệnh nhân chậm. Trên mảng 265 MB tôi đã đo được điều này:

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 |

Tôi đã cập nhật NewMemCmpcâu trả lời của mình để sử dụng AVX-2
Mr Anderson

3

Tôi chưa thấy nhiều giải pháp linq ở đây.

Tôi không chắc chắn về ý nghĩa hiệu suất, tuy nhiên tôi thường tuân linqtheo quy tắc ngón tay cái và sau đó tối ưu hóa sau này nếu cần thiết.

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

Xin lưu ý rằng điều này chỉ hoạt động nếu chúng là các mảng có cùng kích thước. một phần mở rộng có thể trông như vậy

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

Toàn bộ điểm của câu hỏi là một giải pháp nhanh hơn mà hàm được đăng trong câu hỏi.
CodeInChaos

3

Tôi đã thực hiện một số phép đo bằng cách sử dụng chương trình phát hành .net 4.7 đính kèm mà không có trình gỡ lỗi đính kèm. Tôi nghĩ rằng mọi người đã sử dụng sai số liệu vì những gì bạn muốn nếu bạn quan tâm đến tốc độ ở đây là mất bao lâu để tìm ra nếu hai mảng byte bằng nhau. tức là thông lượng tính theo 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

Như bạn có thể thấy, không có cách nào tốt hơn memcmpvà đó là những đơn đặt hàng có cường độ nhanh hơn. Một forvòng lặp đơn giản là lựa chọn tốt thứ hai. Và nó vẫn làm tôi suy nghĩ tại sao Microsoft không thể bao gồm một Buffer.Comparephương thức.

[Chương trình.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

Để so sánh các mảng byte ngắn, sau đây là một hack thú vị:

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

Sau đó, tôi có lẽ sẽ rơi ra giải pháp được liệt kê trong câu hỏi.

Thật thú vị khi thực hiện phân tích hiệu suất của mã này.


int i = 0; for (; i <a1.Lipse-7; i + = 8) if (BitConverter.ToInt64 (a1, i)! = BitConverter.ToInt64 (a2, i)) trả về false; for (; i <a1.Lipse; i ++) if (a1 [i]! = a2 [i]) return false; trả lại đúng sự thật; // chậm hơn một chút so với đơn giản cho vòng lặp.
Hafthor

2

Đối với những người quan tâm đến đơn hàng (tức là muốn bạn memcmptrả lại intnhư vậy thay vì không có gì), .NET Core 3.0 (và có lẽ là .NET Standard 2.1 aka .NET 5.0) sẽ bao gồm một Span.SequenceCompareTo(...)phương thức mở rộng (cộng với a Span.SequenceEqualTo) có thể được sử dụng để so sánh hai ReadOnlySpan<T>trường hợp ( where T: IComparable<T>).

Trong đề xuất GitHub ban đầu , cuộc thảo luận bao gồm các so sánh tiếp cận với các tính toán bảng nhảy, đọc byte[]như long[]sử dụng SIMD và p / gọi cho triển khai CLRmemcmp .

Trong tương lai, đây sẽ là phương pháp tiếp theo của bạn để so sánh các mảng byte hoặc phạm vi byte (nên sử dụng Span<byte>thay vì byte[]cho API .NET Standard 2.1 của bạn) và nó đủ nhanh đến mức bạn không còn quan tâm đến việc tối ưu hóa nó (và không, mặc dù có những điểm tương đồng về tên nhưng nó không hoạt động một cách kinh khủng như kinh khủng 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

Tôi nghĩ về các phương pháp tăng tốc chuyển khối được tích hợp trong nhiều card đồ họa. Nhưng sau đó, bạn sẽ phải sao chép tất cả các byte dữ liệu, vì vậy điều này không giúp ích gì cho bạn nếu bạn không muốn triển khai toàn bộ phần logic của mình trong mã không phụ thuộc vào phần cứng và không được quản lý ...

Một cách khác để tối ưu hóa tương tự như cách tiếp cận được hiển thị ở trên là lưu trữ càng nhiều dữ liệu của bạn càng tốt trong một [] dài hơn là một byte [] ngay từ đầu, ví dụ nếu bạn đang đọc tuần tự từ tệp nhị phân, hoặc nếu bạn sử dụng tệp ánh xạ bộ nhớ, hãy đọc dữ liệu dưới dạng dài [] hoặc các giá trị dài đơn. Sau đó, vòng lặp so sánh của bạn sẽ chỉ cần 1/8 số lần lặp mà nó sẽ phải thực hiện cho một byte [] chứa cùng một lượng dữ liệu. Vấn đề là khi nào và tần suất bạn cần so sánh với thời gian và tần suất bạn cần truy cập dữ liệu theo cách từng byte, ví dụ: sử dụng nó trong lệnh gọi API làm tham số trong phương thức dự kiến một byte []. Cuối cùng, bạn chỉ có thể biết nếu bạn thực sự biết trường hợp sử dụng ...


Câu trả lời được chấp nhận sẽ lặp lại bộ đệm byte dưới dạng bộ đệm dài và so sánh nó như bạn mô tả.
Hafthor

1

Điều này gần như chắc chắn chậm hơn nhiều so với bất kỳ phiên bản nào khác được đưa ra ở đây, nhưng thật vui khi viết.

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

1

Tôi đã giải quyết một giải pháp lấy cảm hứng từ phương pháp EqualBytesLongUnrolled được đăng bởi ArekBulski với một tối ưu hóa bổ sung. Trong ví dụ của tôi, sự khác biệt mảng trong mảng có xu hướng ở gần đuôi của mảng. Trong thử nghiệm, tôi thấy rằng khi đây là trường hợp của các mảng lớn, việc có thể so sánh các phần tử mảng theo thứ tự ngược lại giúp giải pháp này đạt được hiệu suất rất lớn so với giải pháp dựa trên memcmp. Đây là giải pháp:

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

Xin lỗi, nếu bạn đang tìm kiếm một cách được quản lý, bạn đã thực hiện đúng và theo hiểu biết của tôi, không có phương pháp tích hợp nào trong BCL để thực hiện điều này.

Bạn nên thêm một số kiểm tra null ban đầu và sau đó chỉ sử dụng lại nó như thể nó ở đâu trong BCL.


Bạn đã đúng khi bạn viết điều đó, tuy nhiên vào năm 2010 (.NET 4.0), một phương pháp BCL đã xuất hiện, hãy xem câu trả lời của Ohad Schneider. Tại thời điểm của câu hỏi, .NET 3.5 có Linq (xem câu trả lời của aku).
Jeppe Stig Nielsen


-2

Nếu bạn đang tìm kiếm một bộ so sánh đẳng thức byte rất nhanh, tôi khuyên bạn nên xem bài viết này của STSdb ​​Labs: Bộ so sánh đẳng thức mảng Byte. Nó có một số cách triển khai nhanh nhất để so sánh đẳng thức byte [], được trình bày, kiểm tra hiệu năng và tóm tắt.

Bạn cũng có thể tập trung vào các triển khai này:

BigEndianByteArrayComparer - byte nhanh [] array Comparer từ trái sang phải (BigEndian) BigEndianByteArrayEqualityComparer - - byte nhanh [] bình đẳng Comparer từ trái sang phải (BigEndian) LittleEndianByteArrayComparer - byte nhanh [] array Comparer từ phải sang trái (LittleEndian) LittleEndianByteArrayEqualityComparer - byte nhanh [] so sánh bình đẳng từ phải sang trái (LittleEndian)


-2

Câu trả lời ngắn gọn là đây:

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

Theo cách đó, bạn có thể sử dụng so sánh chuỗi .NET được tối ưu hóa để so sánh mảng byte mà không cần phải viết mã không an toàn. Đây là cách nó được thực hiện trong nền :

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

Trong các thử nghiệm của tôi, việc chuyển đổi thành một chuỗi phá hủy lợi thế của việc so sánh nhanh hơn. Điều này chậm hơn khoảng 2,5 lần so với một vòng lặp đơn giản.
Doug Clutter

Khi tôi làm điều tương tự, đơn giản là chậm hơn khoảng 8 lần. Bạn có thể viết mã của bạn ở đây?
Alon

1
Điều này sẽ phá vỡ nếu một byte chứa giá trị null (0)?
Joseph Lennox

-1 Cũng như bị chậm do chuyển đổi thành chuỗi như được chỉ ra bởi @DougClutter, điều này sẽ thất bại nếu mảng byte chứa dữ liệu không phải ASCII. Để có kết quả đúng, cần sử dụng iso-8859-1.
Joe

2
Compare(new byte[]{128}, new byte[]{ 255 }) == truehoàn toàn không có lỗi ...
CodeInChaos

-2

Vì nhiều giải pháp ưa thích ở trên không hoạt động với UWP và vì tôi yêu Linq và các phương pháp tiếp cận chức năng, tôi gây áp lực cho bạn phiên bản của tôi cho vấn đề này. Để thoát khỏi sự so sánh khi sự khác biệt đầu tiên xảy ra, tôi đã chọn .irstOrDefault ()

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 vì mã này bị hỏng và dường như chưa được kiểm tra. Điều này đưa ra một IndexOutOfRangeExceptionkhi so sánh các mảng không trống bởi vì bạn đang truy cập các phần tử 1thông qua ba0.Lengthkhi nào nó sẽ 0thông qua ba0.Length - 1. Nếu bạn khắc phục điều đó với Enumerable.Range(0, ba0.Length)nó vẫn trả về không chính xác truecho các mảng có độ dài bằng nhau, trong đó chỉ có các phần tử đầu tiên khác nhau vì bạn không thể phân biệt giữa các phần tử đầu tiên thỏa mãn predicatekhông có phần tử nào thỏa mãn predicate; FirstOrDefault<int>()trả lại 0trong cả hai trường hợp.
BACON

Bài học ở đây những đứa trẻ: đừng mang dao đến đấu súng
Richard Hauer
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.