Apakah ada dampak kinerja saat memanggil ToList ()?

139

Saat menggunakan ToList(), apakah ada dampak kinerja yang perlu dipertimbangkan?

Saya sedang menulis kueri untuk mengambil file dari direktori, yang merupakan kueri:

string[] imageArray = Directory.GetFiles(directory);

Namun, karena saya lebih suka bekerja dengan List<>, saya memutuskan untuk memasukkan ...

List<string> imageList = Directory.GetFiles(directory).ToList();

Jadi, apakah ada semacam dampak kinerja yang harus dipertimbangkan ketika memutuskan untuk melakukan konversi seperti ini - atau hanya dipertimbangkan ketika berhadapan dengan sejumlah besar file? Apakah ini konversi yang dapat diabaikan?

Cody
sumber
+1 tertarik untuk mengetahui jawabannya di sini juga. IMHO kecuali aplikasinya kritis kinerja, saya pikir saya akan selalu menggunakan List<T>mendukung T[]jika itu membuat kode lebih logis / dapat dibaca / dipelihara (kecuali tentu saja konversi itu menyebabkan masalah kinerja yang nyata dalam hal ini saya akan kembali kunjungi saya kira).
Sepster
Membuat daftar dari array harus sangat murah.
leppie
2
@Sepster Saya hanya menentukan tipe data secara spesifik karena saya perlu melakukan pekerjaan. Jika saya tidak perlu menelepon Addatau Remove, saya akan membiarkannya IEnumerable<T>(atau bahkan lebih baik var)
pswg
4
Saya pikir, dalam hal ini lebih baik memanggil EnumerateFilesdaripada GetFiles, jadi hanya satu array yang akan dibuat.
tukaef
3
GetFiles(directory), seperti yang diterapkan di .NET saat ini, cukup banyak new List<string>(EnumerateFiles(directory)).ToArray(). Jadi GetFiles(directory).ToList()buat daftar, buat array dari itu, lalu buat daftar lagi. Seperti yang dikatakan 2kay, Anda harus memilih untuk melakukannya di EnumerateFiles(directory).ToList()sini.
Joren

Jawaban:

178

IEnumerable.ToList()

Ya, IEnumerable<T>.ToList()memang memiliki dampak kinerja, ini adalah operasi O (n) meskipun kemungkinan hanya membutuhkan perhatian dalam operasi kritis kinerja.

The ToList()operasi akan menggunakan List(IEnumerable<T> collection)konstruktor. Konstruktor ini harus membuat salinan array (lebih umum IEnumerable<T>), jika tidak modifikasi di masa depan dari array asli akan berubah pada sumber T[]juga yang tidak diinginkan secara umum.

Saya ingin mengulangi ini hanya akan membuat perbedaan dengan daftar besar, menyalin potongan memori adalah operasi yang cukup cepat untuk dilakukan.

Tip praktis, AsvsTo

Anda akan melihat di LINQ ada beberapa metode yang dimulai dengan As(seperti AsEnumerable()) dan To(seperti ToList()). Metode yang dimulai dengan Tomemerlukan konversi seperti di atas (mis. Dapat mempengaruhi kinerja), dan metode yang memulai dengan Astidak dan hanya akan memerlukan beberapa pemain atau operasi sederhana.

Rincian tambahan tentang List<T>

Berikut ini sedikit lebih detail tentang bagaimana cara List<T>kerjanya jika Anda tertarik :)

A List<T>juga menggunakan konstruk yang disebut array dinamis yang perlu diubah ukurannya sesuai permintaan, peristiwa ini mengubah ukuran isi dari array lama ke array baru. Jadi itu dimulai dari kecil dan bertambah besar jika diperlukan .

Ini adalah perbedaan antara atribut Capacitydan . mengacu pada ukuran array di belakang layar, adalah jumlah item yang selalu ada . Jadi, ketika suatu item ditambahkan ke daftar, meningkatkannya melewati , ukuran dua kali lipat dan array disalin.CountList<T>CapacityCountList<T><= CapacityCapacityList<T>

Daniel Imms
sumber
2
Saya hanya ingin menekankan bahwa List(IEnumerable<T> collection)konstruktor memeriksa apakah parameter pengumpulan ICollection<T>dan kemudian membuat array internal baru dengan ukuran yang diperlukan segera. Jika pengumpulan parameter tidak ICollection<T>, konstruktor mengulanginya dan memanggil Addsetiap elemen.
Justinas Simanavicius
Penting untuk dicatat bahwa Anda mungkin sering melihat ToList () sebagai operasi yang menyesatkan. Ini terjadi ketika Anda membuat IEnumerable <> melalui kueri LINQ. kueri linq dibuat tetapi tidak dieksekusi. memanggil ToList () akan menjalankan kueri dan karenanya tampak sumber daya intensif - tetapi itu adalah kueri yang intensif dan bukan operasi ToList () (Kecuali jika itu adalah daftar yang sangat besar)
dancer42
36

Apakah ada dampak kinerja saat memanggil toList ()?

Ya tentu saja. Secara teoritis bahkan i++memiliki dampak kinerja, memperlambat program untuk beberapa detik.

Apa yang .ToListharus dilakukan

Ketika Anda memohon .ToList, kode panggilan Enumerable.ToList()yang merupakan metode ekstensi itu return new List<TSource>(source). Dalam konstruktor yang sesuai, di bawah keadaan terburuk, ia melewati wadah item dan menambahkannya satu per satu ke dalam wadah baru. Jadi perilakunya sedikit mempengaruhi kinerja. Tidak mungkin menjadi leher botol kinerja aplikasi Anda.

Apa yang salah dengan kode dalam pertanyaan

Directory.GetFilesmelewati folder dan mengembalikan semua nama file dengan segera ke dalam memori, ia memiliki risiko potensial bahwa string [] menghabiskan banyak memori, memperlambat segalanya.

Apa yang harus dilakukan?

Tergantung. Jika Anda (dan juga logika bisnis Anda) menjamin bahwa jumlah file di folder selalu kecil, kode dapat diterima. Tetapi masih disarankan untuk menggunakan versi malas: Directory.EnumerateFilesdi C # 4. Ini lebih seperti kueri, yang tidak akan segera dieksekusi, Anda dapat menambahkan lebih banyak kueri seperti:

Directory.EnumerateFiles(myPath).Any(s => s.Contains("myfile"))

yang akan berhenti mencari jalan segera setelah file yang namanya mengandung "myfile" ditemukan. Ini jelas memiliki kinerja yang lebih baik .GetFiles.

Cheng Chen
sumber
19

Apakah ada dampak kinerja saat memanggil toList ()?

Ya ada. Menggunakan metode ekstensi Enumerable.ToList()akan membangun List<T>objek baru dari IEnumerable<T>kumpulan sumber yang tentu saja memiliki dampak kinerja.

Namun, pemahaman List<T>dapat membantu Anda menentukan apakah dampak kinerja itu signifikan.

List<T>menggunakan array ( T[]) untuk menyimpan elemen daftar. Array tidak dapat diperpanjang setelah dialokasikan sehingga List<T>akan menggunakan array berukuran lebih besar untuk menyimpan elemen daftar. Ketika List<T>tumbuh melebihi ukuran array yang mendasarinya array baru harus dialokasikan dan isi array lama harus disalin ke array yang lebih besar baru sebelum daftar dapat tumbuh.

Ketika yang baru List<T>dibangun dari IEnumerable<T>ada dua kasus:

  1. Implementasi pengumpulan sumber ICollection<T>: Kemudian ICollection<T>.Countdigunakan untuk mendapatkan ukuran yang tepat dari koleksi sumber dan array backing yang cocok dialokasikan sebelum semua elemen dari koleksi sumber disalin ke array backing menggunakan ICollection<T>.CopyTo(). Operasi ini cukup efisien dan mungkin akan dipetakan ke beberapa instruksi CPU untuk menyalin blok memori. Namun, dalam hal kinerja, memori diperlukan untuk larik baru dan siklus CPU diperlukan untuk menyalin semua elemen.

  2. Jika tidak, ukuran kumpulan sumber tidak diketahui dan enumerator IEnumerable<T>digunakan untuk menambahkan setiap elemen sumber satu per satu ke yang baru List<T>. Awalnya array dukungan kosong dan array ukuran 4 dibuat. Kemudian ketika array ini terlalu kecil ukurannya menjadi dua kali lipat sehingga array backing tumbuh seperti ini 4, 8, 16, 32 dll. Setiap kali array backing tumbuh itu harus realokasi dan semua elemen yang disimpan sejauh ini harus disalin. Operasi ini jauh lebih mahal dibandingkan dengan kasus pertama di mana array dengan ukuran yang benar dapat dibuat segera.

    Juga, jika koleksi sumber Anda mengandung katakanlah 33 elemen daftar akan berakhir dengan menggunakan array 64 elemen yang membuang-buang memori.

Dalam kasus Anda, pengumpulan sumber adalah array yang mengimplementasikan ICollection<T>sehingga dampak kinerja bukanlah sesuatu yang harus Anda perhatikan kecuali jika array sumber Anda sangat besar. Memanggil ToList()hanya akan menyalin array sumber dan membungkusnya dalam suatu List<T>objek. Bahkan kinerja case kedua bukanlah sesuatu yang perlu dikhawatirkan untuk koleksi kecil.

Martin Liversage
sumber
5

"Apakah ada dampak kinerja yang perlu dipertimbangkan?"

Masalah dengan skenario Anda yang tepat adalah bahwa yang pertama dan terpenting perhatian nyata Anda tentang kinerja akan berasal dari kecepatan hard drive dan efisiensi cache drive.

Dari perspektif itu, dampaknya jelas dapat diabaikan sampai-sampai TIDAK itu tidak perlu dipertimbangkan.

TETAPI SAJA jika Anda benar-benar membutuhkan fitur List<>struktur untuk membuat Anda lebih produktif, atau algoritme Anda lebih ramah, atau beberapa keuntungan lainnya. Jika tidak, Anda hanya dengan sengaja menambahkan hit kinerja yang tidak signifikan, tanpa alasan sama sekali. Dalam hal ini, tentu saja, Anda tidak boleh melakukannya! :)

jross
sumber
4

ToList()membuat Daftar baru dan memasukkan unsur-unsur di dalamnya yang berarti ada biaya terkait dengan melakukan ToList(). Dalam kasus pengumpulan kecil itu tidak akan menjadi biaya yang sangat mencolok tetapi memiliki koleksi besar dapat menyebabkan kinerja yang hit jika menggunakan ToList.

Secara umum Anda tidak boleh menggunakan ToList () kecuali jika pekerjaan yang Anda lakukan tidak dapat dilakukan tanpa mengubah koleksi menjadi Daftar. Misalnya jika Anda hanya ingin mengulang melalui koleksi, Anda tidak perlu melakukan ToList

Jika Anda melakukan kueri terhadap sumber data misalnya Basis data menggunakan LINQ ke SQL maka biaya melakukan ToList jauh lebih karena ketika Anda menggunakan ToList dengan LINQ ke SQL alih-alih melakukan Eksekusi Tertunda yaitu memuat item saat diperlukan (yang dapat bermanfaat dalam banyak skenario) secara instan memuat item dari Database ke memori

Haris Hasan
sumber
Haris: apa yang saya tidak yakin tentang sumber asli apa yang akan terjadi pada sumber asli setelah memanggil ToList ()
TalentTuner
@Saurabh GC akan membersihkannya
pswg
@ Sabaurabh tidak akan terjadi apa-apa dengan sumber aslinya. Elemen-elemen sumber asli akan dirujuk oleh daftar yang baru dibuat
Haris Hasan
"Jika Anda hanya ingin mengulang melalui koleksi, Anda tidak perlu melakukan ToList" - jadi bagaimana Anda harus mengulanginya?
SharpC
4

Ini akan seefisien melakukan:

var list = new List<T>(items);

Jika Anda membongkar kode sumber konstruktor yang mengambil IEnumerable<T>, Anda akan melihatnya akan melakukan beberapa hal:

  • Panggil collection.Count, jadi jika collectionada IEnumerable<T>, itu akan memaksa eksekusi. Jika collectionarray, daftar, dll itu harus O(1).

  • Jika collectionmengimplementasikan ICollection<T>, itu akan menyimpan item dalam array internal menggunakan ICollection<T>.CopyTometode ini. Ini harus menjadi O(n), menjadi npanjang koleksi.

  • Jika collectiontidak menerapkan ICollection<T>, itu akan beralih melalui item koleksi, dan akan menambahkannya ke daftar internal.

Jadi, ya, itu akan menghabiskan lebih banyak memori, karena harus membuat daftar baru, dan dalam kasus terburuk, itu akanO(n) , karena akan beralih melalui collectionuntuk membuat salinan setiap elemen.

Oscar Mederos
sumber
3
close, di 0(n)mana njumlah total byte yang disimpan string dalam koleksi asli, bukan jumlah elemen (lebih tepatnya n = bytes / ukuran kata)
user1416420
@ user1416420 Saya mungkin salah, tapi mengapa begitu? Bagaimana jika itu adalah kumpulan dari beberapa jenis lain (misalnya. bool, int, Dll)? Anda tidak benar-benar harus membuat salinan setiap string dalam koleksi. Anda cukup menambahkannya ke daftar baru.
Oscar Mederos
masih tidak masalah alokasi memori baru & menyalin byte adalah apa yang membunuh metode ini. Bool juga akan menempati 4 byte di .NET. Sebenarnya setiap referensi dari suatu objek di .NET setidaknya memiliki panjang 8 byte, jadi ini cukup lambat. 4 byte pertama menunjuk ke tabel jenis & 4 byte kedua menunjuk ke nilai atau lokasi memori di mana menemukan nilai
user1416420
3

Mempertimbangkan kinerja mengambil daftar file, ToList()dapat diabaikan. Tetapi tidak benar-benar untuk skenario lain. Itu benar-benar tergantung di mana Anda menggunakannya.

  • Saat memanggil array, daftar, atau koleksi lain, Anda membuat salinan koleksi sebagai List<T>. Kinerja di sini tergantung pada ukuran daftar. Anda harus melakukannya ketika benar-benar diperlukan.

    Dalam contoh Anda, Anda menyebutnya pada array. Itu beralih di atas array dan menambahkan item satu per satu ke daftar yang baru dibuat. Jadi dampak kinerja tergantung pada jumlah file.

  • Saat memanggil pada IEnumerable<T>, Anda terwujud dalam IEnumerable<T>(biasanya query).

Mohammad Dehghan
sumber
2

ToList Akan membuat daftar baru dan menyalin elemen dari sumber asli ke daftar yang baru dibuat sehingga hanya menyalin elemen dari sumber asli dan tergantung pada ukuran sumber

TalentTuner
sumber