Có tác động hiệu suất khi gọi ToList () không?


139

Khi sử dụng ToList(), có một tác động hiệu suất cần được xem xét?

Tôi đã viết một truy vấn để lấy các tập tin từ một thư mục, đó là truy vấn:

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

Tuy nhiên, vì tôi thích làm việc List<>thay vào đó, tôi quyết định đưa vào ...

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

Vì vậy, có một số loại tác động hiệu suất nên được xem xét khi quyết định thực hiện chuyển đổi như thế này - hoặc chỉ được xem xét khi xử lý một số lượng lớn tệp? Đây có phải là một chuyển đổi không đáng kể?


+1 quan tâm để biết câu trả lời ở đây quá. IMHO trừ khi ứng dụng là hiệu suất quan trọng, tôi nghĩ rằng tôi luôn luôn muốn sử dụng một List<T>trong lợi của một T[]nếu nó làm cho mã logic hơn / dễ đọc / bảo trì (tất nhiên trừ việc chuyển đổi đã gây chú ý vấn đề hiệu suất trong trường hợp này tôi muốn tái thăm nó tôi đoán).
Sepster 20/03/13

Tạo một danh sách từ một mảng nên siêu rẻ.
leppie

2
@Sepster Tôi chỉ xác định loại dữ liệu cụ thể như tôi cần để thực hiện một công việc. Nếu tôi không phải gọi Addhoặc Remove, tôi sẽ để nó dưới dạng IEnumerable<T>(hoặc thậm chí tốt hơn var)
pswg

4
Tôi nghĩ, trong trường hợp này tốt hơn là gọi EnumerateFilesthay vì GetFilesvậy, chỉ có một mảng sẽ được tạo.
tukaef

3
GetFiles(directory), vì nó được triển khai trong .NET hiện tại, khá nhiều new List<string>(EnumerateFiles(directory)).ToArray(). Vì vậy, GetFiles(directory).ToList()tạo một danh sách, tạo một mảng từ đó, sau đó tạo lại một danh sách. Giống như 2kay nói, bạn nên thích làm EnumerateFiles(directory).ToList()ở đây.
Joren

Câu trả lời:


178

IEnumerable.ToList()

Có, IEnumerable<T>.ToList()có tác động hiệu suất, đó là một hoạt động O (n) mặc dù nó có thể sẽ chỉ yêu cầu sự chú ý trong các hoạt động quan trọng về hiệu suất.

Các ToList()hoạt động sẽ sử dụng các nhà List(IEnumerable<T> collection)xây dựng. Hàm tạo này phải tạo một bản sao của mảng (nói chung hơn IEnumerable<T>), nếu không các sửa đổi trong tương lai của mảng ban đầu sẽ thay đổi trên nguồn T[]cũng không được mong muốn nói chung.

Tôi muốn nhắc lại điều này sẽ chỉ tạo ra sự khác biệt với một danh sách khổng lồ, sao chép các đoạn bộ nhớ là một thao tác khá nhanh để thực hiện.

Mẹo tiện dụng, AsvsTo

Bạn sẽ nhận thấy trong LINQ có một số phương thức bắt đầu bằng As(chẳng hạn như AsEnumerable()) và To(chẳng hạn như ToList()). Các phương thức bắt đầu bằng Toyêu cầu chuyển đổi như trên (nghĩa là có thể ảnh hưởng đến hiệu suất) và các phương thức bắt đầu bằng Askhông và sẽ chỉ yêu cầu một số thao tác truyền hoặc đơn giản.

Chi tiết bổ sung về List<T>

Dưới đây là một chi tiết nhỏ hơn về cách List<T>hoạt động trong trường hợp bạn quan tâm :)

Một List<T>cũng sử dụng một cấu trúc được gọi là một mảng động cần được thay đổi kích thước theo yêu cầu, sự kiện thay đổi kích thước này sao chép nội dung của một mảng cũ sang mảng mới. Vì vậy, nó bắt đầu nhỏ và tăng kích thước nếu cần thiết .

Đây là sự khác biệt giữa CapacityCountcác thuộc tính trên List<T>. Capacityđề cập đến kích thước của mảng phía sau hậu trường, Countlà số lượng vật phẩm List<T>luôn luôn có <= Capacity. Vì vậy, khi một mục được thêm vào danh sách, tăng nó qua Capacity, kích thước của mục List<T>được nhân đôi và mảng được sao chép.


2
Tôi chỉ muốn nhấn mạnh rằng hàm List(IEnumerable<T> collection)tạo kiểm tra nếu tham số bộ sưu tập là ICollection<T>và sau đó tạo một mảng bên trong mới với kích thước yêu cầu ngay lập tức. Nếu bộ sưu tập tham số là không ICollection<T>, hàm tạo sẽ lặp qua nó và gọi Addcho từng phần tử.
Justinas Simanavicius

Điều quan trọng cần lưu ý là bạn có thể thường thấy ToList () là một hoạt động đòi hỏi sai lệch. Điều này xảy ra khi bạn tạo một truy vấn LINQ <> viaa LINQ. truy vấn linq được xây dựng nhưng không được thực thi. gọi ToList () sẽ chạy truy vấn và do đó có vẻ tốn nhiều tài nguyên - nhưng đó là truy vấn chuyên sâu và không phải là hoạt động ToList () (Trừ khi đó là một danh sách thực sự lớn)
dancer42

36

Có tác động hiệu suất khi gọi tới List () không?

Phải, tất nhiên. Về mặt lý thuyết thậm chí i++có tác động hiệu suất, nó làm chậm chương trình trong một vài tích tắc.

Không gì .ToListlàm gì?

Khi bạn gọi .ToList, mã gọi Enumerable.ToList()đó là một phương thức mở rộng return new List<TSource>(source). Trong hàm tạo tương ứng, trong trường hợp xấu nhất, nó đi qua vùng chứa vật phẩm và thêm từng cái một vào một thùng chứa mới. Vì vậy, hành vi của nó ảnh hưởng ít đến hiệu suất. Không thể là một cổ chai hiệu suất của ứng dụng của bạn.

Có gì sai với mã trong câu hỏi

Directory.GetFilesđi qua thư mục và trả lại tất cả tên của các tệp ngay lập tức vào bộ nhớ, có nguy cơ tiềm ẩn là chuỗi [] tốn rất nhiều bộ nhớ, làm chậm mọi thứ.

Nên làm gì sau đó

Nó phụ thuộc. Nếu bạn (cũng như logic kinh doanh của bạn) đảm bảo rằng số lượng tệp trong thư mục luôn nhỏ, mã có thể được chấp nhận. Nhưng nó vẫn được đề xuất sử dụng phiên bản lười biếng: Directory.EnumerateFilestrong C # 4. Điều này giống như một truy vấn, sẽ không được thực thi ngay lập tức, bạn có thể thêm nhiều truy vấn vào đó như:

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

sẽ dừng tìm kiếm đường dẫn ngay khi một tệp có tên "myfile" được tìm thấy. Điều này rõ ràng là có một hiệu suất tốt hơn sau đó .GetFiles.


19

Có tác động hiệu suất khi gọi tới List () không?

Có, có. Sử dụng phương thức mở rộng Enumerable.ToList()sẽ xây dựng một List<T>đối tượng mới từ IEnumerable<T>bộ sưu tập nguồn, tất nhiên có tác động hiệu năng.

Tuy nhiên, sự hiểu biết List<T>có thể giúp bạn xác định xem tác động hiệu suất có đáng kể hay không.

List<T>sử dụng một mảng ( T[]) để lưu trữ các phần tử của danh sách. Mảng không thể được mở rộng một khi chúng được phân bổ, vì vậy List<T>sẽ sử dụng một mảng có kích thước quá lớn để lưu trữ các phần tử của danh sách. Khi List<T>tăng trưởng vượt quá kích thước của mảng bên dưới, một mảng mới phải được phân bổ và nội dung của mảng cũ phải được sao chép sang mảng mới lớn hơn trước khi danh sách có thể phát triển.

Khi một cái mới List<T>được xây dựng từ một IEnumerable<T>có hai trường hợp:

  1. Bộ sưu tập nguồn thực hiện ICollection<T>: Sau đó ICollection<T>.Countđược sử dụng để có được kích thước chính xác của bộ sưu tập nguồn và một mảng sao lưu phù hợp được phân bổ trước khi tất cả các phần tử của bộ sưu tập nguồn được sao chép vào mảng sao lưu bằng cách sử dụng ICollection<T>.CopyTo(). Hoạt động này khá hiệu quả và có thể sẽ ánh xạ tới một số lệnh CPU để sao chép các khối bộ nhớ. Tuy nhiên, về mặt bộ nhớ hiệu năng là cần thiết cho mảng mới và chu kỳ CPU được yêu cầu để sao chép tất cả các yếu tố.

  2. Mặt khác, kích thước của bộ sưu tập nguồn là không xác định và bộ liệt kê IEnumerable<T>được sử dụng để thêm từng phần tử nguồn một lần vào cái mới List<T>. Ban đầu mảng sao lưu trống và một mảng có kích thước 4 được tạo. Sau đó, khi mảng này quá nhỏ, kích thước được nhân đôi, do đó, mảng sao lưu phát triển như thế này 4, 8, 16, 32, v.v ... Mỗi khi mảng sao lưu phát triển, nó phải được phân bổ lại và tất cả các phần tử được lưu trữ cho đến nay phải được sao chép. Thao tác này tốn kém hơn nhiều so với trường hợp đầu tiên trong đó một mảng có kích thước chính xác có thể được tạo ngay lập tức.

    Ngoài ra, nếu bộ sưu tập nguồn của bạn chứa 33 phần tử, danh sách sẽ kết thúc bằng cách sử dụng một mảng gồm 64 phần tử gây lãng phí bộ nhớ.

Trong trường hợp của bạn, bộ sưu tập nguồn là một mảng thực hiện ICollection<T>do đó tác động hiệu năng không phải là điều bạn nên quan tâm trừ khi mảng nguồn của bạn rất lớn. Gọi ToList()sẽ chỉ đơn giản là sao chép mảng nguồn và bọc nó trong một List<T>đối tượng. Ngay cả hiệu suất của trường hợp thứ hai cũng không phải là điều đáng lo ngại đối với các bộ sưu tập nhỏ.


5

"có một tác động hiệu suất cần được xem xét?"

Vấn đề với kịch bản chính xác của bạn là trước hết và mối quan tâm thực sự của bạn về hiệu suất sẽ đến từ tốc độ ổ cứng và hiệu quả của bộ đệm của ổ đĩa.

Từ quan điểm đó, tác động chắc chắn là không đáng kể đến mức KHÔNG cần phải xem xét.

NHƯNG CHỈ nếu bạn thực sự cần các tính năng của List<>cấu trúc để có thể làm cho bạn hiệu quả hơn, hoặc thuật toán của bạn thân thiện hơn, hoặc một số lợi thế khác. Mặt khác, bạn chỉ cố tình thêm một hiệu suất không đáng kể, không có lý do nào cả. Trong trường hợp đó, tự nhiên, bạn không nên làm điều đó! :)


4

ToList()tạo một Danh sách mới và đặt các yếu tố trong đó có nghĩa là có một chi phí liên quan với việc thực hiện ToList(). Trong trường hợp bộ sưu tập nhỏ, chi phí sẽ rất đáng chú ý nhưng việc có một bộ sưu tập lớn có thể gây ra hiệu quả trong trường hợp sử dụng ToList.

Nói chung, bạn không nên sử dụng ToList () trừ khi công việc bạn đang làm không thể được thực hiện mà không chuyển đổi bộ sưu tập thành Danh sách. Ví dụ: nếu bạn chỉ muốn lặp qua bộ sưu tập, bạn không cần thực hiện ToList

Nếu bạn đang thực hiện truy vấn đối với nguồn dữ liệu, ví dụ như Cơ sở dữ liệu sử dụng LINQ to SQL thì chi phí thực hiện ToList sẽ cao hơn nhiều vì khi bạn sử dụng ToList với LINQ sang SQL thay vì thực hiện Trì hoãn thực thi, tức là tải các mục khi cần thiết (có thể có lợi trong nhiều tình huống) nó ngay lập tức tải các mục từ Cơ sở dữ liệu vào bộ nhớ


Haris: điều tôi không chắc chắn về nguồn ban đầu điều gì sẽ xảy ra với nguồn ban đầu sau khi gọi tới ToList ()
TalentTuner 20/03/13

@Saurabh GC sẽ dọn sạch nó
pswg

@Saurabh sẽ không có gì xảy ra với nguồn gốc. Các yếu tố của nguồn ban đầu sẽ được tham chiếu bởi danh sách mới được tạo
Haris Hasan

"Nếu bạn chỉ muốn lặp qua bộ sưu tập bạn không cần thực hiện ToList" - vậy bạn nên lặp lại như thế nào?
SharpC

4

Nó sẽ là (trong) hiệu quả như làm:

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

Nếu bạn tháo rời mã nguồn của hàm tạo mất một IEnumerable<T>, bạn sẽ thấy nó sẽ thực hiện một số điều:

  • Gọi collection.Count, vì vậy nếu collectionlà một IEnumerable<T>, nó sẽ buộc thực thi. Nếu collectionlà một mảng, danh sách, vv nó nên được O(1).

  • Nếu collectionthực hiện ICollection<T>, nó sẽ lưu các mục trong một mảng bên trong bằng ICollection<T>.CopyTophương thức. Nó nênO(n), là nchiều dài của bộ sưu tập.

  • Nếu collectionkhông thực hiện ICollection<T>, nó sẽ lặp qua các mục của bộ sưu tập và sẽ thêm chúng vào danh sách nội bộ.

Vì vậy, vâng, nó sẽ tiêu tốn nhiều bộ nhớ hơn, vì nó phải tạo một danh sách mới, và trong trường hợp xấu nhất, nó sẽO(n) , vì nó sẽ lặp qua collectionđể tạo một bản sao của từng thành phần.


3
đóng, 0(n)trong đó ntổng số byte mà các chuỗi trong bộ sưu tập ban đầu chiếm, không phải là tổng số phần tử (chính xác hơn là n = byte / kích thước từ)
user1416420 20/03/13

@ user1416420 Tôi có thể sai, nhưng tại sao vậy? Có gì nếu nó là một bộ sưu tập của một số loại khác (ví dụ. bool, int, Vv)? Bạn không thực sự phải tạo một bản sao của mỗi chuỗi trong bộ sưu tập. Bạn chỉ cần thêm chúng vào danh sách mới.
Oscar Mederos

vẫn không quan trọng việc cấp phát bộ nhớ mới và sao chép byte là điều đang giết chết phương thức này. Một bool cũng sẽ chiếm 4 byte trong .NET. Trên thực tế, mỗi tham chiếu của một đối tượng trong .NET dài ít nhất 8 byte, vì vậy nó khá chậm. 4 byte đầu tiên trỏ đến bảng loại & 4 byte thứ hai trỏ đến giá trị hoặc vị trí bộ nhớ để tìm giá trị
user1416420 20/03/13

3

Xem xét hiệu suất của việc truy xuất danh sách tập tin, ToList()là không đáng kể. Nhưng không thực sự cho các kịch bản khác. Điều đó thực sự phụ thuộc vào nơi bạn đang sử dụng nó.

  • Khi gọi trên một mảng, danh sách hoặc bộ sưu tập khác, bạn tạo một bản sao của bộ sưu tập dưới dạng List<T>. Hiệu suất ở đây phụ thuộc vào kích thước của danh sách. Bạn nên làm điều đó khi thực sự cần thiết.

    Trong ví dụ của bạn, bạn gọi nó trên một mảng. Nó lặp lại trên mảng và thêm từng mục một vào danh sách mới được tạo. Vì vậy, tác động hiệu suất phụ thuộc vào số lượng tập tin.

  • Khi gọi trên IEnumerable<T>, bạn có thể hóa các IEnumerable<T>(thường là một truy vấn).


2

ToList Sẽ tạo một danh sách mới và sao chép các phần tử từ nguồn ban đầu sang danh sách mới được tạo, do đó, điều duy nhất là sao chép các phần tử từ nguồn ban đầu và phụ thuộc vào kích thước nguồn

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.