Saya sering mengalami kasus di mana saya ingin membuat kueri tepat di tempat saya menyatakannya. Ini biasanya karena saya perlu mengulanginya beberapa kali dan itu mahal untuk dihitung. Sebagai contoh:
string raw = "...";
var lines = (from l in raw.Split('\n')
let ll = l.Trim()
where !string.IsNullOrEmpty(ll)
select ll).ToList();
Ini berfungsi dengan baik. Tetapi jika saya tidak akan mengubah hasilnya, maka saya mungkin akan menelepon ToArray()
saja ToList()
.
Namun saya bertanya-tanya apakah ToArray()
diimplementasikan oleh panggilan pertama ToList()
dan karena itu memori kurang efisien daripada hanya menelepon ToList()
.
Apakah saya gila? Haruskah saya menelepon ToArray()
- aman dan aman karena mengetahui bahwa memori tidak akan dialokasikan dua kali?
.net
linq
performance
Frank Krueger
sumber
sumber
Jawaban:
Kecuali Anda hanya membutuhkan array untuk memenuhi kendala lain yang harus Anda gunakan
ToList
. Pada sebagian besar skenarioToArray
akan mengalokasikan lebih banyak memori daripadaToList
.Keduanya menggunakan array untuk penyimpanan, tetapi
ToList
memiliki kendala yang lebih fleksibel. Perlu array setidaknya sebesar jumlah elemen dalam koleksi. Jika array lebih besar, itu tidak masalah. NamunToArray
perlu ukuran array yang tepat dengan jumlah elemen.Untuk memenuhi kendala ini
ToArray
sering melakukan satu alokasi lebih banyak daripadaToList
. Setelah memiliki array yang cukup besar itu mengalokasikan array yang ukurannya tepat dan menyalin elemen kembali ke array itu. Satu-satunya waktu yang dapat dihindarkan adalah ketika algoritma tumbuh untuk array kebetulan bertepatan dengan jumlah elemen yang perlu disimpan (pasti dalam minoritas).EDIT
Beberapa orang bertanya kepada saya tentang konsekuensi memiliki memori ekstra yang tidak digunakan dalam
List<T>
nilai.Ini adalah kekhawatiran yang valid. Jika koleksi yang dibuat berumur panjang, tidak pernah dimodifikasi setelah dibuat dan memiliki peluang besar untuk mendarat di tumpukan Gen2 maka Anda mungkin lebih baik mengambil alokasi ekstra
ToArray
di muka.Secara umum, meskipun saya menemukan ini menjadi kasus yang lebih jarang. Jauh lebih umum untuk melihat banyak
ToArray
panggilan yang segera diteruskan ke penggunaan memori jangka pendek lainnya dalam halToList
ini terbukti lebih baik.Kuncinya di sini adalah profil, profil dan kemudian profil lagi.
sumber
ToArray
bisa mengalokasikan lebih banyak memori jika perlu ukuran lokasi yang tepat di manaToList<>
jelas memiliki lokasi cadangan otomatis itu. (peningkatan otomatis)Perbedaan kinerja tidak akan signifikan, karena
List<T>
diimplementasikan sebagai array berukuran dinamis. Memanggil salah satuToArray()
(yang menggunakanBuffer<T>
kelas internal untuk menumbuhkan array) atauToList()
(yang memanggilList<T>(IEnumerable<T>)
konstruktor) akhirnya akan menjadi masalah menempatkan mereka ke dalam array dan menumbuhkan array sampai cocok dengan mereka semua.Jika Anda menginginkan konfirmasi konkret atas fakta ini, periksa penerapan metode yang dimaksud dalam Reflektor - Anda akan melihatnya menerapkannya ke kode yang hampir sama.
sumber
ToArray()
danToList()
adalah bahwa yang pertama harus memangkas kelebihan, yang melibatkan menyalin seluruh array, sedangkan yang terakhir tidak memangkas kelebihan, tetapi menggunakan rata-rata 25 % lebih banyak memori. Ini hanya akan memiliki implikasi jika tipe datanya besarstruct
. Hanya makanan untuk dipikirkan.ToList
atauToArray
akan mulai dengan membuat buffer kecil. Ketika buffer itu terisi, itu menggandakan kapasitas buffer dan berlanjut. Karena kapasitas selalu berlipat ganda, buffer yang tidak digunakan akan selalu berada di antara 0% dan 50%.List
danBuffer
akan memeriksaICollection
, dalam hal ini kinerja akan identik.(tujuh tahun kemudian ...)
Beberapa jawaban (baik) lainnya telah berkonsentrasi pada perbedaan kinerja mikroskopis yang akan terjadi.
Posting ini hanyalah suplemen untuk menyebutkan perbedaan semantik yang ada antara yang
IEnumerator<T>
diproduksi oleh array (T[]
) dibandingkan dengan yang dikembalikan oleh aList<T>
.Terbaik diilustrasikan dengan contoh:
Kode di atas akan berjalan tanpa kecuali dan menghasilkan output:
Ini menunjukkan bahwa yang
IEnumarator<int>
dikembalikan oleh suatuint[]
tidak melacak apakah array telah dimodifikasi sejak pembuatan enumerator.Perhatikan bahwa saya mendeklarasikan variabel lokal
source
sebagaiIList<int>
. Dengan cara itu saya memastikan compiler C # tidak mengoptimalkanforeach
pernyataan menjadi sesuatu yang setara denganfor (var idx = 0; idx < source.Length; idx++) { /* ... */ }
loop. Ini adalah sesuatu yang mungkin dilakukan oleh kompiler C jika saya menggunakannyavar source = ...;
. Dalam versi saya saat ini. NET framework enumerator yang sebenarnya digunakan di sini adalah tipe referensi non-publikSystem.SZArrayHelper+SZGenericArrayEnumerator`1[System.Int32]
tapi tentu saja ini adalah detail implementasi.Sekarang, jika saya mengubah
.ToArray()
menjadi.ToList()
, saya hanya mendapatkan:diikuti dengan
System.InvalidOperationException
semburan yang mengatakan:Pencacah yang mendasari dalam kasus ini adalah tipe nilai publik yang dapat berubah
System.Collections.Generic.List`1+Enumerator[System.Int32]
(kotak di dalamIEnumerator<int>
kotak dalam kasus ini karena saya gunakanIList<int>
).Kesimpulannya, enumerator yang diproduksi oleh
List<T>
melacak apakah daftar berubah selama enumerasi, sedangkan enumerator yang dihasilkan olehT[]
tidak. Jadi pertimbangkan perbedaan ini ketika memilih antara.ToList()
dan.ToArray()
.Orang sering menambahkan satu ekstra
.ToArray()
atau.ToList()
mengelak dari koleksi yang melacak apakah itu dimodifikasi selama masa pencacah.(Jika ada yang ingin tahu bagaimana yang
List<>
melacak apakah koleksi dimodifikasi, ada bidang swasta_version
di kelas ini yang berubah setiap kali yangList<>
diperbarui.)sumber
Saya setuju dengan @mquander bahwa perbedaan kinerja tidak signifikan. Namun, saya ingin membuat tolok ukur untuk memastikan, jadi saya lakukan - dan itu, tidak signifikan.
Setiap larik / Daftar sumber memiliki 1000 elemen. Jadi, Anda dapat melihat bahwa perbedaan waktu dan memori dapat diabaikan.
Kesimpulan saya: Anda mungkin juga menggunakan ToList () , karena a
List<T>
menyediakan lebih banyak fungsionalitas daripada sebuah array, kecuali beberapa byte memori benar-benar penting bagi Anda.sumber
struct
bukan tipe atau kelas primitif.ToList
atauToArray
panggilan dan bukan penghitunganIEnumerable
. Daftar <T> .ToList () masih membuat Daftar <T> baru - tidak hanya "mengembalikan ini".ToArray()
danToList()
terlalu banyak perbedaan ketika diberikanICollection<T>
parameter - Mereka hanya melakukan alokasi tunggal dan operasi salinan tunggal. KeduanyaList<T>
danArray
implementasikanICollection<T>
, sehingga tolok ukur Anda tidak valid sama sekali..Select(i => i)
untuk menghindariICollection<T>
masalah implementasi, dan termasuk kelompok kontrol untuk melihat berapa banyak waktu yang diambil iterasi dari sumberIEnumerable<>
di tempat pertama.ToList()
biasanya lebih disukai jika Anda menggunakannya diIEnumerable<T>
(dari ORM, misalnya). Jika panjang urutan tidak diketahui di awal,ToArray()
buat koleksi panjang dinamis seperti Daftar dan kemudian mengubahnya menjadi array, yang membutuhkan waktu ekstra.sumber
Enumerable.ToArray()
meneleponnew Buffer<TSource>(source).ToArray()
. Dalam Buffer constructor jika sumber mengimplementasikan ICollection maka ia memanggil source.CopyTo (item, 0), dan kemudian .ToArray () mengembalikan array item internal secara langsung. Jadi tidak ada konversi yang membutuhkan waktu ekstra dalam kasus itu. Jika sumber tidak mengimplementasikan ICollection maka ToArray akan menghasilkan salinan array untuk memangkas lokasi tambahan yang tidak digunakan dari akhir array seperti dijelaskan oleh komentar Scott Rippey di atas.Memori akan selalu dialokasikan dua kali - atau sesuatu yang dekat dengan itu. Karena Anda tidak dapat mengubah ukuran array, kedua metode akan menggunakan semacam mekanisme untuk mengumpulkan data dalam koleksi yang berkembang. (Yah, Daftar itu sendiri merupakan koleksi yang terus tumbuh.)
Daftar menggunakan array sebagai penyimpanan internal, dan menggandakan kapasitas saat dibutuhkan. Ini berarti bahwa rata-rata 2/3 dari item telah dialokasikan kembali setidaknya sekali, setengah dari mereka yang dialokasikan kembali setidaknya dua kali, setengah dari mereka setidaknya tiga kali, dan seterusnya. Itu berarti bahwa setiap item rata-rata telah dialokasikan kembali 1,3 kali, yang tidak terlalu banyak biaya overhead.
Ingat juga bahwa jika Anda mengumpulkan string, koleksi itu sendiri hanya berisi referensi ke string, string itu sendiri tidak dialokasikan kembali.
sumber
Ini tahun 2020 di luar dan semua orang menggunakan .NET Core 3.1 jadi saya memutuskan untuk menjalankan beberapa tolok ukur dengan Benchmark.NET.
TL; DR: ToArray () lebih baik dari segi kinerja dan melakukan maksud penyampaian pekerjaan yang lebih baik jika Anda tidak berencana untuk mengubah koleksi.
Hasilnya adalah:
sumber
ToImmutableArray()
(dari System.Collections.Paket yang dapat ditentukan) 😉Sunting : Bagian terakhir dari jawaban ini tidak valid. Namun, sisanya masih merupakan informasi yang berguna, jadi saya akan meninggalkannya.
Saya tahu ini adalah posting lama, tetapi setelah memiliki pertanyaan yang sama dan melakukan penelitian, saya menemukan sesuatu yang menarik yang mungkin layak untuk dibagikan.
Pertama, saya setuju dengan @mquander dan jawabannya. Dia benar dalam mengatakan bahwa dari segi kinerja, keduanya identik.
Namun, saya telah menggunakan Reflector untuk melihat metode-metode di
System.Linq.Enumerable
namespace ekstensi, dan saya perhatikan optimasi yang sangat umum.Jika memungkinkan,
IEnumerable<T>
sumber dilemparkan keIList<T>
atauICollection<T>
untuk mengoptimalkan metode. Sebagai contoh, lihatElementAt(int)
.Menariknya, Microsoft memilih untuk hanya mengoptimalkan
IList<T>
, tetapi tidakIList
. Sepertinya Microsoft lebih suka menggunakanIList<T>
antarmuka.System.Array
hanya mengimplementasikanIList
, jadi tidak akan mendapat manfaat dari optimasi ekstensi ini.Oleh karena itu, saya sampaikan bahwa praktik terbaik adalah menggunakan
.ToList()
metode.Jika Anda menggunakan salah satu metode ekstensi, atau meneruskan daftar ke metode lain, ada kemungkinan metode itu dioptimalkan untuk
IList<T>
.sumber
Saya menemukan tolok ukur lain yang dilakukan orang di sini kurang, jadi inilah celah saya untuk itu. Beri tahu saya jika Anda menemukan sesuatu yang salah dengan metodologi saya.
Anda dapat mengunduh Script LINQPad di sini .
Hasil:
Tweak kode di atas, Anda akan menemukan bahwa:
int
s daripadastring
s.struct
s besar alih-alihstring
s membutuhkan lebih banyak waktu secara umum, tetapi tidak terlalu mengubah rasio.Ini setuju dengan kesimpulan dari jawaban terpilih:
ToList()
secara konsisten berjalan lebih cepat, dan akan menjadi pilihan yang lebih baik jika Anda tidak berencana untuk bertahan pada hasil untuk waktu yang lama.Memperbarui
@ JonHanna menunjukkan bahwa tergantung pada implementasi
Select
itu mungkin untukToList()
atauToArray()
implementasi untuk memprediksi ukuran koleksi yang dihasilkan sebelumnya. Mengganti.Select(i => i)
kode di atas denganWhere(i => true)
hasil yang sangat mirip saat ini, dan lebih mungkin untuk melakukannya terlepas dari implementasi .NET.sumber
100000
dan menggunakannya untuk mengoptimalkan keduanyaToList()
danToArray()
, denganToArray()
menjadi sedikit lebih ringan karena tidak memerlukan operasi menyusut yang diperlukan jika tidak, yang merupakan satu tempatToList()
memiliki keuntungan. Contoh dalam pertanyaan masih akan kalah, karenaWhere
cara prediksi ukuran seperti itu tidak dapat dilakukan..Select(i => i)
bisa diganti dengan.Where(i => true)
untuk memperbaikinya.ToArray()
keuntungan) dan yang tidak, seperti di atas, dan membandingkan hasilnya.ToArray()
masih kalah dalam skenario kasus terbaik. DenganMath.Pow(2, 15)
elemen, itu (ToList: 700ms, ToArray: 900ms). Menambahkan satu elemen lagi menabraknya (ToList: 925, ToArray: 1350). Saya ingin tahu apakahToArray
masih menyalin array bahkan ketika itu sudah ukuran yang sempurna? Mereka mungkin mengira itu adalah kejadian yang cukup langka sehingga tidak sepadan dengan persyaratan tambahan.Anda harus mendasarkan keputusan Anda untuk memilih
ToList
atauToArray
berdasarkan pada apa yang idealnya pilihan desain. Jika Anda ingin koleksi yang hanya dapat diulang dan diakses dengan indeks, pilihToArray
. Jika Anda ingin kemampuan tambahan untuk menambah dan menghapus dari koleksi nanti tanpa banyak kesulitan, maka lakukanToList
(tidak terlalu Anda tidak dapat menambahkan ke array, tapi itu biasanya bukan alat yang tepat untuk itu).Jika masalah kinerja, Anda juga harus mempertimbangkan apa yang lebih cepat untuk beroperasi. Secara realistis, Anda tidak akan menelepon
ToList
atauToArray
jutaan kali, tetapi mungkin bekerja pada koleksi yang diperoleh sejuta kali. Dalam hal itu[]
lebih baik, karenaList<>
ini[]
dengan beberapa overhead. Lihat utas ini untuk beberapa perbandingan efisiensi: Mana yang lebih efisien: Daftar <int> atau int []Dalam tes saya sendiri beberapa waktu lalu, saya menemukan
ToArray
lebih cepat. Dan saya tidak yakin seberapa miringnya tes itu. Perbedaan kinerja sangat tidak signifikan, yang hanya dapat terlihat jika Anda menjalankan kueri ini dalam satu lingkaran jutaan kali.sumber
Jawaban yang sangat terlambat tetapi saya pikir ini akan sangat membantu bagi para googler.
Mereka berdua payah ketika mereka dibuat menggunakan LINQ. Keduanya menerapkan kode yang sama untuk mengubah ukuran buffer jika perlu .
ToArray
internal menggunakan kelas untuk mengkonversiIEnumerable<>
ke array, dengan mengalokasikan array 4 elemen. Jika itu tidak cukup daripada menggandakan ukuran dengan membuat array baru, gandakan ukuran arus dan menyalin array saat ini ke sana. Pada akhirnya itu mengalokasikan array baru jumlah item Anda. Jika kueri Anda mengembalikan 129 elemen maka ToArray akan membuat 6 alokasi dan operasi penyalinan memori untuk membuat array 256 elemen dan kemudian array 129 yang lain untuk dikembalikan. begitu banyak untuk efisiensi memori.ToList melakukan hal yang sama, tetapi melompati alokasi terakhir karena Anda dapat menambahkan item di masa depan. Daftar tidak peduli apakah itu dibuat dari permintaan LINQ atau dibuat secara manual.
untuk pembuatan Daftar lebih baik dengan memori tetapi lebih buruk dengan cpu karena daftar adalah solusi umum setiap tindakan memerlukan pemeriksaan rentang tambahan untuk pemeriksaan jangkauan internal .net untuk array.
Jadi, jika Anda akan mengulangi melalui set hasil Anda terlalu banyak, maka array baik karena itu berarti lebih sedikit rentang pemeriksaan daripada daftar, dan kompiler umumnya mengoptimalkan array untuk akses berurutan.
Alokasi inisialisasi daftar bisa lebih baik jika Anda menentukan parameter kapasitas saat Anda membuatnya. Dalam hal ini hanya akan mengalokasikan array sekali, dengan asumsi Anda tahu ukuran hasilnya.
ToList
dari linq tidak menentukan kelebihan beban untuk menyediakannya, jadi kami harus membuat metode ekstensi kami yang membuat daftar dengan kapasitas yang diberikan dan kemudian menggunakannyaList<>.AddRange
.Untuk menyelesaikan jawaban ini saya harus menulis kalimat berikut
sumber
List<T>
, tetapi ketika Anda tidak atau ketika Anda tidak bisa, Anda tidak bisa menahannya.Ini adalah pertanyaan lama - tetapi untuk kepentingan pengguna yang menemukan itu, ada juga dan alternatif 'Memoizing' the Enumerable - yang memiliki efek caching dan menghentikan beberapa enumerasi pernyataan Linq, yang merupakan ToArray () dan ToList () digunakan untuk banyak hal, meskipun atribut koleksi dari daftar atau array tidak pernah digunakan.
Memoize tersedia di RX / System. Lib interaktif, dan dijelaskan di sini: Lebih banyak LINQ dengan System.Interactive
(Dari blog Bart De'Smet yang merupakan bacaan yang sangat dianjurkan jika Anda banyak bekerja dengan Linq ke Objects)
sumber
Salah satu opsi adalah menambahkan metode ekstensi Anda sendiri yang mengembalikan hanya baca
ICollection<T>
. Ini bisa lebih baik daripada menggunakanToList
atauToArray
ketika Anda tidak ingin menggunakan properti pengindeksan array / daftar, atau menambah / menghapus dari daftar.Tes unit:
sumber
ToListAsync<T>()
lebih disukai.Dalam Entity Framework 6 kedua metode akhirnya memanggil ke metode internal yang sama, tetapi
ToArrayAsync<T>()
panggilanlist.ToArray()
di akhir, yang diimplementasikan sebagaiBegitu
ToArrayAsync<T>()
juga beberapa overhead, dengan demikianToListAsync<T>()
lebih disukai.sumber
Pertanyaan lama tetapi pertanyaan baru sepanjang waktu.
Menurut sumber System.Linq.Enumerable ,
ToList
cukup kembalikan anew List(source)
, sementaraToArray
gunakan anew Buffer<T>(source).ToArray()
untuk mengembalikan aT[]
.Saat berjalan pada satu-
IEnumerable<T>
satunya objek,ToArray
jangan mengalokasikan memori sekali lagiToList
. Tetapi Anda tidak perlu mempedulikannya dalam banyak kasus, karena GC akan melakukan pengumpulan sampah saat dibutuhkan.Mereka yang mempertanyakan pertanyaan ini dapat menjalankan kode berikut di komputer Anda sendiri, dan Anda akan mendapatkan jawaban Anda.
Saya mendapat hasil ini di mesin saya:
Karena batas stackoverflow untuk jumlah karakter dari jawaban, daftar sampel Group2 dan Group3 dihilangkan.
Seperti yang Anda lihat, itu benar-benar tidak penting untuk digunakan
ToList
atauToArry
dalam kebanyakan kasus.Saat memproses
IEnumerable<T>
objek yang dihitung runtime , jika beban yang dibawa oleh perhitungan lebih berat dari alokasi memori dan operasi salin dariToList
danToArray
, disparitas tidak signifikan (C.ToList vs C.ToArray
danS.ToList vs S.ToArray
).Perbedaannya hanya dapat diamati pada
IEnumerable<T>
objek yang dihitung non-runtime (C1.ToList vs C1.ToArray
danS1.ToList vs S1.ToArray
). Tetapi perbedaan absolut (<60ms) masih dapat diterima pada satu juta objek kecilIEnumerable<T>
. Bahkan, perbedaannya ditentukan oleh implementasiEnumerator<T>
dariIEnumerable<T>
. Jadi, jika program Anda benar-benar sangat sensitif tentang ini, Anda harus profil, profil, profil ! Akhirnya Anda mungkin akan menemukan bahwa bottleneck bukan padaToList
atauToArray
, tetapi detail enumerator.Dan, hasil dari
C2.ToList vs C2.ToArray
danS2.ToList vs S2.ToArray
menunjukkan bahwa, Anda benar-benar tidak perlu peduliToList
atauToArray
padaICollection<T>
objek yang dihitung non-runtime .Tentu saja, ini hanya hasil pada mesin saya, waktu yang sebenarnya dihabiskan untuk operasi ini pada mesin yang berbeda tidak akan sama, Anda dapat mengetahui pada mesin Anda menggunakan kode di atas.
Satu-satunya alasan Anda perlu membuat pilihan adalah bahwa, Anda memiliki kebutuhan khusus
List<T>
atauT[]
, seperti yang dijelaskan oleh jawaban @Jeppe Stig Nielsen .sumber
Bagi siapa pun yang tertarik menggunakan hasil ini di Linq-to-sql lain seperti
maka SQL yang dihasilkan sama apakah Anda menggunakan Daftar atau Array untuk myListOrArray. Sekarang saya tahu beberapa orang mungkin bertanya mengapa bahkan menyebutkan sebelum pernyataan ini, tetapi ada perbedaan antara SQL yang dihasilkan dari IQueryable vs (Daftar atau Array).
sumber