簡體   English   中英

在linq中為什么后續調用IEnumerable.Intersect這么快

[英]in linq why are subsequent calls of IEnumerable.Intersect so much faster

在看這個問題C#兩個數組的相似性時,注意到初始linq調用明顯慢於后續調用。 什么是緩存,正在產生這樣的差異? 我感興趣的是什么時候我們可以期望實現這種類型的行為(也許這只是因為相同的列表被反復使用)。

    static void Main(string[] args)
    {
        var a = new List<int>() { 7, 17, 21, 29, 30, 33, 40, 42, 51, 53, 60, 63, 66, 68, 70, 84, 85, 91, 101, 102, 104, 108, 109, 112, 115, 116, 118, 125, 132, 137, 139, 142, 155, 163, 164, 172, 174, 176, 179, 184, 185, 186, 187, 188, 189, 192, 197, 206, 209, 234, 240, 244, 249, 250, 252, 253, 254, 261, 263, 270, 275, 277, 290, 292, 293, 304, 308, 310, 314, 316, 319, 321, 322, 325, 326, 327, 331, 332, 333, 340, 367, 371, 374, 403, 411, 422, 427, 436, 440, 443, 444, 446, 448, 449, 450, 452, 455, 459, 467, 470, 487, 488, 489, 492, 494, 502, 503, 505, 513, 514, 522, 523, 528, 532, 534, 535, 545, 547, 548, 553, 555, 556, 565, 568, 570, 577, 581, 593, 595, 596, 598, 599, 606, 608, 613, 615, 630, 638, 648, 661, 663, 665, 669, 673, 679, 681, 685, 687, 690, 697, 702, 705, 708, 710, 716, 719, 724, 725, 727, 728, 732, 733, 739, 744, 760, 762, 775, 781, 787, 788, 790, 795, 797, 802, 806, 808, 811, 818, 821, 822, 829, 835, 845, 848, 851, 859, 864, 866, 868, 875, 881, 898, 899, 906, 909, 912, 913, 915, 916, 920, 926, 929, 930, 933, 937, 945, 946, 949, 954, 957, 960, 968, 975, 980, 985, 987, 989, 995 };
        var b = new List<int>() { 14, 20, 22, 23, 32, 36, 40, 48, 63, 65, 67, 71, 83, 87, 90, 100, 104, 109, 111, 127, 128, 137, 139, 141, 143, 148, 152, 153, 157, 158, 161, 163, 166, 187, 192, 198, 210, 211, 217, 220, 221, 232, 233, 236, 251, 252, 254, 256, 257, 272, 273, 277, 278, 283, 292, 304, 305, 307, 321, 333, 336, 341, 342, 344, 349, 355, 356, 359, 366, 373, 379, 386, 387, 392, 394, 396, 401, 409, 412, 433, 437, 441, 445, 447, 452, 465, 471, 476, 479, 483, 511, 514, 516, 521, 523, 531, 544, 548, 551, 554, 559, 562, 566, 567, 571, 572, 574, 576, 586, 592, 593, 597, 600, 602, 615, 627, 631, 636, 644, 650, 655, 657, 660, 667, 670, 680, 691, 697, 699, 703, 704, 706, 707, 716, 742, 748, 751, 754, 766, 770, 779, 785, 788, 790, 802, 803, 806, 811, 812, 815, 816, 821, 824, 828, 841, 848, 853, 863, 866, 870, 872, 875, 879, 880, 882, 883, 885, 886, 887, 888, 892, 894, 902, 905, 909, 912, 913, 914, 916, 920, 922, 925, 926, 928, 930, 935, 936, 938, 942, 945, 952, 954, 955, 957, 959, 960, 961, 963, 970, 974, 976, 979, 987 };
        var s = new System.Diagnostics.Stopwatch();
        const int cycles = 10;
        for (int i = 0; i < cycles; i++)
        {
            s.Start();
            var z= a.Intersect(b);
            s.Stop();
            Console.WriteLine("Test 1-{0}: {1} {2}", i, s.ElapsedTicks, z.Count());
            s.Reset();
            a[0]=i;//simple attempt to make sure entire result isn't cached
        }

        for (int i = 0; i < cycles; i++)
        {
            var z1 = new List<int>(a.Count);
            s.Start();
            int j = 0;
            int b1 = b[j];
            foreach (var a1 in a)
            {
                while (b1 <= a1)
                {
                    if (b1 == a1)
                        z1.Add(b[j]);
                    j++;
                    if (j >= b.Count)
                        break;
                    b1 = b[j];
                }
            }
            s.Stop();
            Console.WriteLine("Test 2-{0}: {1} {2}", i, s.ElapsedTicks, z1.Count);
            s.Reset();
            a[0]=i;//simple attempt to make sure entire result isn't cached
        }

        Console.Write("Press Enter to quit");
        Console.ReadLine();
    }
}

根據某些要求 - 示例輸出:

Test 1-0: 2900 45
Test 1-1: 2 45
Test 1-2: 0 45
Test 1-3: 1 45

(正常循環顯示連續運行之間只有輕微差異)

改變之后注意調用a.Intersect(b).ToArray(); 而不just a.Intersect(b); 正如@kerem所建議的那樣,結果變為:

Test 1-0: 13656 45
Test 1-1: 113 45
Test 1-2: 76 45
Test 1-3: 64 45
Test 1-4: 90 45 
...

我希望任何循環的第一次運行都會變慢,原因有三:

  1. 代碼必須在第一次進行,但不能隨后進行。
  2. 如果運行的可執行代碼足夠小以適應緩存,則它不會被驅逐,並且加載CPU的速度更快。
  3. 如果數據足夠小以適應緩存,則它不會被驅逐,並且加載CPU的速度更快。

LINQ大量使用延遲執行。 除非您枚舉查詢,否則它不會被執行。

更改

 s.Start();
 z= a.Intersect(b);
 s.Stop();

 s.Start();
 z= a.Intersect(b).**ToArray**();
 s.Stop();

並請發布新的績效結果。

a.Intersect(b)表示一個表達式,與a和b的值無關。 僅當通過枚舉計算表達式時,才使用a和b的值。

JITting System.Enumerable。

把新的List()。相交(new List()); new System.Diagnostics.Stopwatch()。Stop(); 作為您的第一行代碼和所有交互將花費相同的時間。

Enumerable.Intersect不執行任何緩存。 它是使用HashSet實現的。 第一個序列被添加到HashSet 然后從HashSet刪除第二個序列。 HashSet的其余元素作為可枚舉的元素序列產生。 您必須實際枚舉HashSet以支付創建HashSet的成本。 即使對於小型集合,這種實現也令人驚訝地高效。

如果你在后續調用中看到性能上的差異,那不是因為Enumerable.Intersect執行任何緩存,但可能是因為你需要“預熱”你的基准測試。

只有在調用Count()時,才會枚舉Intersect()的結果; 當交叉點的計算實際發生時。 您正在計時的部分是創建可枚舉對象, 對象表示交集的未來計算

除了其他人注意到的jitting懲罰之外,第一次調用Intersect()可能是第一次使用System.Core.dll中的類型,因此您可能正在查看將IL代碼加載到內存中所需的時間,如好。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM