เปรียบเทียบสองอาร์เรย์ไบต์ใน. NET


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 จัดตำแหน่ง" นั่นเป็นเรื่องใหญ่ถ้า คุณควรแก้ไขรหัสเพื่อแสดงว่า
Joe Chung

4
กลับ a1.Length == a2.Length &&! a1.Where ((t, i) => t! = a2 [i]) ใด ๆ ();
alerya

ฉันชอบ @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 ใช้เวลาในการประมวลผลนานกว่าการเปรียบเทียบที่ไม่ปลอดภัยหรือไม่ โดยเฉพาะอย่างยิ่งเมื่อคุณทำการเปรียบเทียบ 1,000 รายการ
tcables

90
ใช่มันทำงานช้ากว่าการเปรียบเทียบที่ไม่ปลอดภัยประมาณ 50x
Hafthor

27
นี่เป็นสิ่งที่ทำให้คนตายที่นี่จริง ๆ แต่ความช้าเป็นคำที่ไม่ดีที่จะใช้ที่นี่ ช้าลง 50x ฟังดูไม่ดี แต่บ่อยครั้งที่คุณเปรียบเทียบข้อมูลมากพอที่จะสร้างความแตกต่างและถ้าคุณเป็นคุณจะต้องเปรียบเทียบสิ่งนี้กับกรณีของคุณเองด้วยเหตุผลมากมาย ตัวอย่างเช่นโปรดทราบว่าผู้สร้างคำตอบที่ไม่ปลอดภัยบันทึกความแตกต่างของ 7x ช้าซึ่งต่างจาก 50x ช้ากว่า (ความเร็วของวิธีที่ไม่ปลอดภัยยังขึ้นอยู่กับการจัดตำแหน่งของข้อมูล) ในกรณีที่หายากซึ่งตัวเลขเหล่านี้มีความสำคัญ P / การเรียกใช้จะเร็วยิ่งขึ้น
Selali Adobor

4
ดังนั้นการนำไปใช้ที่ช้าลงจะได้รับมากกว่า 300 ไลค์ ฉันขอแนะนำให้ hooking msvcrt.dll เนื่องจากเป็นวิธีที่เร็วที่สุดในการทำงานให้เสร็จ
TGarrett

69
เร็วที่สุดไม่ใช่สิ่งสำคัญที่สุดสำหรับธุรกิจ ความสามารถในการบำรุงรักษานั้น "เร็ว" มากกว่าการประหยัดในรหัสนี้จะเท่ากับ 99% ของกรณี ฉันใช้ SequenceEqual และรหัสทั้งหมดของฉันคือ <1ms สิ่งเหล่านั้นที่คุณกำลังบันทึกจะไม่เพิ่มขึ้นอีกถึง 5 นาทีหากไม่มีการอ่าน P / Invoke
PRMan

236

P / เรียกใช้พลังเปิดใช้งาน!

[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/…
Erik Forbes

25
การปักหมุดไม่จำเป็นในกรณีนี้ marshaller ทำการตรึงอัตโนมัติเมื่อเรียกรหัสเนทีฟด้วย PInvoke การอ้างอิง: stackoverflow.com/questions/2218444/…
มาร์คกลาสโกว์

14
P / Invoke อาจลวงตา แต่มันก็เป็นวิธีที่เร็วที่สุดในบรรดาโซลูชั่นที่นำเสนอรวมถึงการติดตั้งที่ฉันใช้ด้วยซึ่งใช้การเปรียบเทียบขนาดตัวชี้ที่ไม่ปลอดภัย มีการเพิ่มประสิทธิภาพเล็กน้อยที่คุณสามารถทำได้ก่อนที่จะโทรหารหัสเนทีฟรวมถึงความเท่าเทียมกันของการอ้างอิงและการเปรียบเทียบองค์ประกอบแรกและสุดท้าย
Josh

38
ทำไมต้องบู ผู้ลงประกาศต้องการการนำไปใช้งานที่รวดเร็วและการเปรียบเทียบภาษาแอสเซมบลีที่ไม่สามารถเอาชนะได้ ฉันไม่ทราบวิธีรับ "REPE CMPSD" จาก. NET โดยไม่มี P / INVOKE
Jason Goemaat

14
Nitpick: MSVCR.dll ไม่ควรใช้รหัสผู้ใช้ ในการใช้ MSVCR คุณจะต้องแจกจ่าย runtime ใช้เวอร์ชันที่คุณเผยแพร่ ( 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
ช้ามาก ประมาณ 180x ช้ากว่าง่ายสำหรับลูป
Hafthor

ใช้งานได้ แต่ฉันไม่เข้าใจว่าทำไม ไบต์ [] เป็นชนิดดั้งเดิมที่ไม่ได้ใช้ IStructuralEquatable ดังนั้นทำไมคุณถึงเลือกนักแสดง - และนักแสดงโดยปริยายในนั้น! และจากนั้นส่วนต่อประสานของวิธี "Equals" จะสามารถใช้งานได้อย่างน่าอัศจรรย์ ... การใช้งานของวิธีนั้นมาจากไหน? บางคนสามารถแย้มฉันได้ไหม
Josh

1
StructuralComparisons.StructuralEqualityComparer.Equals(a1, a2)ทำไมไม่เพียง ไม่มีNullReferenceExceptionที่นี่
ta.speot.is

1
@ ta.speot.is ขอบคุณไม่สามารถโต้เถียงกับสายการบินเดียว! วิธีการแก้ปัญหาก่อนหน้านี้มีประสิทธิภาพมากขึ้นเล็กน้อยเนื่องจากมันได้บันทึกการส่งไปยัง IStructuralEquatable (อาร์เรย์เป็นที่รู้จักกันว่าเป็น
Ohad Schneider

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ห่วง การใช้ไลบรารี J # ดำเนินการเทียบเท่ากับforลูปดั้งเดิม การใช้. EquenceEqual จะทำงานช้าลงประมาณเจ็ดเท่า ฉันคิดว่าเพียงเพราะใช้ IEnumerator.MoveNext ฉันจินตนาการว่าโซลูชั่นที่ใช้ LINQ นั้นช้าหรือแย่กว่านั้น


3
ทางออกที่ดี แต่คำใบ้หนึ่ง (เล็ก): การเปรียบเทียบถ้าการอ้างอิง a1 และ a2 เท่ากันอาจเร็วขึ้นหากสิ่งใดที่ให้อาร์เรย์เหมือนกันสำหรับ a1 และ b1
mmmmmmmm

12
ข้อมูลการทดสอบใหม่ใน. NET 4 x64 release: IStructualEquatable.equals ~ 180x ช้ากว่า SequenceEqual 15x ช้าลงแฮช SHA1 เปรียบเทียบ 11x ช้ากว่า bitconverter ~ เดียวกันไม่ปลอดภัย 7x เร็วกว่า pinvoke เร็วขึ้น 11x ค่อนข้างเท่ที่ไม่ปลอดภัยจะช้ากว่า P / Invoke บน memcmp เพียงเล็กน้อยเท่านั้น
Hafthor

3
ลิงก์นี้ให้รายละเอียดที่ดีเกี่ยวกับสาเหตุที่การจัดตำแหน่งหน่วยความจำมีความสำคัญibm.com/developerworks/library/pa-dalignดังนั้นการปรับให้เหมาะสมอาจเป็นการตรวจสอบการจัดตำแหน่งและถ้าทั้งสองอาร์เรย์อยู่นอกการจัดตำแหน่งด้วยจำนวนเท่ากันให้ทำไบต์เปรียบเทียบ บนขอบเขต qword
Hafthor

5
จะไม่ให้เป็นเท็จเมื่อทั้ง a1 และ a2 เป็นโมฆะ?
nawfal

2
@CristiDiaconescu ฉันวนคำตอบของ KevinDriedger สิ่งที่ฉันควรทำคือทำให้ชุดทดสอบและผลลัพธ์ของฉันพร้อมใช้งานบน GitHub และลิงก์ไปยังคำตอบของฉัน
Hafthor

73

Span<T> เสนอทางเลือกที่มีการแข่งขันสูงโดยไม่ต้องสร้างความสับสนและ / หรือความผิดเพี้ยนแบบพกพาลงในฐานรหัสแอปพลิเคชันของคุณ

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

(ที่กล้าของ) การดำเนินงาน ณ วันที่ .NET หลัก 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 |

ฉันประหลาดใจที่เห็นSpansEqualว่าไม่ได้ออกมาอยู่ด้านบนสำหรับวิธีการขนาดอาเรย์สูงสุด แต่ความแตกต่างนั้นน้อยมากจนฉันไม่คิดว่ามันจะสำคัญ

ข้อมูลระบบของฉัน:

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
Zastai

1
@Zastai ใช่{ReadOnly,}Span<T>มีรุ่นของตัวเองSequenceEqual(ชื่อเดียวกันเพราะมีสัญญาแบบเดียวกับIEnumerable<T>วิธีการขยายที่สอดคล้องกันมันเร็วขึ้น) โปรดทราบว่า{ReadOnly,}Span<T>ไม่สามารถใช้IEnumerable<T>วิธีส่วนขยายได้เนื่องจากข้อ จำกัด ด้านref structประเภท
Joe Amenta

1
@Sentinel แพคเกจSystem.Memoryมีการใช้งาน "แบบพกพา" / "ช้า" Span<T>สำหรับnetstandard1.1และเหนือ (ดังนั้นเล่นกับแผนภูมิเชิงโต้ตอบนี้เพื่อดูว่าเป็นใคร) "Fast" Span<T>มีอยู่ใน. NET Core 2.1 เท่านั้นในขณะนี้ แต่โปรดทราบว่าSequenceEqual<T>โดยเฉพาะอย่างยิ่งควรมีความแตกต่างเล็กน้อยระหว่าง "เร็ว" และ "ช้า" / "พกพา" (แม้ว่าnetstandard2.0เป้าหมายจะเห็นการปรับปรุงเล็กน้อยเพราะพวกเขา มีเส้นทางรหัส vectorized)
Joe Amenta

1
install-package system.memory
Chris Moschini

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>ที่ (มีผล) เปรียบเทียบสองอาร์เรย์ไบต์ ทราบว่ายังมีผู้ประกอบการที่มีการแปลงนัยจากSystem.Data.Linq.Binarybyte[]

เอกสาร MSDN: System.Data.Linq.Binary

Decompile ของ Reflector ของวิธี 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;
}

สิ่งที่น่าสนใจก็คือพวกมันจะทำการวนซ้ำแบบไบต์ต่อไบต์เท่านั้นหากการแฮชของวัตถุไบนารีทั้งสองเหมือนกัน อย่างไรก็ตามสิ่งนี้มีค่าใช้จ่ายในการคำนวณแฮชในตัวสร้างของBinaryวัตถุ (โดยการเลื่อนอาร์เรย์ด้วยforloop :-))

การนำไปใช้ข้างต้นหมายความว่าในกรณีที่เลวร้ายที่สุดคุณอาจต้องสำรวจอาร์เรย์สามครั้ง: ก่อนอื่นเพื่อคำนวณแฮชของ array1 จากนั้นคำนวณค่าแฮชของ array2 และสุดท้าย (เพราะนี่เป็นสถานการณ์กรณีที่เลวร้ายที่สุดความยาวและแฮชเท่ากัน) ไบต์ในอาร์เรย์ 1 กับไบต์ในอาร์เรย์ 2

โดยรวมแม้ว่าSystem.Data.Linq.Binaryจะสร้างไว้ใน BCL ฉันไม่คิดว่ามันจะเป็นวิธีที่เร็วที่สุดในการเปรียบเทียบสองอาร์เรย์ไบต์: - |


20

ฉันโพสต์คำถามที่คล้ายกันเกี่ยวกับการตรวจสอบว่าไบต์ [] เต็มศูนย์ (รหัส 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;
    }
}

วัดจากสองอาร์เรย์ 256MB ไบต์:

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

1
ผมยืนยัน. ฉันก็ทำการทดสอบด้วย นี่คือเร็วกว่าคำตอบที่ใช้ memcmp โทรไม่ปลอดภัย
ujeenator

1
@AmberdeBlack คุณแน่ใจหรือไม่ คุณทดสอบกับอาร์เรย์เล็ก ๆ หรือไม่?
Zar Shardan

@ArekBulski คุณแน่ใจหรือว่านี่เร็วกว่า memcmp ทำให้การทดสอบของฉันแสดงเป็นอย่างอื่น
Zar Shardan

ฉันได้ประสิทธิภาพที่เหมือนกันระหว่างนี้กับ memcmp ดังนั้น +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
นั่นคือสิ่งที่ฉันใช้ แต่มันเป็นเรื่องอืมม ... ฟังดูเหมือนเป็นการเปรียบเทียบตามลำดับที่คุณจะทำโดยใช้การวนรอบแบบง่าย ๆ ดังนั้นจึงไม่เร็วมาก มันคงจะดีถ้าได้สะท้อนมันและดูว่ากำลังทำอะไรอยู่ ตัดสินโดยชื่อมันไม่มีอะไรแฟนซี
Sergey Akopov

1
ใช่ แต่ได้กล่าวถึงแล้วในคำตอบที่ยอมรับแล้ว btw คุณสามารถลบข้อมูลจำเพาะของประเภทที่นั่น
nawfal

10

ขอเพิ่มอีกหนึ่ง!

เมื่อเร็ว ๆ นี้ไมโครซอฟท์เปิดตัวแพคเกจ NuGet พิเศษSystem.Runtime.CompilerServices.Unsafe มันพิเศษเพราะมันเขียนในILและมีฟังก์ชั่นระดับต่ำที่ไม่สามารถใช้งานได้โดยตรงใน C #

หนึ่งในวิธีการของมันUnsafe.As<T>(object)ช่วยให้การหล่อประเภทการอ้างอิงใด ๆ ไปยังอีกประเภทการอ้างอิงข้ามการตรวจสอบความปลอดภัยใด ๆ นี่เป็นความคิดที่แย่มากแต่ถ้าทั้งสองประเภทมีโครงสร้างเดียวกันมันสามารถใช้งานได้ ดังนั้นเราสามารถใช้สิ่งนี้เพื่อส่ง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.Lengthจะยังคงส่งคืนความยาวของอาเรย์ดั้งเดิมเนื่องจากเก็บไว้ในฟิลด์ในโครงสร้างหน่วยความจำของอาเรย์

วิธีนี้ไม่เร็วเท่าวิธีอื่นที่แสดงไว้ที่นี่ แต่มันเร็วกว่าวิธีที่ไร้เดียงสามากไม่ใช้รหัสที่ไม่ปลอดภัยหรือ P / เรียกใช้หรือตรึงและการใช้งานค่อนข้างตรงไปตรง (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 |

ฉันได้สร้างส่วนสำคัญด้วยการทดสอบทั้งหมด


ไม่ใช้คำหลักที่ไม่ปลอดภัย แต่จะเรียกรหัสที่ไม่ปลอดภัยต่อไปโดยใช้ System.Runtime.CompilerServices.Unsafe
Paulo Zemek

ฉันได้อัปเดตNewMemCmpคำตอบเพื่อใช้ AVX-2
Mr Anderson เมื่อ

8

ฉันพัฒนาวิธีการที่เต้นเล็กน้อยmemcmp()(คำตอบของรูปสลัก) และเต้นEqualBytesLongUnrolled()เบา ๆ (คำตอบของ Arek Bulski) บนพีซีของฉัน โดยพื้นฐานแล้วมันจะทำการวนลูป 4 แทน 8

อัพเดท 30 มีนาคม 2019 :

เริ่มต้นใน. NET core 3.0 เรามีรองรับ SIMD!

โซลูชันนี้เร็วที่สุดด้วยอัตรากำไรขั้นต้นสูงบนพีซีของฉัน:

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

รหัสล้มเหลวเมื่อเปรียบเทียบสองอาร์เรย์ 0 nullความยาวเพราะผลตอบแทนที่ปักหมุด
Glenn Slayden

memcmp ไม่ได้เป็นเพียงเครื่องมือเปรียบเทียบหุ้น มันให้ข้อมูลที่วัตถุมีขนาดใหญ่หรือเล็ก คุณสามารถใช้อัลกอริทึมของคุณเพื่อวัตถุประสงค์นี้และตรวจสอบประสิทธิภาพได้หรือไม่
nicolay.anykienko

มันเร็วกว่าSpanและmemcpy?
Silkfire

@silkfire บน. NET core 3 และ CPU สมัยใหม่ควรเร็วขึ้น 2-3 เท่าสำหรับอาร์เรย์ขนาดใหญ่
นายแอนเดอร์สัน

6

ฉันจะใช้รหัสที่ไม่ปลอดภัยและเรียกใช้การforวนลูปเปรียบเทียบตัวชี้ Int32

บางทีคุณควรพิจารณาตรวจสอบอาร์เรย์ว่าไม่เป็นโมฆะ


5

ถ้าคุณดูว่า. NET ทำสตริงอย่างไรคุณจะเห็นว่ามันใช้วิธีการส่วนตัวที่เรียกว่า EqualsHelper ซึ่งมีการใช้งานตัวชี้ "ไม่ปลอดภัย" .NET Reflectorเป็นเพื่อนของคุณเพื่อดูว่าสิ่งต่าง ๆ เกิดขึ้นภายในได้อย่างไร

นี้สามารถใช้เป็นแม่แบบสำหรับการเปรียบเทียบอาร์เรย์ไบต์ซึ่งผมได้นำไปใช้ในการโพสต์บล็อกไบต์ด่วนเปรียบเทียบอาร์เรย์ใน C # ฉันยังได้ทำเกณฑ์พื้นฐานบางอย่างเพื่อดูว่าการใช้งานที่ปลอดภัยเร็วกว่าที่ไม่ปลอดภัย

ที่กล่าวว่าถ้าคุณไม่ต้องการประสิทธิภาพนักฆ่าจริงๆฉันจะไปเปรียบเทียบลูป fr ง่าย ๆ


3

ไม่พบโซลูชันที่ฉันมีความสุขอย่างสมบูรณ์ (ประสิทธิภาพที่สมเหตุสมผล แต่ไม่มีรหัส / pinvoke ที่ไม่ปลอดภัย) ดังนั้นฉันจึงได้สิ่งนี้ขึ้นมาไม่มีอะไรเป็นของจริง แต่ใช้งานได้:

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

ประสิทธิภาพเทียบกับโซลูชันอื่น ๆ บางส่วนในหน้านี้:

Simple Loop: 19837 ticks, 1.00

* BitConverter: 4886 เห็บ 4.06

UnsafeCompare: 1636 เห็บ 12.12

EqualBytesLongUnrolled: 637 ticks, 31.09

P / วิงวอน memcmp: 369 เห็บ 53.67

การทดสอบใน LinqPad, 1000000 ไบต์อาร์เรย์เหมือนกัน (กรณีสถานการณ์ที่เลวร้าย) 500 ซ้ำแต่ละ


ใช่ฉันตั้งข้อสังเกตว่าในความคิดเห็นของstackoverflow.com/a/1445280/4489ว่าการทดสอบของฉันแสดงให้เห็นว่าจริง ๆ แล้วช้ากว่าแบบง่ายสำหรับลูปที่ฉันมีในคำถามเดิมเล็กน้อย
Hafthor

คุณแน่ใจไหม? ในการทดสอบของฉันเร็วกว่าไหม 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 |

ฉันได้อัปเดตNewMemCmpคำตอบเพื่อใช้ AVX-2
Mr Anderson เมื่อ

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

3

ฉันทำการวัดบางอย่างโดยใช้โปรแกรม. net 4.7 build build ที่แนบมาโดยไม่ต้องดีบักเกอร์ ฉันคิดว่าผู้คนใช้เมตริกที่ไม่ถูกต้องเนื่องจากสิ่งที่คุณเป็นถ้าคุณสนใจความเร็วนี่คือระยะเวลาที่ใช้ในการคิดว่าสองไบต์อาร์เรย์เท่ากันหรือไม่ เช่นปริมาณงานเป็นไบต์

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วนรอบอย่างง่ายเป็นตัวเลือกที่ดีที่สุดอันดับสอง และมันก็ทำให้ฉันนึกไม่ออกว่าทำไม 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; สำหรับ (; i <a1.Length-7; i + = 8) ถ้า (BitConverter.ToInt64 (a1, i)! = BitConverter.ToInt64 (a2, i)) กลับเท็จ; สำหรับ (; i <a1.Length; i ++) ถ้า (a1 [i]! = a2 [i]) กลับเท็จ กลับจริง // ช้ากว่าเล็กน้อยสำหรับลูป
Hafthor

2

สำหรับบรรดาของคุณที่สนใจเกี่ยวกับการสั่งซื้อ (เช่นต้องการให้คุณmemcmpกลับมาintเหมือนมันไม่ควรจะไม่มีอะไร),. NET Core 3.0 (และสันนิษฐาน. NET มาตรฐาน 2.1 aka. NET 5.0) จะรวมถึงSpan.SequenceCompareTo(...)วิธีการขยาย (บวกSpan.SequenceEqualTo) ที่สามารถ นำมาใช้เพื่อเปรียบเทียบสองReadOnlySpan<T>อินสแตนซ์ ( where T: IComparable<T>)

ในข้อเสนอเดิม GitHubอภิปรายรวมถึงการเปรียบเทียบวิธีการด้วยการคำนวณตารางกระโดดอ่านbyte[]เป็นlong[]การใช้งาน SIMD และ P / memcmpวิงวอนกับการดำเนินงานของ

นี่เป็นวิธีการไปสู่การเปรียบเทียบอาร์เรย์ไบต์หรือช่วงไบต์ (เช่นควรใช้Span<byte>แทนbyte[]. NET API 2.1 มาตรฐาน API ของคุณ) และมันก็เร็วพอที่คุณไม่ควรกังวลเกี่ยวกับการปรับให้เหมาะสมอีกต่อไป (และ ไม่แม้จะมีความคล้ายคลึงกันในชื่อมันก็ไม่ได้ดำเนินการอย่างสุดซึ้งเหมือนความน่ากลัว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

ฉันคิดเกี่ยวกับวิธีการเร่งความเร็วในการถ่ายโอนบล็อกที่สร้างขึ้นในการ์ดกราฟิกจำนวนมาก แต่คุณจะต้องคัดลอกข้อมูลไปทุกไบต์ดังนั้นนี่ไม่ได้ช่วยอะไรมากถ้าคุณไม่ต้องการใช้ตรรกะทั้งหมดในโค้ดที่ไม่มีการจัดการและขึ้นกับฮาร์ดแวร์ ...

วิธีการเพิ่มประสิทธิภาพอีกวิธีหนึ่งคล้ายกับวิธีที่แสดงข้างต้นคือการจัดเก็บข้อมูลของคุณให้มากที่สุดเท่าที่จะเป็นไปได้ใน [ยาว] แทนที่จะเป็นไบต์ [] ตั้งแต่ต้นตัวอย่างเช่นถ้าคุณอ่านตามลำดับจากไฟล์ไบนารี หรือถ้าคุณใช้ไฟล์ที่แม็พหน่วยความจำให้อ่านข้อมูลนาน [] หรือค่ายาวเดี่ยว จากนั้นการเปรียบเทียบลูปของคุณจะต้องการ 1 / 8th ของจำนวนการวนซ้ำที่จะต้องทำสำหรับไบต์ [] ที่มีจำนวนข้อมูลเท่ากัน มันเป็นเรื่องของเวลาและความถี่ที่คุณต้องการเปรียบเทียบกับเวลาและความถี่ที่คุณต้องการเข้าถึงข้อมูลในลักษณะไบต์ต่อไบต์เช่นใช้ในการเรียก API เป็นพารามิเตอร์ในวิธีการที่คาดหวัง ไบต์ [] ในที่สุดคุณสามารถบอกได้ว่าคุณรู้จักกรณีการใช้งานจริงหรือไม่


คำตอบที่ยอมรับจะกู้คืนบัฟเฟอร์ไบต์เป็นบัฟเฟอร์ยาวและเปรียบเทียบตามที่คุณอธิบาย
Hafthor

1

นี่เกือบจะช้ากว่าเวอร์ชั่นอื่น ๆ ที่ให้ไว้อย่างแน่นอน แต่มันสนุกที่จะเขียน

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

1

ฉันตัดสินโซลูชันที่ได้รับแรงบันดาลใจจากวิธี EqualBytesLongUnrolled ที่โพสต์โดย ArekBulski พร้อมการเพิ่มประสิทธิภาพเพิ่มเติม ในอินสแตนซ์ของฉันความแตกต่างของอาร์เรย์ในอาร์เรย์มักอยู่ใกล้กับส่วนท้ายของอาร์เรย์ ในการทดสอบฉันพบว่าเมื่อเป็นกรณีของอาร์เรย์ขนาดใหญ่ความสามารถในการเปรียบเทียบองค์ประกอบของอาร์เรย์ในลำดับย้อนกลับทำให้โซลูชันนี้ได้รับประสิทธิภาพที่เหนือกว่าโซลูชันที่ใช้ 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 ในตัวสำหรับการทำสิ่งนี้

คุณควรเพิ่มการตรวจสอบโมฆะเริ่มต้นจากนั้นนำมาใช้ซ้ำราวกับว่าอยู่ใน BCL


คุณถูกต้องเมื่อคุณเขียนสิ่งนั้นอย่างไรก็ตามในปี 2010 (. NET 4.0) มีวิธีการ BCL มาดูคำตอบของ Ohad Schneider ในช่วงเวลาของคำถาม. NET 3.5 มี Linq (ดูคำตอบของ aku)
Jeppe Stig Nielsen


-2

หากคุณกำลังมองหาตัวเปรียบเทียบความเท่าเทียมกันของอาร์เรย์แบบไบต์ที่รวดเร็วมากฉันขอแนะนำให้คุณดูที่บทความ STSdb ​​Labs นี้: ตัวเปรียบเทียบความเท่าเทียมกันของอาร์เรย์ไบต์ มันมีการใช้งานที่เร็วที่สุดสำหรับการเปรียบเทียบความเท่าเทียมกันของไบต์ [] ซึ่งนำเสนอการทดสอบประสิทธิภาพและสรุป

คุณยังสามารถมุ่งเน้นไปที่การใช้งานเหล่านี้:

BigEndianByteArrayComparer - ไบต์รวดเร็ว [] อาร์เรย์ Comparer จากซ้ายไปขวา (BigEndian) BigEndianByteArrayEqualityComparer - - ไบต์รวดเร็ว [] ความเสมอภาค Comparer จากซ้ายไปขวา (BigEndian) LittleEndianByteArrayComparer - ไบต์รวดเร็ว [] อาร์เรย์ Comparer จากขวาไปซ้าย (LittleEndian) LittleEndianByteArrayEqualityComparer - ไบต์ได้อย่างรวดเร็ว [] ความเท่าเทียมกัน Comparer จากขวาไปซ้าย (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);
    }
}

ในการทดสอบของฉันการแปลงเป็นสตริงจะทำลายข้อดีของการเปรียบเทียบที่เร็วกว่า นี่ช้ากว่าแบบลูปธรรมดาประมาณ 2.5 เท่า
Doug Clutter

เมื่อฉันทำเช่นเดียวกันง่ายสำหรับก็ช้าลงประมาณ 8 ครั้ง คุณสามารถเขียนรหัสของคุณที่นี่ได้ไหม
Alon

1
ตัวแบ่งนี้จะเกิดขึ้นหรือไม่หากไบต์มีค่า Null (0)
โจเซฟเลนน็อกซ์

-1 เช่นเดียวกับการช้าเนื่องจากการแปลงเป็นสตริงตามที่ระบุโดย @DougClutter สิ่งนี้จะล้มเหลวหากอาร์เรย์ไบต์มีข้อมูลที่ไม่ใช่ ASCII เพื่อให้ได้ผลลัพธ์ที่ถูกต้องจะต้องใช้ iso-8859-1
Joe

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

บทเรียนที่นี่สำหรับเด็ก ๆ : อย่านำมีดมาต่อสู้ด้วยปืน
Richard Hauer
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.