Pengantar singkat
System.Collections.Generic.List <T>adalah kelas koleksi generik di .NET, yang dapat menyimpan semua jenis data karena kenyamanan dan API-nya yang kaya, yang banyak digunakan dalam kehidupan kita sehari-hari, dan dapat dikatakan sebagai kelas koleksi yang paling banyak digunakan.
Dalam penulisan kode, kita sering perlu mengulangi koleksi Daftar <T>untuk mendapatkan elemen di dalamnya untuk beberapa pemrosesan bisnis. Biasanya, tidak banyak elemen dalam satu set dan sangat cepat untuk dilintasi. Tetapi untuk beberapa pemrosesan data besar, statistik, komputasi waktu nyata, dll.<T>, Bagaimana cara dengan cepat melintasi kumpulan daftar puluhan ribu atau ratusan ribu data? Itulah yang perlu saya bagikan kepada Anda hari ini.
Mode traversal
Mari kita lihat performa metode traversal yang berbeda, dan bangun tolok ukur performa berikut, menggunakan traversal koleksi urutan besarnya yang berbeda untuk melihat performa metode yang berbeda. Cuplikan kode terlihat seperti ini:
Gunakan pernyataan foreach
foreach adalah cara paling umum kita melintasi koleksi, ini adalah implementasi gula sintaks dari pola iterator, dan juga digunakan sebagai tolok ukur untuk kali ini.
Karena pernyataan foreach adalah gula sintaks, kompiler akhirnya memanggil GetEnumerator() dan MoveNext() dengan perulangan while untuk mengimplementasikan fungsionalitas. Kode yang dikompilasi terlihat seperti ini:
Implementasi metode MoveNext() akan memastikan bahwa tidak akan ada utas lain yang memodifikasi koleksi dalam iterasi, dan jika modifikasi terjadi, itu akan melemparkan pengecualian InvalidOperationException, dan akan memiliki pemeriksaan overflow untuk memeriksa apakah indeks saat ini sah, dan juga perlu menetapkan elemen yang sesuai ke enumerator. Atribut saat ini,Jadi nyatanya, kinerjanya bukan yang terbaik, cuplikan kode terlihat seperti ini:
Mari kita lihat bagaimana kinerjanya di berbagai ukuran set, dan hasilnya terlihat seperti ini:
Dapat dilihat bahwa dalam kasus ukuran yang berbeda, hubungan pertumbuhan linier dari proses yang memakan waktu diperlukan, bahkan jika melintasi data 100w tanpa logika pemrosesan apa pun, dibutuhkan setidaknya 1 detik.
Gunakan metode Daftar ForEach
Cara umum lainnya adalah dengan menggunakan List<T>. ForEach(), yang memungkinkan Anda meneruskan <T>delegasi Tindakan, yang akan memanggil delegasi Tindakan saat menelemen-iterasi elemen<T>.
Ini adalah <T>metode implementasi internal List, sehingga dapat langsung mengakses array pribadi dan menghindari pemeriksaan overflow. Secara teori, itu harus cepat; Tetapi dalam skenario kami hanya ada satu metode kosong, yang mungkin tidak berperilaku baik dengan panggilan sebaris sepenuhnya ke metode foreach. Di bawah ini adalah kode sumber metode ForEach, yang menunjukkan bahwa metode tersebut tidak memiliki pemeriksaan overflow, tetapi masih mempertahankan pemeriksaan nomor versi bersamaan.
Selain itu, karena perlu untuk meneruskan delegasi ke metode ForEach, dalam kode panggilan, itu akan memeriksa apakah objek delegasi di kelas pembuatan penutupan kosong setiap saat, dan jika tidak, Action<T>() baru, seperti yang ditunjukkan di bawah ini:
Mari kita lihat perbandingannya dengan kata kunci foreach dalam hal kinerja. Gambar berikut menunjukkan hasil tolok ukur:
Dilihat dari hasil pengujian, 40% lebih lambat daripada menggunakan kata kunci foreach secara langsung, tampaknya jika tidak perlu, itu adalah pilihan yang lebih baik untuk digunakan foreach secara langsung, jadi apakah ada cara yang lebih cepat?
untuk traversal loop
Kembali ke cara tertua kami, yaitu menggunakan kata kunci for untuk melintasi koleksi. Ini harus menjadi metode traversal berkinerja terbaik saat ini, karena tidak memerlukan beberapa kode redundan seperti yang sebelumnya (meskipun pengindeks juga diperiksa untuk mencegah luapan air), dan jelas tidak memeriksa nomor versi, jadi dalam lingkungan multithreaded koleksi diubah, dan tidak akan ada pengecualian yang dilemparkan saat menggunakan for. Kode pengujian terlihat seperti ini:
Mari kita lihat bagaimana hasilnya.
Sepertinya ini adalah cara yang kita harapkan.Menggunakan loop for secara langsung 60% lebih cepat daripada foreach, satu set yang dulu membutuhkan waktu 1 detik untuk melintasi, sekarang hanya membutuhkan waktu 400 milidetik. Jadi apakah ada cara yang lebih cepat?
Gunakan KoleksiMarshal
Setelah .NET5, komunitas dotnet mengimplementasikan kelas CollectionsMarshal untuk meningkatkan performa operasi pengumpulan. Kelas ini mengimplementasikan cara mengakses array native dari jenis koleksi (jika Anda telah melihat [. .NET Performance Optimization - You Should Set the Initial Size for Collection Types], Anda tahu bahwa implementasi yang mendasari banyak struktur data adalah array). Jadi dapat melewati semua jenis deteksi dan langsung mengakses array asli, yang seharusnya menjadi yang tercepat. Kodenya terlihat seperti ini:
Anda dapat melihat bahwa kode yang dihasilkan oleh kompiler sangat efisien.
Akses langsung ke array yang mendasarinya sangat berbahaya, Anda harus tahu apa yang Anda lakukan dengan setiap baris kode, dan memiliki pengujian yang cukup. Hasil benchmark adalah sebagai berikut:
WowMenggunakan CollectionsMarshal 79% lebih cepat daripada menggunakan foreach, tetapi itu harus menjadi alasan untuk pengoptimalan JIT, tidak ada perbedaan besar antara menggunakan foreach dan for keyword loop Span.
ringkasan
Hari ini saya berbicara dengan Anda tentang cara cepat melintasi koleksi List, dan dalam banyak kasus disarankan untuk menggunakan kata kunci foreach, yang memiliki pemeriksaan overflow dan kontrol nomor versi multi-threaded, yang dapat memudahkan kita untuk menulis kode yang benar.
Jika Anda memerlukan performa tinggi dan volume data besar, disarankan untuk menggunakan dan CollectionsMarshal.AsSpan secara langsung untuk melintasi koleksi.
Tautan kode sumber artikel ini:
Login hyperlink terlihat.
Tautan asli:Login hyperlink terlihat.
|