Tại sao chọn ở đâu và chọn tốt hơn chỉ chọn?


145

Tôi có một lớp học, như thế này:

public class MyClass
{
    public int Value { get; set; }
    public bool IsValid { get; set; }
}

Trong thực tế, nó lớn hơn nhiều, nhưng điều này tái tạo vấn đề (sự kỳ lạ).

Tôi muốn lấy tổng của Value, trong đó cá thể hợp lệ. Cho đến nay, tôi đã tìm thấy hai giải pháp cho việc này.

Cái đầu tiên là đây:

int result = myCollection.Where(mc => mc.IsValid).Select(mc => mc.Value).Sum();

Tuy nhiên, cái thứ hai là:

int result = myCollection.Select(mc => mc.IsValid ? mc.Value : 0).Sum();

Tôi muốn có được phương pháp hiệu quả nhất. Tôi, lúc đầu, nghĩ rằng cái thứ hai sẽ hiệu quả hơn. Sau đó, phần lý thuyết của tôi bắt đầu "Chà, một là O (n + m + m), phần còn lại là O (n + n). Phần đầu tiên sẽ hoạt động tốt hơn với nhiều thương binh hơn, trong khi phần thứ hai sẽ hoạt động tốt hơn với số ít". Tôi nghĩ rằng họ sẽ thực hiện như nhau. EDIT: Và sau đó @Martin đã chỉ ra rằng Vị trí và Chọn được kết hợp với nhau, vì vậy nó thực sự phải là O (m + n). Tuy nhiên, nếu bạn nhìn bên dưới, có vẻ như điều này không liên quan.


Vì vậy, tôi đưa nó vào thử nghiệm.

(Đó là hơn 100 dòng, vì vậy tôi nghĩ tốt hơn nên đăng nó dưới dạng Gist.)
Kết quả thật ... thú vị.

Với sai số ràng buộc 0%:

Các thang đo có lợi cho SelectWhere, khoảng ~ 30 điểm.

How much do you want to be the disambiguation percentage?
0
Starting benchmarking.
Ties: 0
Where + Select: 65
Select: 36

Với dung sai cà vạt 2%:

Điều này cũng tương tự, ngoại trừ một số người trong phạm vi 2%. Tôi muốn nói rằng đó là một lỗi tối thiểu. SelectWherebây giờ chỉ có một dẫn ~ 20 điểm.

How much do you want to be the disambiguation percentage?
2
Starting benchmarking.
Ties: 6
Where + Select: 58
Select: 37

Với dung sai cà vạt 5%:

Đây là những gì tôi muốn nói là tỷ lệ lỗi tối đa của tôi. Nó làm cho nó tốt hơn một chút cho Select, nhưng không nhiều.

How much do you want to be the disambiguation percentage?
5
Starting benchmarking.
Ties: 17
Where + Select: 53
Select: 31

Với dung sai cà vạt 10%:

Đây là cách thoát khỏi lỗi của tôi, nhưng tôi vẫn quan tâm đến kết quả. Bởi vì nó mang lại cho SelectWherehai mươi điểm dẫn đầu mà nó đã có được một thời gian.

How much do you want to be the disambiguation percentage?
10
Starting benchmarking.
Ties: 36
Where + Select: 44
Select: 21

Với dung sai cà vạt 25%:

Đây là bằng cách nào, cách ra khỏi lề của tôi về lỗi, nhưng tôi vẫn đang quan tâm đến kết quả, bởi vì SelectWhere vẫn (gần) giữ dẫn 20 điểm của họ. Có vẻ như nó vượt xa nó trong một số ít khác biệt, và đó là điều khiến nó dẫn đầu.

How much do you want to be the disambiguation percentage?
25
Starting benchmarking.
Ties: 85
Where + Select: 16
Select: 0


Bây giờ, tôi đoán rằng vai 20 điểm đến từ giữa, nơi họ đang cả ràng buộc để có được xung quanh việc thực hiện tương tự. Tôi có thể thử và đăng nhập nó, nhưng nó sẽ là toàn bộ thông tin cần đưa vào. Một đồ thị sẽ tốt hơn, tôi đoán vậy.

Vì vậy, đó là những gì tôi đã làm.

Chọn vs Chọn và ở đâu.

Nó cho thấy rằng Selectdòng giữ ổn định (dự kiến) và Select + Wheredòng tăng lên (dự kiến). Tuy nhiên, điều khiến tôi băn khoăn là tại sao nó không đáp ứng Selectở mức 50 hoặc sớm hơn: thực tế tôi đã mong đợi sớm hơn 50, vì một điều tra viên phụ phải được tạo ra cho SelectWhere. Ý tôi là, điều này cho thấy sự dẫn đầu 20 điểm, nhưng nó không giải thích tại sao. Điều này, tôi đoán, là điểm chính của câu hỏi của tôi.

Tại sao nó lại hành xử như vậy? Tôi có nên tin tưởng nó? Nếu không, tôi nên sử dụng cái khác hay cái này?


Như @KingKong đã đề cập trong các bình luận, bạn cũng có thể sử dụng Sumtình trạng quá tải mất lambda. Vì vậy, hai tùy chọn của tôi bây giờ được thay đổi thành này:

Đầu tiên:

int result = myCollection.Where(mc => mc.IsValid).Sum(mc => mc.Value);

Thứ hai:

int result = myCollection.Sum(mc => mc.IsValid ? mc.Value : 0);

Tôi sẽ làm cho nó ngắn hơn một chút, nhưng:

How much do you want to be the disambiguation percentage?
0
Starting benchmarking.
Ties: 0
Where: 60
Sum: 41
How much do you want to be the disambiguation percentage?
2
Starting benchmarking.
Ties: 8
Where: 55
Sum: 38
How much do you want to be the disambiguation percentage?
5
Starting benchmarking.
Ties: 21
Where: 49
Sum: 31
How much do you want to be the disambiguation percentage?
10
Starting benchmarking.
Ties: 39
Where: 41
Sum: 21
How much do you want to be the disambiguation percentage?
25
Starting benchmarking.
Ties: 85
Where: 16
Sum: 0

Dẫn đầu hai mươi điểm vẫn còn đó, có nghĩa là nó không liên quan đến WhereSelectsự kết hợp được chỉ ra bởi @Marcin trong các bình luận.

Cảm ơn đã đọc qua tường văn bản của tôi! Ngoài ra, nếu bạn quan tâm, đây là phiên bản sửa đổi ghi nhật ký CSV mà Excel thực hiện.


1
Tôi muốn nói rằng nó phụ thuộc vào mức độ đắt đỏ và quyền truy cập mc.Value.
Medinoc

14
@ ItNotALie. Where+ Selectkhông gây ra hai lần lặp tách biệt trên bộ sưu tập đầu vào. LINQ to Object tối ưu hóa nó thành một lần lặp. Đọc thêm về bài đăng trên blog
MarcinJuraszek

4
Hấp dẫn. Hãy để tôi chỉ ra rằng một vòng lặp for trên một mảng sẽ nhanh hơn gấp 10 lần so với giải pháp LINQ tốt nhất. Vì vậy, nếu bạn đi săn tìm sự hoàn hảo, đừng sử dụng LINQ ngay từ đầu.
usr

2
Đôi khi mọi người hỏi sau khi nghiên cứu thực sự, đây là một câu hỏi ví dụ: Tôi không phải là người dùng C # đến từ danh sách câu hỏi nóng.
Grijesh Chauhan

2
@WiSaGaN Đó là một điểm tốt. Tuy nhiên, nếu điều này là do di chuyển chi nhánh và có điều kiện, chúng tôi hy vọng sẽ thấy sự khác biệt lớn nhất ở mức 50% / 50%. Ở đây, chúng ta thấy sự khác biệt lớn nhất ở cuối, nơi phân nhánh là dễ dự đoán nhất. Nếu Trường hợp là một nhánh và chim nhạn là một động thái có điều kiện, thì chúng ta sẽ mong đợi Thời gian quay trở lại khi tất cả các yếu tố hợp lệ, nhưng nó không bao giờ quay trở lại.
John Tseng

Câu trả lời:


131

SelectLặp lại một lần trên toàn bộ tập hợp và, đối với mỗi mục, thực hiện một nhánh có điều kiện (kiểm tra tính hợp lệ) và +thao tác.

Where+Selecttạo một trình vòng lặp bỏ qua các phần tử không hợp lệ (không phải yieldchúng), chỉ thực hiện +trên các mục hợp lệ.

Vì vậy, chi phí cho một Selectlà:

t(s) = n * ( cost(check valid) + cost(+) )

Và cho Where+Select:

t(ws) = n * ( cost(check valid) + p(valid) * (cost(yield) + cost(+)) )

Ở đâu:

  • p(valid) là xác suất để một mục trong danh sách là hợp lệ.
  • cost(check valid) là chi phí của chi nhánh kiểm tra tính hợp lệ
  • cost(yield)là chi phí xây dựng trạng thái mới của wheretrình vòng lặp, phức tạp hơn trình lặp đơn giản mà Selectphiên bản sử dụng.

Như bạn có thể thấy, đối với một phiên bản nhất định n, Selectphiên bản là một hằng số, trong khi Where+Selectphiên bản là một phương trình tuyến tính với p(valid)một biến. Các giá trị thực tế của chi phí xác định điểm giao nhau của hai đường và vì cost(yield)có thể khác nhau cost(+), nên chúng không nhất thiết phải giao nhau tại p(valid)= 0,5.


34
+1 vì là câu trả lời duy nhất (cho đến nay) thực sự giải quyết câu hỏi, không đoán được câu trả lời và không chỉ tạo ra "tôi nữa!" số liệu thống kê.
Nhị phân nhị phân

4
Về mặt kỹ thuật, các phương thức LINQ tạo các cây biểu thức được chạy trên toàn bộ bộ sưu tập một lần thay vì "bộ".
Spoike

Có gì cost(append)? Câu trả lời thực sự tốt mặc dù, nhìn nó từ một góc độ khác chứ không chỉ là số liệu thống kê.
Đó là NÓI.

5
Wherekhông tạo ra bất cứ thứ gì, chỉ trả về một phần tử tại thời điểm từ sourcechuỗi nếu chỉ nó điền vào vị ngữ.
MarcinJuraszek

13
@Spoike - Cây biểu thức không liên quan ở đây, vì đây là linq-to-object , không phải linq-to- Something-other (Ví dụ: Thực thể). Đó là sự khác biệt giữa IEnumerable.Select(IEnumerable, Func)IQueryable.Select(IQueryable, Expression<Func>). Bạn đúng rằng LINQ không "làm gì" cho đến khi bạn lặp lại bộ sưu tập, đó có lẽ là điều bạn muốn nói.
Kobi

33

Đây là một lời giải thích sâu sắc về những gì gây ra sự khác biệt về thời gian.


Các Sum()chức năng cho IEnumerable<int>vẻ bề ngoài như thế này:

public static int Sum(this IEnumerable<int> source)
{
    int sum = 0;
    foreach(int item in source)
    {
        sum += item;
    }
    return sum;
}

Trong C #, foreachchỉ là đường cú pháp cho phiên bản .Net của một trình vòng lặp, (đừng nhầm lẫn với ) . Vì vậy, đoạn mã trên thực sự được dịch sang đây:IEnumerator<T> IEnumerable<T>

public static int Sum(this IEnumerable<int> source)
{
    int sum = 0;

    IEnumerator<int> iterator = source.GetEnumerator();
    while(iterator.MoveNext())
    {
        int item = iterator.Current;
        sum += item;
    }
    return sum;
}

Hãy nhớ rằng, hai dòng mã bạn đang so sánh là như sau

int result1 = myCollection.Where(mc => mc.IsValid).Sum(mc => mc.Value);
int result2 = myCollection.Sum(mc => mc.IsValid ? mc.Value : 0);

Bây giờ đây là kicker:

LINQ sử dụng thực thi hoãn lại . Như vậy, trong khi nó có thể xuất hiệnresult1lặp trên bộ sưu tập hai lần, nó thực sự chỉ lặp qua nó một lần. Điều Where()kiện này thực sự được áp dụng trong Sum(), bên trong lời kêu gọi MoveNext() (Điều này có thể nhờ vào phép thuật của yield return) .

Điều này có nghĩa là, cho result1, mã bên trong whilevòng lặp,

{
    int item = iterator.Current;
    sum += item;
}

chỉ được thực hiện một lần cho mỗi mục với mc.IsValid == true. Khi so sánh, result2sẽ thực thi mã đó cho mọi mục trong bộ sưu tập. Đó là lý do tại sao result1nói chung là nhanh hơn.

(Mặc dù, lưu ý rằng việc gọi Where()điều kiện bên trong MoveNext()vẫn có một số chi phí nhỏ, vì vậy nếu hầu hết / tất cả các mục có mc.IsValid == true, result2thực sự sẽ nhanh hơn!)


Hy vọng bây giờ rõ ràng tại sao result2thường chậm hơn. Bây giờ tôi muốn giải thích lý do tại sao tôi nói trong các ý kiến ​​rằng những so sánh hiệu suất LINQ này không quan trọng .

Tạo một biểu thức LINQ là rẻ. Gọi chức năng đại biểu là giá rẻ. Phân bổ và lặp qua một iterator là rẻ. Nhưng nó thậm chí còn rẻ hơn để không làm những điều này. Do đó, nếu bạn thấy rằng một câu lệnh LINQ là nút cổ chai trong chương trình của bạn, theo kinh nghiệm của tôi, việc viết lại nó mà không có LINQ sẽ luôn làm cho nó nhanh hơn bất kỳ phương thức LINQ nào khác.

Vì vậy, quy trình làm việc LINQ của bạn sẽ như thế này:

  1. Sử dụng LINQ ở mọi nơi.
  2. Hồ sơ.
  3. Nếu trình hồ sơ cho biết LINQ là nguyên nhân gây ra tắc nghẽn, hãy viết lại đoạn mã đó mà không có LINQ.

May mắn thay, tắc nghẽn LINQ là rất hiếm. Heck, tắc nghẽn là rất hiếm. Tôi đã viết hàng trăm báo cáo LINQ trong vài năm qua và cuối cùng đã thay thế <1%. Và hầu hết trong số đó là do tối ưu hóa SQL kém của LINQ2EF , thay vì là lỗi của LINQ.

Vì vậy, như mọi khi, trước tiên hãy viết mã rõ ràng và hợp lý và đợi cho đến khi bạn định cấu hình để lo lắng về tối ưu hóa vi mô.


3
Phụ lục nhỏ: câu trả lời hàng đầu đã được sửa.
Đó là NÓI.

16

Điều buồn cười. Bạn có biết làm thế nào được Sum(this IEnumerable<TSource> source, Func<TSource, int> selector)định nghĩa? Nó sử dụng Selectphương pháp!

public static int Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, int> selector)
{
    return source.Select(selector).Sum();
}

Vì vậy, thực sự, tất cả nên hoạt động gần như nhau. Tôi đã tự mình nghiên cứu nhanh chóng và đây là kết quả:

Where -- mod: 1 result: 0, time: 371 ms
WhereSelect -- mod: 1  result: 0, time: 356 ms
Select -- mod: 1  result 0, time: 366 ms
Sum -- mod: 1  result: 0, time: 363 ms
-------------
Where -- mod: 2 result: 4999999, time: 469 ms
WhereSelect -- mod: 2  result: 4999999, time: 429 ms
Select -- mod: 2  result 4999999, time: 362 ms
Sum -- mod: 2  result: 4999999, time: 358 ms
-------------
Where -- mod: 3 result: 9999999, time: 441 ms
WhereSelect -- mod: 3  result: 9999999, time: 452 ms
Select -- mod: 3  result 9999999, time: 371 ms
Sum -- mod: 3  result: 9999999, time: 380 ms
-------------
Where -- mod: 4 result: 7500000, time: 571 ms
WhereSelect -- mod: 4  result: 7500000, time: 501 ms
Select -- mod: 4  result 7500000, time: 406 ms
Sum -- mod: 4  result: 7500000, time: 397 ms
-------------
Where -- mod: 5 result: 7999999, time: 490 ms
WhereSelect -- mod: 5  result: 7999999, time: 477 ms
Select -- mod: 5  result 7999999, time: 397 ms
Sum -- mod: 5  result: 7999999, time: 394 ms
-------------
Where -- mod: 6 result: 9999999, time: 488 ms
WhereSelect -- mod: 6  result: 9999999, time: 480 ms
Select -- mod: 6  result 9999999, time: 391 ms
Sum -- mod: 6  result: 9999999, time: 387 ms
-------------
Where -- mod: 7 result: 8571428, time: 489 ms
WhereSelect -- mod: 7  result: 8571428, time: 486 ms
Select -- mod: 7  result 8571428, time: 384 ms
Sum -- mod: 7  result: 8571428, time: 381 ms
-------------
Where -- mod: 8 result: 8749999, time: 494 ms
WhereSelect -- mod: 8  result: 8749999, time: 488 ms
Select -- mod: 8  result 8749999, time: 386 ms
Sum -- mod: 8  result: 8749999, time: 373 ms
-------------
Where -- mod: 9 result: 9999999, time: 497 ms
WhereSelect -- mod: 9  result: 9999999, time: 494 ms
Select -- mod: 9  result 9999999, time: 386 ms
Sum -- mod: 9  result: 9999999, time: 371 ms

Đối với các triển khai sau:

result = source.Where(x => x.IsValid).Sum(x => x.Value);
result = source.Select(x => x.IsValid ? x.Value : 0).Sum();
result = source.Sum(x => x.IsValid ? x.Value : 0);
result = source.Where(x => x.IsValid).Select(x => x.Value).Sum();

modcó nghĩa là: mỗi 1 từ modcác mục không hợp lệ: cho mod == 1mỗi mục không hợp lệ, cho mod == 2các mục lẻ không hợp lệ, v.v. Bộ sưu tập chứa 10000000các mục.

nhập mô tả hình ảnh ở đây

Và kết quả cho bộ sưu tập với 100000000các mục:

nhập mô tả hình ảnh ở đây

Như bạn có thể thấy, SelectSumkết quả khá nhất quán trên tất cả các modgiá trị. Tuy nhiên wherewhere+ selectthì không.


1
Điều rất thú vị là trong kết quả của bạn, tất cả các phương thức đều bắt đầu ở cùng một vị trí và phân kỳ, trong khi kết quả của ItsNotALie nằm ở giữa.
John Tseng

6

Tôi đoán là phiên bản có Where lọc ra 0 và chúng không phải là chủ đề cho Sum (tức là bạn không thực hiện bổ sung). Tất nhiên đây chỉ là phỏng đoán vì tôi không thể giải thích cách thực hiện biểu thức lambda bổ sung và gọi nhiều phương thức vượt trội hơn so với phép cộng 0 đơn giản.

Một người bạn của tôi đề nghị rằng thực tế là 0 trong tổng có thể gây ra hình phạt hiệu suất nghiêm trọng vì kiểm tra tràn. Sẽ rất thú vị khi xem điều này sẽ thực hiện như thế nào trong bối cảnh không được kiểm soát.


Một số thử nghiệm uncheckedlàm cho nó một chút, nhỏ hơn một chút tốt hơn cho Select.
Đó là NÓI.

Ai đó có thể nói nếu không được kiểm tra ảnh hưởng đến các phương thức được gọi xuống ngăn xếp hoặc chỉ các hoạt động cấp cao nhất không?
Stilgar

1
@Stilgar Nó chỉ áp dụng cho cấp cao nhất.
Branko Dimitrijevic

Vì vậy, có lẽ chúng ta cần triển khai Sum không được kiểm tra và thử theo cách này.
Stilgar

5

Chạy mẫu sau, tôi thấy rõ rằng lần duy nhất + Chọn có thể vượt trội hơn Chọn thực tế là khi nó loại bỏ một lượng tốt (khoảng một nửa trong các thử nghiệm không chính thức của tôi) về các mặt hàng tiềm năng trong danh sách. Trong ví dụ nhỏ dưới đây, tôi nhận được các số gần giống nhau trong cả hai mẫu khi Trường hợp bỏ qua khoảng 4 triệu mục trong số 10mil. Tôi đã chạy trong bản phát hành và sắp xếp lại việc thực hiện where + select vs select với cùng kết quả.

static void Main(string[] args)
        {
            int total = 10000000;
            Random r = new Random();
            var list = Enumerable.Range(0, total).Select(i => r.Next(0, 5)).ToList();
            for (int i = 0; i < 4000000; i++)
                list[i] = 10;

            var sw = new Stopwatch();
            sw.Start();

            int sum = 0;

            sum = list.Where(i => i < 10).Select(i => i).Sum();            

            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);

            sw.Reset();
            sw.Start();
            sum = list.Select(i => i).Sum();            

            sw.Stop();

            Console.WriteLine(sw.ElapsedMilliseconds);
        }

Có thể đó không phải là vì bạn không loại bỏ hàng chục người dưới Select?
Đó là NÓI.

3
Chạy trong gỡ lỗi là vô ích.
MarcinJuraszek

1
@MarcinJuraszek Rõ ràng. Thực sự muốn nói rằng tôi đã chạy trong bản phát hành :)
DavidN

@ ItsNotALie Đó là điểm chính. Dường như với tôi, cách duy nhất ở đâu + Chọn có thể vượt trội hơn Chọn là khi Nơi đang lọc ra một lượng lớn các mục được tóm tắt.
DavidN

2
Về cơ bản đó là những gì câu hỏi của tôi nêu. Họ buộc khoảng 60%, giống như mẫu này. Câu hỏi là tại sao, mà không được trả lời ở đây.
Đó là NÓI.

4

Nếu bạn cần tốc độ, chỉ cần thực hiện một vòng lặp đơn giản có lẽ là đặt cược tốt nhất của bạn. Và làm forcó xu hướng tốt hơn foreach(giả sử bộ sưu tập của bạn là truy cập ngẫu nhiên).

Dưới đây là thời gian tôi nhận được với 10% yếu tố không hợp lệ:

Where + Select + Sum:   257
Select + Sum:           253
foreach:                111
for:                    61

Và với 90% yếu tố không hợp lệ:

Where + Select + Sum:   177
Select + Sum:           247
foreach:                105
for:                    58

Và đây là mã điểm chuẩn của tôi ...

public class MyClass {
    public int Value { get; set; }
    public bool IsValid { get; set; }
}

class Program {

    static void Main(string[] args) {

        const int count = 10000000;
        const int percentageInvalid = 90;

        var rnd = new Random();
        var myCollection = new List<MyClass>(count);
        for (int i = 0; i < count; ++i) {
            myCollection.Add(
                new MyClass {
                    Value = rnd.Next(0, 50),
                    IsValid = rnd.Next(0, 100) > percentageInvalid
                }
            );
        }

        var sw = new Stopwatch();
        sw.Restart();
        int result1 = myCollection.Where(mc => mc.IsValid).Select(mc => mc.Value).Sum();
        sw.Stop();
        Console.WriteLine("Where + Select + Sum:\t{0}", sw.ElapsedMilliseconds);

        sw.Restart();
        int result2 = myCollection.Select(mc => mc.IsValid ? mc.Value : 0).Sum();
        sw.Stop();
        Console.WriteLine("Select + Sum:\t\t{0}", sw.ElapsedMilliseconds);
        Debug.Assert(result1 == result2);

        sw.Restart();
        int result3 = 0;
        foreach (var mc in myCollection) {
            if (mc.IsValid)
                result3 += mc.Value;
        }
        sw.Stop();
        Console.WriteLine("foreach:\t\t{0}", sw.ElapsedMilliseconds);
        Debug.Assert(result1 == result3);

        sw.Restart();
        int result4 = 0;
        for (int i = 0; i < myCollection.Count; ++i) {
            var mc = myCollection[i];
            if (mc.IsValid)
                result4 += mc.Value;
        }
        sw.Stop();
        Console.WriteLine("for:\t\t\t{0}", sw.ElapsedMilliseconds);
        Debug.Assert(result1 == result4);

    }

}

BTW, tôi đồng tình với dự đoán của Stilgar : tốc độ tương đối của hai trường hợp của bạn khác nhau tùy thuộc vào tỷ lệ phần trăm của các mục không hợp lệ, đơn giản vì số lượng công việc Sumcần thực hiện khác nhau trong trường hợp "Ở đâu".


1

Thay vì cố gắng giải thích thông qua mô tả, tôi sẽ thực hiện một cách tiếp cận toán học hơn.

Đưa ra mã dưới đây sẽ xấp xỉ những gì LINQ đang thực hiện trong nội bộ, chi phí tương đối như sau:
Chỉ chọn: Nd + Na
Trong đó + Chọn:Nd + Md + Ma

Để tìm ra điểm mà chúng sẽ giao nhau, chúng ta cần làm một đại số nhỏ:
Nd + Md + Ma = Nd + Na => M(d + a) = Na => (M/N) = a/(d+a)

Điều này có nghĩa là để điểm uốn ở mức 50%, chi phí cho một lời mời đại biểu phải gần bằng với chi phí bổ sung. Vì chúng tôi biết rằng điểm uốn thực tế là khoảng 60%, chúng tôi có thể làm việc ngược lại và xác định rằng chi phí của một lời mời đại biểu cho @ ItNotALie thực sự là khoảng 2/3 chi phí bổ sung rất đáng ngạc nhiên, nhưng đó là những gì đáng ngạc nhiên Con số của anh ấy nói.

static void Main(string[] args)
{
    var set = Enumerable.Range(1, 10000000)
                        .Select(i => new MyClass {Value = i, IsValid = i%2 == 0})
                        .ToList();

    Func<MyClass, int> select = i => i.IsValid ? i.Value : 0;
    Console.WriteLine(
        Sum(                        // Cost: N additions
            Select(set, select)));  // Cost: N delegate
    // Total cost: N * (delegate + addition) = Nd + Na

    Func<MyClass, bool> where = i => i.IsValid;
    Func<MyClass, int> wSelect = i => i.Value;
    Console.WriteLine(
        Sum(                        // Cost: M additions
            Select(                 // Cost: M delegate
                Where(set, where),  // Cost: N delegate
                wSelect)));
    // Total cost: N * delegate + M * (delegate + addition) = Nd + Md + Ma
}

// Cost: N delegate calls
static IEnumerable<T> Where<T>(IEnumerable<T> set, Func<T, bool> predicate)
{
    foreach (var mc in set)
    {
        if (predicate(mc))
        {
            yield return mc;
        }
    }
}

// Cost: N delegate calls
static IEnumerable<int> Select<T>(IEnumerable<T> set, Func<T, int> selector)
{
    foreach (var mc in set)
    {
        yield return selector(mc);
    }
}

// Cost: N additions
static int Sum(IEnumerable<int> set)
{
    unchecked
    {
        var sum = 0;
        foreach (var i in set)
        {
            sum += i;
        }

        return sum;
    }
}

0

Tôi nghĩ thật thú vị khi kết quả của MarcinJuraszek khác với ItsNotALie. Cụ thể, kết quả của MarcinJuraszek bắt đầu với tất cả bốn triển khai tại cùng một nơi, trong khi kết quả của ItsNotALie nằm ở giữa. Tôi sẽ giải thích làm thế nào điều này hoạt động từ nguồn.

Chúng ta hãy giả sử rằng có ncác yếu tố tổng, và mcác yếu tố hợp lệ.

Các Sumchức năng là khá đơn giản. Nó chỉ lặp qua trình liệt kê: http://typedescriptor.net/browse/members/367300-System.Linq.Enumerable.Sum(IEnumerable%601)

Để đơn giản, hãy giả sử bộ sưu tập là một danh sách. Cả ChọnWhereSelect sẽ tạo một WhereSelectListIterator. Điều này có nghĩa là các trình vòng lặp thực tế được tạo ra là như nhau. Trong cả hai trường hợp, có một Sumvòng lặp trên một vòng lặp , WhereSelectListIterator. Phần thú vị nhất của iterator là phương thức MoveNext .

Vì các vòng lặp là như nhau, các vòng lặp là như nhau. Sự khác biệt duy nhất là trong cơ thể của các vòng.

Cơ thể của những lambdas có chi phí rất giống nhau. Mệnh đề where trả về một giá trị trường và vị từ ternary cũng trả về một giá trị trường. Mệnh đề select trả về một giá trị trường và hai nhánh của toán tử ternary trả về giá trị trường hoặc hằng số. Mệnh đề chọn kết hợp có nhánh là toán tử ternary, nhưng WhereSelect sử dụng nhánh trong MoveNext.

Tuy nhiên, tất cả các hoạt động này là khá rẻ. Hoạt động tốn kém nhất cho đến nay là chi nhánh, nơi một dự đoán sai sẽ khiến chúng ta phải trả giá.

Một hoạt động đắt tiền ở đây là Invoke. Gọi một hàm mất nhiều thời gian hơn một giá trị, như Branko Dimitrijevic đã chỉ ra.

Cũng cân trong là tích lũy kiểm tra trong Sum. Nếu bộ xử lý không có cờ tràn số học, thì điều này cũng có thể tốn kém để kiểm tra.

Do đó, các chi phí thú vị là: là:

  1. ( n+ m) * Gọi + m*checked+=
  2. n* Gọi + n*checked+=

Như vậy, nếu chi phí của Invoke cao hơn nhiều so với chi phí tích lũy được kiểm tra thì trường hợp 2 luôn tốt hơn. Nếu chúng về chẵn, thì chúng ta sẽ thấy sự cân bằng khi khoảng một nửa các yếu tố hợp lệ.

Có vẻ như trên hệ thống của MarcinJuraszek, đã kiểm tra + = có chi phí không đáng kể, nhưng trên các hệ thống của ItsNotALie và Branko Dimitrijevic, đã kiểm tra + = có chi phí đáng kể. Có vẻ như nó đắt nhất trên hệ thống của ItsNotALie vì điểm hòa vốn cao hơn nhiều. Dường như không có ai đăng kết quả từ một hệ thống mà việc tích lũy tốn kém hơn nhiều so với Gọi.


@ ItNotALie. Tôi không nghĩ ai có kết quả sai. Tôi không thể giải thích một số điều. Tôi đã giả định rằng chi phí của Invoke nhiều hơn so với + =, nhưng có thể hình dung rằng chúng có thể gần hơn nhiều tùy thuộc vào tối ưu hóa phần cứng.
John Tseng
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.