Là linq hiệu quả hơn so với nó xuất hiện trên bề mặt?


13

Nếu tôi viết một cái gì đó như thế này:

var things = mythings
    .Where(x => x.IsSomeValue)
    .Where(y => y.IsSomeOtherValue)

Đây có giống như:

var results1 = new List<Thing>();
foreach(var t in mythings)
    if(t.IsSomeValue)
        results1.Add(t);

var results2 = new List<Thing>();
foreach(var t in results1)
    if(t.IsSomeOtherValue)
        results2.Add(t);

Hoặc có một số phép thuật dưới vỏ bọc hoạt động giống như thế này:

var results = new List<Thing>();
foreach(var t in mythings)
    if(t.IsSomeValue && t.IsSomeOtherValue)
        results.Add(t);

Hay nó là một cái gì đó hoàn toàn khác nhau hoàn toàn?


4
Bạn có thể xem điều này trong ILSpy.
ChaosPandion

1
Nó giống như ví dụ thứ hai hơn là câu trả lời đầu tiên nhưng thứ hai của ChaosPandion rằng ILSpy là bạn của bạn.
Michael

Câu trả lời:


27

Truy vấn LINQ là lười biếng . Điều đó có nghĩa là mã:

var things = mythings
    .Where(x => x.IsSomeValue)
    .Where(y => y.IsSomeOtherValue);

làm rất ít Số liệt kê gốc ( mythings) chỉ được liệt kê khi liệt kê kết quả ( things) được tiêu thụ, ví dụ: bởi một foreachvòng lặp .ToList(), hoặc .ToArray().

Nếu bạn gọi things.ToList(), nó gần tương đương với mã sau của bạn, có lẽ một số chi phí (thường không đáng kể) từ các điều tra viên.

Tương tự như vậy, nếu bạn sử dụng vòng lặp foreach:

foreach (var t in things)
    DoSomething(t);

Nó tương tự như hiệu suất:

foreach (var t in mythings)
    if (t.IsSomeValue && t.IsSomeOtherValue)
        DoSomething(t);

Một số lợi thế về hiệu suất của phương pháp lười biếng đối với vô số (trái ngược với việc tính toán tất cả các kết quả và lưu trữ chúng trong danh sách) là nó sử dụng rất ít bộ nhớ (vì chỉ có một kết quả được lưu trữ tại một thời điểm) và không có gì đáng kể chi phí mặt tiền.

Nếu liệt kê chỉ được liệt kê một phần, điều này đặc biệt quan trọng. Xem xét mã này:

things.First();

Cách LINQ được triển khai, mythingssẽ chỉ được liệt kê cho đến phần tử đầu tiên phù hợp với điều kiện của bạn. Nếu yếu tố đó xuất hiện sớm trong danh sách, đây có thể là một sự tăng hiệu suất rất lớn (ví dụ O (1) thay vì O (n)).


1
Một điểm khác biệt về hiệu năng giữa LINQ và mã tương đương sử dụng foreachlà LINQ sử dụng các lệnh ủy nhiệm, có một số chi phí. Điều này có thể có ý nghĩa khi các điều kiện thực thi rất nhanh (điều mà chúng thường làm).
Svick

2
Đó là những gì tôi muốn nói bởi điều tra viên. Nó có thể là một vấn đề trong một số trường hợp (hiếm), nhưng theo kinh nghiệm của tôi không thường xuyên - thường thì thời gian bắt đầu rất nhỏ hoặc vượt xa các hoạt động khác mà bạn đang thực hiện.
Cá Cyanfish

Một hạn chế khó chịu trong đánh giá lười biếng của Linq là không có cách nào để "chụp nhanh" bảng liệt kê ngoại trừ thông qua các phương thức như ToListhoặc ToArray. Nếu một thứ như vậy đã được tích hợp đúng cách IEnumerable, có thể yêu cầu một danh sách "chụp nhanh" bất kỳ khía cạnh nào có thể thay đổi trong tương lai mà không phải tạo ra mọi thứ.
supercat

7

Các mã sau đây:

var things = mythings
    .Where(x => x.IsSomeValue)
    .Where(y => y.IsSomeOtherValue);

Tương đương với không có gì, vì đánh giá lười biếng, sẽ không có gì xảy ra.

var things = mythings
    .Where(x => x.IsSomeValue)
    .Where(y => y.IsSomeOtherValue)
    .ToList();

Là khác nhau, bởi vì đánh giá sẽ được đưa ra.

Mỗi mục mythingssẽ được trao cho người đầu tiên Where. Nếu nó vượt qua, nó sẽ được trao cho thứ hai Where. Nếu nó vượt qua, nó sẽ là một phần của đầu ra.

Vì vậy, nó trông giống như thế này hơn:

var results = new List<Thing>();
foreach(var t in mythings)
{
    if(t.IsSomeValue)
    {
        if(t.IsSomeOtherValue)
        {
            results.Add(t);
        }
    }
}

7

Trì hoãn thực hiện sang một bên (mà các câu trả lời khác đã giải thích, tôi sẽ chỉ nêu một chi tiết khác), nó giống như trong ví dụ thứ hai của bạn.

Hãy chỉ tưởng tượng bạn gọi ToListvào things.

Việc thực hiện Enumerable.Wheretrả về a Enumerable.WhereListIterator. Khi bạn gọi Wherevào đó WhereListIterator(còn gọi là chuỗi Where-calls), bạn không còn gọi nữa Enumerable.Where, nhưng Enumerable.WhereListIterator.Where, thực sự kết hợp các vị từ (sử dụng Enumerable.CombinePredicates).

Vì vậy, nó giống như nhiều hơn if(t.IsSomeValue && t.IsSomeOtherValue).


"trả về một Enumerable.WhereListIterator" làm cho nó nhấp cho tôi. Có lẽ là một khái niệm rất đơn giản, nhưng đó là những gì tôi đã xem với ILSpy. Cảm ơn
ConditionRacer

Xem việc thực hiện lại tối ưu hóa này của Jon Skeet nếu bạn quan tâm đến phân tích chuyên sâu hơn.
Phục vụ

1

Không, nó không giống nhau. Trong ví dụ của bạn thingslà một IEnumerable, mà tại thời điểm này vẫn chỉ là một trình vòng lặp, không phải là một mảng hoặc danh sách thực tế. Hơn nữa vì thingskhông được sử dụng, vòng lặp thậm chí không bao giờ được đánh giá. Kiểu IEnumerablecho phép lặp qua các phần tử yieldđược tạo bởi các hướng dẫn Linq và xử lý chúng thêm với các hướng dẫn khác, điều đó có nghĩa là cuối cùng bạn thực sự chỉ có một vòng lặp.

Nhưng ngay sau khi bạn thêm một hướng dẫn như .ToArray()hoặc .ToList(), bạn đang ra lệnh tạo cấu trúc dữ liệu thực tế, do đó đặt giới hạn cho chuỗi của bạn.

Xem câu hỏi SO có liên quan này: /programming/2789389/how-do-i-im vây-ienumerable

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.