2011-02-07 44 views
14

Trong ứng dụng của chúng ta, chúng ta có một mảng byte rất lớn và chúng ta phải chuyển đổi các byte này thành các kiểu khác nhau. Hiện tại, chúng tôi sử dụng BitConverter.ToXXXX() cho mục đích này. Những người chơi nặng nề của chúng tôi là ToInt16ToUInt64.Nhanh chóng truyền trong C# bằng cách sử dụng BitConverter, nó có thể nhanh hơn không?

Đối với UInt64, vấn đề của chúng tôi là luồng dữ liệu thực tế có 6 byte dữ liệu để biểu thị một số nguyên lớn. Vì không có chức năng tự nhiên để chuyển đổi 6-byte dữ liệu để UInt64, chúng tôi làm:

UInt64 value = BitConverter.ToUInt64() & 0x0000ffffffffffff; 

sử dụng của chúng tôi ToInt16 là đơn giản hơn, làm không cần phải làm bất cứ thao tác bit.

Chúng tôi thực hiện rất nhiều trong số 2 hoạt động này mà tôi muốn hỏi cộng đồng SO xem có cách nào nhanh hơn để thực hiện các chuyển đổi này hay không. Ngay bây giờ, khoảng 20% ​​toàn bộ chu kỳ CPU của chúng tôi được tiêu thụ bởi hai chức năng này.

+8

Hiệu suất nguyên bản không phải là vấn đề của bạn. Xử lý các mảng lớn hầu như luôn làm cho bus RAM chậm bị nghẽn. Hãy chú ý đến bộ đếm hiệu suất "Last Level Cache Misses" trong đầu ra profiler của bạn. –

+0

@Hans: Bạn chắc chắn đúng: Chúng tôi là bộ nhớ bị ràng buộc. Nhưng vì điều đó, tôi không biết phải làm gì. Chúng ta có một mảng lớn và chúng ta phải đi qua từng byte để trích xuất dữ liệu. Khi tôi đi tuyến tính trong mảng, trình tìm nạp trước phần cứng có thể khóa vào mẫu truy cập và hơn thế nữa, tôi không biết điều gì khác có thể được thực hiện. --thanks – SomethingBetter

Trả lời

6

Bạn đã nghĩ về việc sử dụng con trỏ bộ nhớ một cách trực tiếp. Tôi không thể xác minh cho hiệu suất của nó nhưng nó là một thủ thuật phổ biến trong C++ \ C ...

 byte[] arr = { 1, 2, 3, 4, 5, 6, 7, 8 ,9,10,11,12,13,14,15,16}; 

     fixed (byte* a2rr = &arr[0]) 
     { 

      UInt64* uint64ptr = (UInt64*) a2rr; 
      Console.WriteLine("The value is {0:X2}", (*uint64ptr & 0x0000FFFFFFFFFFFF)); 
      uint64ptr = (UInt64*) ((byte*) uint64ptr+6); 
      Console.WriteLine("The value is {0:X2}", (*uint64ptr & 0x0000FFFFFFFFFFFF)); 
     } 

Bạn sẽ cần phải làm cho lắp ráp của bạn "không an toàn" trong các thiết lập xây dựng cũng như đánh dấu phương pháp trong bạn sẽ làm điều này không an toàn. Bạn cũng bị ràng buộc với chút ít người theo cách tiếp cận này.

+0

Điều này hóa ra là cách nhanh nhất để làm điều đó, ít nhất là cho đến nay. – SomethingBetter

+1

Hãy cẩn thận với điều này. Nếu bạn muốn đọc một trong những số 6 byte ở cuối mảng, bạn sẽ nhận được một ngoại lệ. Tức là, nếu trong ví dụ trên mảng chỉ dài 12 byte, bạn sẽ nhận được một ngoại lệ khi đọc giá trị thứ hai. –

2

Tại sao không:

UInt16 valLow = BitConverter.ToUInt16(); 
UInt64 valHigh = (UInt64)BitConverter.ToUInt32(); 
UInt64 Value = (valHigh << 16) | valLow; 

Bạn có thể làm điều đó một tuyên bố duy nhất, mặc dù trình biên dịch JIT có thể sẽ làm điều đó cho bạn tự động.

Điều đó sẽ ngăn bạn đọc thêm hai byte mà cuối cùng bạn sẽ vứt bỏ.

Nếu điều đó không làm giảm CPU, thì có thể bạn sẽ muốn viết trình chuyển đổi của riêng bạn đọc byte trực tiếp từ bộ đệm. Bạn có thể sử dụng chỉ mục mảng hoặc, nếu bạn nghĩ rằng nó là cần thiết, mã không an toàn với con trỏ. Lưu ý rằng, với tư cách là người bình luận đã chỉ ra, nếu bạn sử dụng bất kỳ gợi ý nào trong số này, thì bạn bị giới hạn ở "endian-ness" cụ thể, hoặc bạn sẽ phải viết mã của mình để phát hiện ít/lớn endian và phản ứng tương ứng. Mẫu mã tôi đã trình bày ở trên hoạt động cho ít endian (x86).

+2

Bạn nên đề cập rằng điều này làm việc cho một endianness nhất định (tôi nghĩ rằng ít, nhưng tôi luôn luôn trộn lẫn hai). Nó có thể hoặc có thể không quan trọng đối với OP. –

+0

@Martinho: Tốt. Tôi đã cập nhật câu trả lời của mình. –

+0

Tôi đã làm theo đề xuất ban đầu của bạn khi nghĩ rằng đọc thêm 2 byte và ném chúng ra phải làm chậm tôi, nhưng hóa ra, đây là cách làm chậm nhất. Tôi đoán vì dữ liệu đã được lưu vào bộ nhớ cache nên không thực sự quan trọng khi đọc 8 hoặc 6 byte tại một thời điểm. Đề xuất thứ hai của bạn, mã @Jimmy được cung cấp dưới dạng câu trả lời hoạt động nhanh hơn nhiều. - cảm ơn – SomethingBetter

4

Bạn có thể sử dụng lớp System.Buffer để sao chép một mảng toàn bộ giao cho một mảng của một loại khác nhau như một nhanh, 'block copy' hoạt động:

Phương pháp BlockCopy truy cập các byte trong mảng tham số src sử dụng offsets vào bộ nhớ, không phải các cấu trúc lập trình như chỉ mục hoặc giới hạn mảng trên và dưới.

Các loại mảng phải thuộc loại 'nguyên thủy', chúng phải căn chỉnh và thao tác sao chép là nhạy cảm với người cuối. Trong trường hợp các số nguyên 6 byte của bạn, nó không thể căn chỉnh với bất kỳ kiểu 'nguyên thủy' nào của .NET, trừ khi bạn có thể lấy mảng nguồn với hai byte đệm cho mỗi sáu, sau đó sẽ căn chỉnh với Int64. Nhưng phương pháp này sẽ làm việc cho các mảng Int16, có thể tăng tốc một số hoạt động của bạn.

+0

Cảm ơn thông tin System.Buffer.BlockCopy. Trong trường hợp của chúng ta, UInt64 và Int16 được xen kẽ trong mảng, vì vậy BlockCopy sẽ không hoạt động cho chúng ta, nhưng thông tin này là hữu ích, chúng ta có thể sử dụng phương thức này trong tương lai. – SomethingBetter

1

Xem câu trả lời của tôi cho một câu hỏi tương tự here. Đó là thao tác bộ nhớ không an toàn giống như câu trả lời của Jimmy, nhưng theo cách "thân thiện" hơn cho người tiêu dùng. Nó sẽ cho phép bạn xem mảng byte của bạn dưới dạng mảng UInt64.

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