2013-02-12 42 views
5

Tôi đang làm việc trên một số IEqualityComparer được cho là so sánh các mảng của các kiểu nguyên thủy thực sự nhanh chóng. Kế hoạch của tôi là lấy con trỏ tới các mảng và memcmp chúng. Như thế này:Làm thế nào để sử dụng cố định với một biến loại Array hoặc T []?

public unsafe override bool Equals(T[] x, T[] y) 
    { 
     if (ReferenceEquals(x, y)) return true; 
     if (x == null || y == null) return false; 
     if (x.Length != y.Length) return false; 

     var xArray = (Array)x; 
     var yArray = (Array)y; 
     fixed (void* xPtr = xArray) //compiler error 1 
     fixed (T* yPtr = y) //compiler error 2 
     { 
      return memcmp(xPtr, yPtr, x.Length * this.elementSize); 
     } 
    } 

Báo cáo cố định không cho phép tôi để ghim hoặc Array hoặc T[].

Có thông báo lỗi là:

1. Cannot implicitly convert type 'System.Array' to 'void*' 
2. Cannot take the address of, get the size of, or declare a pointer to a managed type ('T') 

Bây giờ, tôi không thực sự quan tâm làm thế nào tôi làm công việc này (tôi không cam kết phương pháp này chính xác). Làm thế nào tôi có thể memcmp hai số T[] nơi tôi biết rằng T là loại nguyên thủy/có thể ghi được?

Tôi thực sự muốn tránh bật loại và tạo phiên bản mã chuyên biệt (và được nhân bản) cho từng loại thú vị. Bất kỳ loại giải pháp phản chiếu nào cũng không thể thực hiện được do các ràng buộc về hiệu suất (và có, tôi thực sự cần hiệu suất ở đây - không có cảnh báo tối ưu hóa sớm nào áp dụng vì nó là thông lệ trên Stack Overflow).

+1

bạn không thể gọi 'memcpy' từ C# –

+0

Bên cạnh đó, bạn đã thử' công cộng không an toàn ghi đè bool Equals (T [] x, T [] y) trong đó T: struct'? – Davio

+1

tại sao bạn đang cố tối ưu hóa mã của mình? bạn có thể tạo ra nhiều vấn đề về hiệu suất hơn bằng cách marshaling thành mã không được quản lý. –

Trả lời

5

nơi tôi biết rằng T là một nguyên thủy/blittable loại

Bạn biết điều đó, trình biên dịch không biết điều đó. CLR yêu cầu mọi thứ trong một đối tượng được ghim có thể không còn được di chuyển bởi bộ thu gom rác nữa. Đối với một mảng, bao gồm các phần tử mảng của nó. Chỉ loại T đủ điều kiện là loại giá trị đơn giản, một loại là blittable. Generics không cung cấp cho bạn một cách để hạn chế T đến một loại blittable.

Bạn thường khai báo các đối số của memcmp() dưới dạng byte []. Các marshaller pinvoke sau đó đã làm điều đúng và sẽ pin các mảng byte [] trước khi gọi memcmp(). Tuy nhiên, điều này sẽ không hoạt động vì bạn không thể dễ dàng chuyển đổi T [] thành byte []. Bạn sẽ phải ghim chính mình bằng GCHandle. Khai báo các đối số memcmp() dưới dạng IntPtr thay vì byte [] cho phù hợp.

Tập hợp con các loại có thể hoạt động trong thực tế đủ nhỏ để xem xét việc viết các phương thức quá tải thay vì phương pháp chung. Bây giờ cho phép các trình khắc phục sự cố để chăm sóc việc ghim, quá tải các khai báo hàm memcmp() tương ứng.

+1

Bạn có tham chiếu đến các đối tượng 'fixed' statement _not_ ghim trong bộ nhớ không, đó là [what] (http://msdn.microsoft.com/en-gb/library/ f58wzh21.aspx) đó là [được cho là] (http://blogs.msdn.com/b/ericlippert/archive/2009/08/27/what-s-the-difference-between-fixed-and-fixed.aspx) để – Rawling

+0

Chỉ cần nhìn vào IL được tạo ra bởi tuyên bố –

+1

Rất có giá trị, Hans.Tôi biết rằng trình biên dịch C# chỉ sử dụng ma con trỏ lồng nhau ("con trỏ nội thất") nhưng tôi không bao giờ nhận ra rằng điều này có nghĩa là đối tượng là * không * ghim .; Bây giờ tôi được thuyết phục rằng tuyên bố cuối cùng mà bạn đã chỉnh sửa là sự lựa chọn thiết kế phù hợp. Tôi sẽ theo dõi điều này. – usr

1

Tôi đồng ý với ý kiến ​​Daniel A. White nói với bạn rằng nó có thể không dẫn đến một hiệu suất đạt được nhưng trong một hit thực hiện vì sự cần thiết của việc marshaling để unmanaged code, vv

Có nói rằng, bạn nên có thể sử dụng GCHandle.Alloc:

public unsafe bool Equals(T[] x, T[] y) 
{ 
    if (ReferenceEquals(x, y)) return true; 
    if (x == null || y == null) return false; 
    if (x.Length != y.Length) return false; 

    GCHandle handleOfX = default(GCHandle); 
    GCHandle handleOfY = default(GCHandle); 
    handleOfX = GCHandle.Alloc(x, GCHandleType.Pinned); 
    handleOfY = GCHandle.Alloc(y, GCHandleType.Pinned); 
    try 
    { 
     return memcmp(handleOfX.AddrOfPinnedObject(), 
         handleOfY.AddrOfPinnedObject(), 
         x.Length * this.elementSize); 
    } 
    finally 
    { 
     if(handleOfX != default(GCHandle)) handleOfX.Free(); 
     if(handleOfY != default(GCHandle)) handleOfY.Free(); 
    } 
} 
+2

Tôi sẽ để đánh giá phương pháp này và đăng những gì tôi đã tìm thấy. Tôi lo ngại rằng GCHandle có trọng lượng nặng. cố định không sử dụng chúng. (Btw, tôi sẽ chỉ PInvoke cho mảng lớn. Tôi sẽ so sánh các mảng nhỏ trong mã được quản lý không an toàn nên không có chi phí cuộc gọi nào cả). – usr

+0

Ngoài ra hãy kiểm tra điều này: http://techmikael.blogspot.co.uk/2009/01/fast-byte-array-comparison-in-c.html Và tôi cho rằng nó sẽ tăng tốc độ cho các mảng LARGE (mặc dù tôi nghĩ phiên bản sử dụng GC sẽ tăng thêm rất nhiều chi phí so với việc chỉ sửa mảng - nhưng tất nhiên việc sửa lỗi sẽ dẫn đến mã không an toàn –

+0

Hiệu suất GCHandle thực sự khủng khiếp. Tôi không ngờ nó sẽ tệ đến vậy. giải pháp duy nhất cho phép không chuyên về T. – usr

2

Bạn có thể viết một giao diện P/Invoke riêng cho mỗi loại mảng mà bạn muốn so sánh. Tôi biết bạn không thực sự muốn chuyên về T, nhưng chi phí không quá lớn tôi nghĩ.

Đây là một chút hack, bởi vì tôi định nghĩa nhiều phương thức P/Invoke với các chữ ký khác nhau cho cùng một hàm API, nhưng bằng cách làm điều này tôi có thể tận dụng sự hỗ trợ marshalling P/Invoke.

(Lưu ý rằng dấu của giá trị trả về từ memcmp thực sự chỉ có ý nghĩa nếu dữ liệu nguồn thực sự là một mảng byte.Nếu bạn đang cho nó một mảng ints, bạn chỉ nên so sánh giá trị trả về bằng 0 và bỏ qua dấu của nó. Trật tự đó nó ngụ ý là không có ý nghĩa cho ints)

Ví dụ, đoạn mã sau in sau cho tôi (CHÍ xây dựng, không phải debug xây dựng):.

MemCmp with ints took 00:00:08.0768666 
ManagedMemCmp with ints took 00:00:10.3750453 
MemCmp with bytes took 00:00:01.8740001 
ManagedMemCmp with bytes took 00:00:09.2885763 

Lưu ý rằng các byte [] thử nghiệm đang sử dụng byte để so sánh một phần tư số byte so với phép thử int [] sử dụng. Mã được quản lý tạo ra cùng một số lượng so sánh, vì vậy nó tương đối chậm hơn rất nhiều.

Không có sự khác biệt lớn giữa thời gian so sánh các mảng ints, nhưng có sự khác biệt lớn khi so sánh các mảng byte. Điều này gợi ý với tôi rằng có thể có tối ưu hóa được quản lý sử dụng cố định để đưa con trỏ đến ints từ mảng byte để so sánh 4 byte tại một thời điểm (với một số fiddling cho các byte có thể thêm vào cuối mà don ' t phù hợp với một int). Tôi cũng nghĩ rằng bạn có thể viết một phiên bản được quản lý đa luồng (sử dụng "int *" tối ưu hóa để so sánh các mảng byte) sẽ là MUCH FASTER nhanh hơn memcmp không được quản lý(), tất nhiên là không đa luồng (theo như tôi đã đọc). biết).

Dù sao, đây là mã thử nghiệm của tôi. Hãy nhớ, RELEASE xây dựng, không gỡ lỗi!

using System; 
using System.Diagnostics; 
using System.Runtime.InteropServices; 


namespace Demo 
{ 
    public static class Program 
    { 
     private static void Main(string[] args) 
     { 
      int[] a1 = new int[1000000]; 
      int[] a2 = new int[1000000]; 

      for (int i = 0; i < a1.Length-1; ++i) 
      { 
       a1[i] = i; 
       a2[i] = i; 
      } 

      a1[a1.Length-1] = 1; 
      a2[a1.Length-1] = 2; 

      byte[] b1 = new byte[1000000]; 
      byte[] b2 = new byte[1000000]; 

      for (int i = 0; i < b1.Length-1; ++i) 
      { 
       b1[i] = (byte)i; 
       b2[i] = (byte)i; 
      } 

      b1[a1.Length-1] = 1; 
      b2[a1.Length-1] = 2; 

      Stopwatch sw = Stopwatch.StartNew(); 
      testWithMemCmp(a1, a2); 
      sw.Stop(); 
      Console.WriteLine("MemCmp with ints took " + sw.Elapsed); 

      sw.Restart(); 
      testWithManagedMemCmp(a1, a2); 
      sw.Stop(); 
      Console.WriteLine("ManagedMemCmp with ints took " + sw.Elapsed); 

      sw.Restart(); 
      testWithMemCmp(b1, b2); 
      sw.Stop(); 
      Console.WriteLine("MemCmp with bytes took " + sw.Elapsed); 

      sw.Restart(); 
      testWithManagedMemCmp(b1, b2); 
      sw.Stop(); 
      Console.WriteLine("ManagedMemCmp with bytes took " + sw.Elapsed); 
     } 

     private static void testWithMemCmp(int[] a1, int[] a2) 
     { 
      for (int j = 0; j < COUNT; ++j) 
      { 
       MemCmp(a1, a2); 
      } 
     } 

     private static void testWithMemCmp(byte[] a1, byte[] a2) 
     { 
      for (int j = 0; j < COUNT; ++j) 
      { 
       MemCmp(a1, a2); 
      } 
     } 

     private static void testWithManagedMemCmp(int[] a1, int[] a2) 
     { 
      for (int j = 0; j < COUNT; ++j) 
      { 
       ManagedMemCmp(a1, a2); 
      } 
     } 

     private static void testWithManagedMemCmp(byte[] a1, byte[] a2) 
     { 
      for (int j = 0; j < COUNT; ++j) 
      { 
       ManagedMemCmp(a1, a2); 
      } 
     } 

     public static bool ManagedMemCmp(int[] a1, int[] a2) 
     { 
      if (a1 == null || a2 == null || a1.Length != a2.Length) 
      { 
       throw new InvalidOperationException("Arrays are null or different lengths."); 
      } 

      for (int i = 0; i < a1.Length; ++i) 
      { 
       if (a1[i] != a2[i]) 
       { 
        return false; 
       } 
      } 

      return true; 
     } 

     public static bool ManagedMemCmp(byte[] a1, byte[] a2) 
     { 
      if (a1 == null || a2 == null || a1.Length != a2.Length) 
      { 
       throw new InvalidOperationException("Arrays are null or different lengths."); 
      } 

      for (int i = 0; i < a1.Length; ++i) 
      { 
       if (a1[i] != a2[i]) 
       { 
        return false; 
       } 
      } 


      return true; 
     } 

     public static bool MemCmp(byte[] a1, byte[] a2) 
     { 
      if (a1 == null || a2 == null || a1.Length != a2.Length) 
      { 
       throw new InvalidOperationException("Arrays are null or different lengths."); 
      } 

      return memcmp(a1, a2, new UIntPtr((uint)a1.Length)) == 0; 
     } 

     public static bool MemCmp(int[] a1, int[] a2) 
     { 
      if (a1 == null || a2 == null || a1.Length != a2.Length) 
      { 
       throw new InvalidOperationException("Arrays are null or different lengths."); 
      } 

      return memcmp(a1, a2, new UIntPtr((uint)(a1.Length * sizeof(int)))) == 0; 
     } 

     [DllImport("msvcrt.dll")] 
     private static extern int memcmp(byte[] a1, byte[] a2, UIntPtr count); 

     [DllImport("msvcrt.dll")] 
     private static extern int memcmp(int[] a1, int[] a2, UIntPtr count); 

     private const int COUNT = 10000; 
    } 
} 
+0

Tôi đã thử nghiệm phiên bản PInvoke và trong vòng so sánh được quản lý, tôi sử dụng 'ulong *' để so sánh 64 bit cùng một lúc. Những con số này tốt hơn nhiều so với vòng lặp yếu tố an toàn, ngây thơ (không đáng ngạc nhiên như bạn đã nói). – usr

+0

Cảm ơn bạn đã trả lời câu hỏi này. Nó đã giúp triển khai giải pháp mà tôi hiện có: Đối với độ dài <8, sử dụng vòng lặp phần tử đơn an toàn. Đối với độ dài <512, sử dụng vòng lặp 64 bit không an toàn. Trong các trường hợp khác sử dụng memcmp. – usr

Các vấn đề liên quan