Berikut versi lain untuk kami pengguna Kerangka yang ditinggalkan oleh Microsoft. Ini 4 kali lebih cepat Array.Clear
dan lebih cepat daripada solusi Panos Theof dan paralel Eric J dan Petar Petrov - hingga dua kali lebih cepat untuk array besar.
Pertama, saya ingin menyajikan kepada Anda leluhur fungsi tersebut, karena itu membuatnya lebih mudah untuk memahami kodenya. Kinerja-bijaksana ini cukup setara dengan kode Panos Theof, dan untuk beberapa hal yang mungkin sudah cukup:
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);
}
Seperti yang Anda lihat, ini didasarkan pada penggandaan berulang dari bagian yang sudah diinisialisasi. Ini sederhana dan efisien, tetapi bertabrakan dengan arsitektur memori modern. Oleh karena itu lahir versi yang menggunakan penggandaan hanya untuk membuat blok seed yang ramah cache, yang kemudian diledakkan berulang-ulang di atas area target:
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);
}
Catatan: kode sebelumnya diperlukan (count + 1) >> 1
sebagai batas untuk loop pengganda untuk memastikan bahwa operasi penyalinan akhir memiliki cukup makanan untuk mencakup semua yang tersisa. Ini tidak akan menjadi kasus perhitungan ganjil jika count >> 1
digunakan sebagai gantinya. Untuk versi saat ini, ini tidak penting karena loop salinan linier akan mengambil setiap kelonggaran.
Ukuran sel array harus dilewatkan sebagai parameter karena - boggles pikiran - generik tidak diizinkan untuk digunakan sizeof
kecuali mereka menggunakan constraint ( unmanaged
) yang mungkin atau mungkin tidak tersedia di masa depan. Perkiraan yang salah bukanlah masalah besar tetapi kinerja terbaik jika nilainya akurat, karena alasan berikut:
Meremehkan ukuran elemen dapat menyebabkan ukuran blok lebih besar dari setengah cache L1, sehingga meningkatkan kemungkinan data sumber salinan diusir dari L1 dan harus diambil kembali dari tingkat cache yang lebih lambat.
Menaksir terlalu tinggi ukuran elemen menghasilkan utilisasi yang kurang dari cache L1 CPU, yang berarti loop copy blok linier dieksekusi lebih sering daripada pemanfaatan optimal. Dengan demikian, lebih dari overhead loop / panggilan tetap yang dikeluarkan daripada yang diperlukan.
Berikut adalah patokan mengadu kode saya Array.Clear
dan tiga solusi lainnya yang disebutkan sebelumnya. Pengaturan waktu adalah untuk mengisi bilangan bulat array ( Int32[]
) dari ukuran yang diberikan. Untuk mengurangi variasi yang disebabkan oleh cache vagaries dll. Setiap tes dieksekusi dua kali, kembali ke belakang, dan waktunya diambil untuk eksekusi kedua.
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
Jika kinerja kode ini tidak mencukupi maka jalan yang menjanjikan akan memparalelkan loop copy linier (dengan semua utas menggunakan blok sumber yang sama), atau teman lama kita P / Invoke.
Catatan: membersihkan dan mengisi blok biasanya dilakukan dengan rutinitas runtime yang bercabang ke kode yang sangat khusus menggunakan instruksi MMX / SSE dan yang lainnya, jadi dalam lingkungan yang layak orang hanya akan memanggil setara moral masing-masing std::memset
dan dijamin tingkat kinerja profesional. TKI, berdasarkan haknya fungsi perpustakaan Array.Clear
harus meninggalkan semua versi lintingan tangan kami dalam debu. Fakta bahwa itu sebaliknya menunjukkan betapa jauh dari hal-hal yang sebenarnya. Hal yang sama berlaku karena harus menggulung sendiri Fill<>
di tempat pertama, karena masih hanya dalam Core dan Standard tetapi tidak dalam Framework. .NET telah ada selama hampir dua puluh tahun sekarang dan kita masih harus P / Panggil kiri dan kanan untuk hal-hal yang paling mendasar atau menggulung ...