ToList () çağrılırken performans etkisi var mı?


139

Kullanırken ToList()dikkate alınması gereken bir performans etkisi var mı?

Ben sorgu olan bir dizinden dosyaları almak için bir sorgu yazıyordu:

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

Ancak, onunla çalışmaktan hoşlandığım için List<>, ...

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

Öyleyse, böyle bir dönüşüm yapmaya karar verirken göz önünde bulundurulması gereken veya yalnızca çok sayıda dosyayla uğraşırken göz önünde bulundurulması gereken bir tür performans etkisi var mı? Bu önemsiz bir dönüşüm mü?


+1 de cevabı burada bilmek istiyor. Uygulamanın performansı kritik olmadığı sürece IMHO, ben her zaman bir kullanma düşünüyorum List<T>bir yana T[]elbette dönüşüm sürece (kod daha / mantıksal okunabilir / sürdürülebilir yaparsa oldu neden farkedilir ben yeniden ediyorum ki bu durumda performans sorunları ziyaret edin sanırım).
Mart'ta Sepster

Bir diziden liste oluşturmak çok ucuz olmalıdır.
leppie

2
@Sepster Veri türünü yalnızca bir iş yapmam gerektiği kadar belirtirim. Aramaya yoksa Addya Remove, ben gibi bırakacaktı IEnumerable<T>(hatta daha iyi var)
pswg

4
Bence, bu durumda bunun EnumerateFilesyerine aramak daha iyidir GetFiles, bu yüzden sadece bir dizi oluşturulacaktır.
tukaef

3
GetFiles(directory), şu anda .NET'te uygulandığı gibi, hemen hemen öyle new List<string>(EnumerateFiles(directory)).ToArray(). Böylece GetFiles(directory).ToList()bir liste oluşturur, bundan bir dizi oluşturur, sonra tekrar bir liste oluşturur. 2kay'ın dediği gibi, EnumerateFiles(directory).ToList()burada yapmayı tercih etmelisiniz .
Joren

Yanıtlar:


178

IEnumerable.ToList()

Evet, IEnumerable<T>.ToList()performans etkisi vardır, O (n) operasyonudur, ancak performans açısından kritik operasyonlarda dikkat edilmesi gerekecektir.

ToList()Operasyon kullanacağı List(IEnumerable<T> collection)yapıcı. Bu kurucu dizinin bir kopyasını oluşturmalıdır (daha genel olarak IEnumerable<T>), aksi takdirde orijinal dizinin gelecekteki değişiklikleri kaynakta T[]da değişecektir ve bu da genellikle istenmez.

Bu sadece büyük bir liste ile bir fark yaratacak yinelemek istiyorum, bellek yığınlarını kopyalamak oldukça hızlı bir işlemdir.

Kullanışlı ipucu, AsvsTo

LINQ'da As(gibi AsEnumerable()) ve To(gibi ) ile başlayan birkaç yöntem olduğunu fark edeceksiniz ToList(). İle başlayan yöntemler Toyukarıdaki gibi bir dönüşüm gerektirir (yani performansı etkileyebilir) ve ile başlayan yöntemler Assadece bir miktar döküm veya basit bir işlem gerektirmez ve gerektirecektir.

Hakkında ek ayrıntılar List<T>

List<T>İlgilenmeniz durumunda nasıl çalıştığına dair biraz daha detay :)

A List<T>ayrıca isteğe bağlı olarak yeniden boyutlandırılması gereken dinamik dizi adı verilen bir yapı kullanır; bu yeniden boyutlandırma olayı eski bir dizinin içeriğini yeni diziye kopyalar. Böylece küçük başlar ve gerekirse boyutu artar .

Bu Capacityve ile Countöznitelikleri arasındaki farktır List<T>. Capacitysahnenin arkasındaki dizinin boyutunu belirtir , içinde her zaman Countbulunan öğe sayısıdır . Bu nedenle, listeye bir öğe eklendiğinde, geçmişi artırır, öğesinin boyutu iki katına çıkar ve dizi kopyalanır.List<T><= CapacityCapacityList<T>


2
Ben sadece List(IEnumerable<T> collection)yapıcı toplama parametresi olup olmadığını denetler ICollection<T>ve sonra hemen gerekli boyutu ile yeni bir iç dizi oluşturur vurgulamak istedim . Parametre koleksiyonu değilse ICollection<T>, yapıcı onu tekrarlar ve Addher bir elemanı çağırır .
Justinas Simanavicius

ToList'i () sık sık yanıltıcı derecede zorlu bir işlem olarak görebileceğinizi not etmek önemlidir. Bu bir IEnumerable <> througha LINQ sorgusu oluşturduğunuzda olur. linq sorgusu oluşturulur ancak çalıştırılmaz. ToList () çağrılması sorguyu çalıştırır ve bu nedenle kaynak yoğun
görünür

36

ToList () öğesini çağırırken performans etkisi var mı?

Evet tabi ki. Teorik olarak bile i++bir performans etkisi vardır, programı belki birkaç kenara yavaşlatır.

Ne yapar .ToList?

Çağırdığınızda .ToList, kod Enumerable.ToList()bir uzantı yöntemi olan çağırır return new List<TSource>(source). İlgili kurucuda, en kötü durumda, öğe kabından geçer ve bunları tek tek yeni bir kaba ekler. Bu nedenle davranışı performansı çok az etkiler. Uygulamanızın performans şişe boynu olmak imkansızdır.

Söz konusu koddaki sorun ne

Directory.GetFilesklasörden geçer ve tüm dosyaların adlarını hemen belleğe döndürür , [] dizesinin çok fazla belleğe mal olması ve her şeyi yavaşlatması riski vardır.

O zaman ne yapılmalı

Değişir. Siz (hem de iş mantığınız) klasördeki dosya miktarının her zaman küçük olduğunu garanti ederseniz, kod kabul edilebilir. Ancak yine de tembel bir sürüm kullanılması önerilir: Directory.EnumerateFilesC # 4'te. Bu, hemen yürütülmeyecek bir sorguya çok benzer, üzerine daha fazla sorgu ekleyebilirsiniz:

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

adı "dosyam" içeren bir dosya bulunur bulunmaz yol aramayı durduracaktır . Bu açıkçası daha iyi bir performansa sahip .GetFiles.


19

ToList () öğesini çağırırken performans etkisi var mı?

Evet var. Uzantı yöntemini kullanmak, kaynak koleksiyonundan, elbette performans etkisi olan Enumerable.ToList()yeni bir List<T>nesne IEnumerable<T>oluşturacaktır.

Ancak, anlayış List<T>performans etkisinin önemli olup olmadığını belirlemenize yardımcı olabilir.

List<T>T[]listenin öğelerini saklamak için bir dizi ( ) kullanır . Diziler ayrıldıktan sonra genişletilemez, bu nedenle List<T>listenin öğelerini saklamak için büyük boyutlu bir dizi kullanılır. Ne zaman List<T>boyutunun ötesine yatan diziyi büyür yeni dizi tahsis edilecek vardır ve eski dizinin içeriği liste büyüyebilir önce yeni büyük diziye kopyalanacak vardır.

Birinden yeni List<T>bir inşa IEnumerable<T>edildiğinde iki durum vardır:

  1. Kaynak koleksiyonu uygular ICollection<T>: Daha sonra kaynak koleksiyonunun ICollection<T>.Counttam boyutunu elde etmek için kullanılır ve kaynak koleksiyonunun tüm öğeleri kullanılarak yedekleme dizisine kopyalanmadan önce eşleşen bir yedekleme dizisi ayrılır ICollection<T>.CopyTo(). Bu işlem oldukça verimlidir ve muhtemelen bellek bloklarını kopyalamak için bazı CPU talimatlarıyla eşleşecektir. Bununla birlikte, performans açısından yeni dizi için bellek gereklidir ve tüm öğeleri kopyalamak için CPU döngüleri gereklidir.

  2. Aksi takdirde, kaynak koleksiyonunun boyutu bilinmemektedir ve IEnumerable<T>her bir kaynak elemanı yenisine birer birer eklemek için numaralandırıcısı kullanılır List<T>. Başlangıçta destek dizisi boştur ve 4 boyutunda bir dizi oluşturulur. Daha sonra bu dizi çok küçük olduğunda, boyut iki katına çıkar, böylece destek dizisi bu şekilde büyür 4, 8, 16, 32 vb. Bu işlem, doğru boyutta bir dizinin hemen oluşturulabileceği ilk duruma kıyasla çok daha maliyetlidir.

    Ayrıca, kaynak koleksiyonunuzda 33 öğe varsa, liste biraz bellek harcayan 64 öğeden oluşan bir dizi kullanır.

Sizin durumunuzda, kaynak koleksiyonu uygulayan bir dizidir, bu ICollection<T>nedenle performans etkisi, kaynak diziniz çok büyük olmadıkça endişelenmeniz gereken bir şey değildir. Aramak ToList(), kaynak diziyi kopyalayıp bir List<T>nesneye sarar . İkinci davanın performansı bile küçük koleksiyonlar için endişelenecek bir şey değil.


5

"Dikkate alınması gereken bir performans etkisi var mı?"

Kesin senaryo ile ilgili sorun, her şeyden önce performansla ilgili gerçek kaygınızın, sürücünün önbelleğinin sabit sürücü hızı ve verimliliğinden kaynaklanmasıdır.

Bu açıdan bakıldığında, darbe o noktaya mutlaka önemsiz NO o dikkate alınması gerekmez.

AMA SADECE List<>yapının özelliklerine gerçekten daha üretken olmanız veya algoritmanızın daha kolay olması veya başka bir avantaj sağlamanız gerekiyorsa. Aksi takdirde, bilerek önemsiz bir performans isabeti eklersiniz, hiçbir sebep olmadan. Bu durumda, doğal olarak, yapmamalısınız! :)


4

ToList()yeni bir Liste oluşturur ve öğeleri içine koyar, bu da yapmakla ilişkili bir maliyet olduğu anlamına gelir ToList(). Küçük bir toplama durumunda, bu fark edilir bir maliyet olmayacaktır, ancak büyük bir koleksiyona sahip olmak, ToList kullanımı durumunda bir performans isabetine neden olabilir.

Genelde ToList () yöntemini, yaptığınız iş koleksiyonu Listeye dönüştürmeden yapılamazsa kullanmamalısınız. Örneğin, yalnızca koleksiyonu yinelemek istiyorsanız ToList uygulamanıza gerek yoktur

Bir veri kaynağına karşı sorguları örneğin LINQ to SQL kullanarak bir Veritabanı gerçekleştiriyorsanız, ToList yapmanın maliyeti çok daha fazladır çünkü Gecikmeli Yürütme yerine LINQ to SQL ile ToList kullandığınızda, yani gerektiğinde öğeleri yükle (yararlı olabilir) birçok senaryoda) öğeleri anında Veritabanından belleğe yükler


Haris: orijinal kaynak hakkında emin değilim ToList () çağırdıktan sonra orijinal kaynağa ne olacak
TalentTuner 20:13

@Saurabh GC temizleyecek
pswg

@Surabh orijinal kaynağa hiçbir şey olmayacak. Orijinal kaynak unsurlarına yeni oluşturulan liste atıfta bulunacak
Haris Hasan

"Sadece koleksiyon boyunca yineleme yapmak istiyorsanız ToList uygulamanız gerekmez" - peki nasıl yinelemelisiniz?
SharpC

4

Yapmak kadar verimli olacaktır:

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

Bir alanın kurucusunun kaynak kodunu sökerseniz, IEnumerable<T>birkaç şey yapacağını göreceksiniz:

  • Çağrı collection.Count, eğer öyleyse collectionbir olduğunu IEnumerable<T>, bu çalışmaya zorlar. Bir collectiondizi, liste vb O(1). Olmalıdır .

  • Eğer collectionuygular ICollection<T>, bu kullanarak bir iç dizideki öğeleri kurtaracak ICollection<T>.CopyToyöntem. O gerektiğini olmak O(n)olmak nkoleksiyon uzunluğu.

  • Eğer collectionuygulamıyor ICollection<T>, bu koleksiyonun öğeler arasında yineleme olacak ve bir iç listeye ekleyecektir.

Yani, evet, yeni bir liste oluşturmak zorunda olduğu için daha fazla bellek tüketecek ve en kötü durumda,O(n)collection her öğenin bir kopyasını yapmak için yineleneceği için olacaktır .


3
kapat, orijinal koleksiyondaki dizelerin işgal ettiği baytların toplamı 0(n)nerede n, öğelerin sayısını değil (daha kesin olmak
gerekirse

@ user1416420 Yanlış olabilirim, ama neden böyle? De (örneğin. Diğer bazı türde bir koleksiyon nedir eğer bool, intvs.)? Koleksiyondaki her dizenin bir kopyasını oluşturmanız gerekmez. Sadece yeni listeye eklersiniz.
Oscar Mederos

hala yeni bellek ayırma ve baytların kopyalanması bu yöntemi öldüren önemli değil. Bir bool ayrıca .NET'te 4 bayt kaplar. Aslında .NET'te bir nesneye yapılan her başvuru en az 8 bayt uzunluğunda olduğundan, oldukça yavaştır. ilk 4 bayt tür tablosunu gösterir ve ikinci 4 bayt değeri veya değeri bulacağınız yeri gösterir
user1416420

3

Dosya listesi almanın performansı göz önüne alındığında ToList(), ihmal edilebilir. Ama diğer senaryolar için değil. Bu gerçekten nerede kullandığınıza bağlıdır.

  • Bir diziyi, listeyi veya başka bir koleksiyonu çağırırken, koleksiyonun bir kopyasını List<T>. Buradaki performans listenin boyutuna bağlıdır. Gerçekten gerekli olduğunda yapmalısınız.

    Örneğinizde, bunu bir dizide çağırırsınız. Dizi üzerinde yineleme yapar ve öğeleri yeni oluşturulan listeye tek tek ekler. Bu nedenle performans etkisi dosya sayısına bağlıdır.

  • Bir çağıran zaman IEnumerable<T>, sen gerçekleştirmekIEnumerable<T> (genellikle bir sorgu).


2

ToList Yeni bir liste oluşturur ve öğeleri orijinal kaynaktan yeni oluşturulan listeye kopyalar, böylece tek şey öğeleri orijinal kaynaktan kopyalamak ve kaynak boyutuna bağlıdır

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.