Thứ tự của các hàm LINQ có quan trọng không?


114

Về cơ bản, như câu hỏi đã nêu ... thứ tự của các hàm LINQ có quan trọng về mặt hiệu suất không? Rõ ràng là kết quả sẽ phải giống hệt nhau vẫn ...

Thí dụ:

myCollection.OrderBy(item => item.CreatedDate).Where(item => item.Code > 3);
myCollection.Where(item => item.Code > 3).OrderBy(item => item.CreatedDate);

Cả hai đều trả về cho tôi kết quả giống nhau, nhưng theo thứ tự LINQ khác. Tôi nhận ra rằng việc sắp xếp lại một số mục sẽ dẫn đến các kết quả khác nhau và tôi không lo lắng về những điều đó. Mối quan tâm chính của tôi là biết liệu, để có được kết quả tương tự, việc đặt hàng có thể ảnh hưởng đến hiệu suất hay không. Và, không chỉ trên 2 cuộc gọi LINQ mà tôi đã thực hiện (OrderBy, Where), mà trên bất kỳ cuộc gọi LINQ nào.


9
Câu hỏi tuyệt vời.
Robert S.

Rõ ràng hơn là vấn đề tối ưu hóa của nhà cung cấp với một trường hợp phức tạp hơn như var query = myCollection.OrderBy(item => item.Code).Where(item => item.Code == 3);.
Mark Hurd

1
Bạn xứng đáng được bình chọn lên :), câu hỏi thú vị. Tôi sẽ cân nhắc nó khi tôi viết Linq của mình cho các Thực thể trong EF.
GibboK

1
@GibboK: Hãy cẩn thận khi cố gắng "tối ưu hóa" các truy vấn LINQ của bạn (xem câu trả lời bên dưới). Đôi khi bạn không thực sự tối ưu hóa bất cứ điều gì. Tốt nhất bạn nên sử dụng công cụ tạo hồ sơ khi cố gắng tối ưu hóa.
myermian 23/10/12

Câu trả lời:


147

Nó sẽ phụ thuộc vào nhà cung cấp LINQ đang sử dụng. Đối với LINQ to Object, điều đó chắc chắn có thể tạo ra sự khác biệt rất lớn . Giả sử chúng ta thực sự có:

var query = myCollection.OrderBy(item => item.CreatedDate)
                        .Where(item => item.Code > 3);

var result = query.Last();

Điều đó yêu cầu toàn bộ bộ sưu tập phải được sắp xếp và sau đó lọc. Nếu chúng tôi có một triệu mục, chỉ một trong số đó có mã lớn hơn 3, chúng tôi sẽ lãng phí rất nhiều thời gian để sắp xếp các kết quả sẽ bị loại bỏ.

So sánh điều đó với thao tác đã đảo ngược, lọc trước:

var query = myCollection.Where(item => item.Code > 3)
                        .OrderBy(item => item.CreatedDate);

var result = query.Last();

Lần này chúng tôi chỉ sắp xếp các kết quả được lọc, trong trường hợp mẫu là "chỉ một mục duy nhất phù hợp với bộ lọc" sẽ hiệu quả hơn rất nhiều - cả về thời gian và không gian.

Nó cũng có thể tạo ra sự khác biệt trong việc truy vấn có thực thi đúng hay không. Xem xét:

var query = myCollection.Where(item => item.Code != 0)
                        .OrderBy(item => 10 / item.Code);

var result = query.Last();

Điều đó tốt - chúng tôi biết chúng tôi sẽ không bao giờ chia cho 0. Nhưng nếu chúng tôi thực hiện thứ tự trước khi lọc, truy vấn sẽ ném ra một ngoại lệ.


2
@Jon Skeet, Có tài liệu về Big-O cho từng Nhà cung cấp và chức năng của LINQ không? Hay đây chỉ đơn thuần là một trường hợp "mỗi biểu hiện là duy nhất cho tình huống".
michael

1
@michael: Nó không được ghi chép rõ ràng lắm, nhưng nếu bạn đọc loạt blog "Edulinq" của tôi, tôi nghĩ tôi nói về nó một cách chi tiết hợp lý.
Jon Skeet

3
@ Michael: bạn có thể tìm thấy nó ở đây msmvps.com/blogs/jon_skeet/archive/tags/Edulinq/default.aspx
VoodooChild

3
@gdoron: Thành thật mà nói thì không rõ ý bạn là gì. Có vẻ như bạn có thể muốn viết một câu hỏi mới. Hãy nhớ rằng Queryable không cố gắng diễn giải truy vấn của bạn - công việc của nó chỉduy trì truy vấn của bạn để thứ khác có thể diễn giải nó. Cũng lưu ý rằng LINQ to Objects thậm chí không sử dụng cây biểu thức.
Jon Skeet

1
@gdoron: Vấn đề là đó là công việc của nhà cung cấp, không phải công việc của Queryable. Và cũng không thành vấn đề khi sử dụng Entity Framework. Tuy nhiên, nó không quan trọng đối với LINQ đối với Đối tượng. Nhưng có, bằng mọi cách hãy hỏi một câu hỏi khác.
Jon Skeet

17

Đúng.

Nhưng chính xác những gì mà khác biệt hiệu suất là phụ thuộc vào cách cây biểu hiện cơ bản được đánh giá bởi các nhà cung cấp LINQ.

Ví dụ: truy vấn của bạn có thể thực thi nhanh hơn ở lần thứ hai (với mệnh đề WHERE trước) đối với LINQ-to-XML, nhưng nhanh hơn ở lần đầu tiên đối với LINQ-to-SQL.

Để biết chính xác sự khác biệt về hiệu suất là gì, rất có thể bạn sẽ muốn lập hồ sơ ứng dụng của mình. Tuy nhiên, đối với những thứ như vậy, việc tối ưu hóa quá sớm thường không đáng để nỗ lực - bạn có thể thấy các vấn đề khác ngoài hiệu suất LINQ quan trọng hơn.


5

Trong ví dụ cụ thể của bạn, nó có thể tạo ra sự khác biệt cho hiệu suất.

Truy vấn đầu tiên: Cuộc OrderBygọi của bạn cần lặp lại toàn bộ chuỗi nguồn, bao gồm cả những mục có từ Code3 trở xuống. Sau Wheređó mệnh đề cũng cần phải lặp lại toàn bộ chuỗi có thứ tự.

Truy vấn thứ hai: Cuộc Wheregọi giới hạn chuỗi chỉ những mục có giá trị Codelớn hơn 3. Sau OrderByđó, cuộc gọi chỉ cần duyệt qua chuỗi giảm được trả về bởi Wherecuộc gọi.


3

Trong Linq-To-Objects:

Sắp xếp khá chậm và sử dụng O(n)bộ nhớ. Wheremặt khác là tương đối nhanh và sử dụng bộ nhớ không đổi. Vì vậy, thực hiện Wheretrước sẽ nhanh hơn và đối với các bộ sưu tập lớn nhanh hơn đáng kể.

Theo kinh nghiệm của tôi, áp lực bộ nhớ giảm cũng có thể đáng kể, vì phân bổ trên đống đối tượng lớn (cùng với bộ sưu tập của chúng) tương đối đắt theo kinh nghiệm của tôi.


1

Rõ ràng là kết quả sẽ phải giống hệt nhau vẫn ...

Lưu ý rằng điều này không thực sự đúng - cụ thể là hai dòng sau sẽ cho kết quả khác nhau (đối với hầu hết các nhà cung cấp / bộ dữ liệu):

myCollection.OrderBy(o => o).Distinct();
myCollection.Distinct().OrderBy(o => o);

1
Không, ý tôi là kết quả phải giống nhau để thậm chí xem xét tối ưu hóa. Không có ích gì khi "tối ưu hóa" một thứ gì đó và nhận được một kết quả khác.
michael

1

Cần lưu ý rằng bạn nên cẩn thận khi xem xét cách tối ưu hóa truy vấn LINQ. Ví dụ: nếu bạn sử dụng phiên bản khai báo của LINQ để làm như sau:

public class Record
{
    public string Name { get; set; }
    public double Score1 { get; set; }
    public double Score2 { get; set; }
}


var query = from record in Records
            order by ((record.Score1 + record.Score2) / 2) descending
            select new
                   {
                       Name = record.Name,
                       Average = ((record.Score1 + record.Score2) / 2)
                   };

Nếu vì bất kỳ lý do gì, bạn quyết định "tối ưu hóa" truy vấn bằng cách lưu giá trị trung bình vào một biến trước, bạn sẽ không nhận được kết quả mong muốn:

// The following two queries actually takes up more space and are slower
var query = from record in Records
            let average = ((record.Score1 + record.Score2) / 2)
            order by average descending
            select new
                   {
                       Name = record.Name,
                       Average = average
                   };

var query = from record in Records
            let average = ((record.Score1 + record.Score2) / 2)
            select new
                   {
                       Name = record.Name,
                       Average = average
                   }
            order by average descending;

Tôi biết không nhiều người sử dụng LINQ khai báo cho các đối tượng, nhưng nó là một số thức ăn tốt cho suy nghĩ.


0

Nó phụ thuộc vào mức độ liên quan. Giả sử nếu bạn có rất ít mặt hàng có Mã = 3, thì đơn hàng tiếp theo sẽ thực hiện trên bộ sưu tập nhỏ để lấy đơn hàng theo ngày.

Trong khi nếu bạn có nhiều mặt hàng có cùng Ngày tạo, thì đơn đặt hàng tiếp theo sẽ thực hiện trên tập hợp bộ sưu tập lớn hơn để nhận đơn hàng theo ngày.

Vì vậy, trong cả hai trường hợp sẽ có sự khác biệt về hiệu suất

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.