Đây là một phiên bản khác cho chúng tôi Người dùng khung bị Microsoft bỏ rơi. Nó nhanh gấp 4 lần Array.Clear
và nhanh hơn giải pháp của Panos Theof và song song của Eric J và Petar Petrov - nhanh gấp hai lần đối với các mảng lớn.
Đầu tiên tôi muốn trình bày cho bạn tổ tiên của hàm, bởi vì điều đó giúp dễ hiểu mã hơn. Hiệu suất-khôn ngoan này tương đương với mã của Panos Theof và đối với một số thứ có thể là đủ:
public static void Fill<T> (T[] array, int count, T value, int threshold = 32)
{
if (threshold <= 0)
throw new ArgumentException("threshold");
int current_size = 0, keep_looping_up_to = Math.Min(count, threshold);
while (current_size < keep_looping_up_to)
array[current_size++] = value;
for (int at_least_half = (count + 1) >> 1; current_size < at_least_half; current_size <<= 1)
Array.Copy(array, 0, array, current_size, current_size);
Array.Copy(array, 0, array, current_size, count - current_size);
}
Như bạn có thể thấy, điều này dựa trên việc nhân đôi nhiều lần phần đã được khởi tạo. Điều này là đơn giản và hiệu quả, nhưng nó chạy trên kiến trúc bộ nhớ hiện đại. Do đó, sinh ra một phiên bản chỉ sử dụng nhân đôi để tạo khối hạt giống thân thiện với bộ đệm, sau đó được thổi lặp đi lặp lại trên khu vực mục tiêu:
const int ARRAY_COPY_THRESHOLD = 32; // 16 ... 64 work equally well for all tested constellations
const int L1_CACHE_SIZE = 1 << 15;
public static void Fill<T> (T[] array, int count, T value, int element_size)
{
int current_size = 0, keep_looping_up_to = Math.Min(count, ARRAY_COPY_THRESHOLD);
while (current_size < keep_looping_up_to)
array[current_size++] = value;
int block_size = L1_CACHE_SIZE / element_size / 2;
int keep_doubling_up_to = Math.Min(block_size, count >> 1);
for ( ; current_size < keep_doubling_up_to; current_size <<= 1)
Array.Copy(array, 0, array, current_size, current_size);
for (int enough = count - block_size; current_size < enough; current_size += block_size)
Array.Copy(array, 0, array, current_size, block_size);
Array.Copy(array, 0, array, current_size, count - current_size);
}
Lưu ý: mã trước đó cần (count + 1) >> 1
làm giới hạn cho vòng lặp nhân đôi để đảm bảo rằng thao tác sao chép cuối cùng có đủ thức ăn để bao gồm tất cả những gì còn lại. Điều này sẽ không xảy ra đối với số lượng lẻ nếu count >> 1
được sử dụng thay thế. Đối với phiên bản hiện tại, điều này không có ý nghĩa gì vì vòng lặp sao chép tuyến tính sẽ thu được bất kỳ sự chậm chạp nào.
Kích thước của một ô mảng phải được truyền dưới dạng tham số vì - kính bảo vệ tâm trí - thuốc generic không được phép sử dụng sizeof
trừ khi chúng sử dụng một ràng buộc ( unmanaged
) có thể có hoặc không có sẵn trong tương lai. Ước tính sai không phải là một vấn đề lớn nhưng hiệu suất là tốt nhất nếu giá trị là chính xác, vì những lý do sau:
Việc đánh giá thấp kích thước phần tử có thể dẫn đến kích thước khối lớn hơn một nửa bộ đệm L1, do đó làm tăng khả năng dữ liệu nguồn sao chép bị xóa khỏi L1 và phải được tìm nạp lại từ các mức bộ đệm chậm hơn.
Đánh giá quá cao kích thước phần tử dẫn đến việc sử dụng không đúng bộ đệm L1 của CPU, có nghĩa là vòng lặp sao chép khối tuyến tính được thực thi thường xuyên hơn so với việc sử dụng tối ưu. Do đó, nhiều chi phí vòng lặp / cuộc gọi cố định phát sinh hơn mức cần thiết.
Đây là một điểm chuẩn đưa mã của tôi chống lại Array.Clear
và ba giải pháp khác đã đề cập trước đó. Thời gian là để điền vào các mảng số nguyên ( Int32[]
) của các kích thước đã cho. Để giảm sự thay đổi gây ra bởi các bộ nhớ cache, v.v., mỗi bài kiểm tra được thực hiện hai lần, quay lại và thời gian được thực hiện cho lần thực hiện thứ hai.
array size Array.Clear Eric J. Panos Theof Petar Petrov Darth Gizka
-------------------------------------------------------------------------------
1000: 0,7 µs 0,2 µs 0,2 µs 6,8 µs 0,2 µs
10000: 8,0 µs 1,4 µs 1,2 µs 7,8 µs 0,9 µs
100000: 72,4 µs 12,4 µs 8,2 µs 33,6 µs 7,5 µs
1000000: 652,9 µs 135,8 µs 101,6 µs 197,7 µs 71,6 µs
10000000: 7182,6 µs 4174,9 µs 5193,3 µs 3691,5 µs 1658,1 µs
100000000: 67142,3 µs 44853,3 µs 51372,5 µs 35195,5 µs 16585,1 µs
Nếu hiệu suất của mã này không đủ thì một con đường đầy hứa hẹn sẽ song song với vòng lặp sao chép tuyến tính (với tất cả các luồng sử dụng cùng một khối nguồn) hoặc P / Invoke, người bạn cũ tốt của chúng tôi.
Lưu ý: việc xóa và điền khối thường được thực hiện bằng các thói quen thời gian chạy phân nhánh thành mã chuyên dụng cao bằng cách sử dụng các hướng dẫn MMX / SSE và vì vậy, trong bất kỳ môi trường tốt nào, người ta chỉ cần gọi tương đương đạo đức tương ứng std::memset
và được đảm bảo về mức hiệu suất chuyên nghiệp. IOW, theo quyền, chức năng thư viện Array.Clear
sẽ để lại tất cả các phiên bản cuộn tay của chúng tôi trong bụi. Thực tế là cách khác xung quanh cho thấy những thứ thực sự xa vời đến mức nào. Điều tương tự cũng xảy ra khi bạn phải tự quay vòng Fill<>
ở vị trí đầu tiên, bởi vì nó vẫn chỉ ở trong Core và Standard chứ không phải trong Framework. .NET đã tồn tại được gần hai mươi năm nay và chúng tôi vẫn phải P / Gọi trái và phải cho những thứ cơ bản nhất hoặc tự cuộn ...