简体   繁体   中英

Is LINQ slower than foreach loop for aggregation operations?

The short question : Why is LINQ slower than looping via foreach for aggregation operations?

Description -

I am trying to optimize some old code and while LINQ queries are used extensively throughout the code, trivial operations like summing up numbers in an Enumerable is done using loops.

So, I ran a little test to compare the performance of the two methods. Below is the code for computing sum of a 1000 numbers long enumerable using LINQ's .Sum() method and using a foreach loop and summing up each occurence manually.

List<Double> numbers = new List<Double>();
Double[] sums1 = new Double[1000];
Double[] sums2 = new Double[1000];

for (int i = 0; i < 1000; i++)
{
    numbers.Add(i * i);
}

Int64 startTime1 = Stopwatch.GetTimestamp();
for (int i = 0; i < 1000; i++)
{
    Double sum = 0;
    sum = numbers.Sum();
    sums1[i] = sum;
}
Int64 endTime1 = Stopwatch.GetTimestamp();

Int64 startTime2 = Stopwatch.GetTimestamp();
for (int i = 0; i < 1000; i++)
{
    Double sum = 0;
    foreach (Double number in numbers)
    {
        sum += number;
    }
    sums2[i] = sum;
}
Int64 endTime2 = Stopwatch.GetTimestamp();

Console.WriteLine("LINQ.    Start = {0}, End = {1}: Diff = {2}", startTime1, endTime1, endTime1 - startTime1);
Console.WriteLine("ForEach. Start = {0}, End = {1}: Diff = {2}", startTime2, endTime2, endTime2 - startTime2);

I ran this test a couple (ten) times and the results were:

LINQ.    Start = 117385428996, End = 117385462197: Diff = 33201
Foreach. Start = 117385462203, End = 117385476329: Diff = 14126
LINQ.    Start = 117385478555, End = 117385499802: Diff = 21247
Foreach. Start = 117385499808, End = 117385520756: Diff = 20948
LINQ.    Start = 117385521426, End = 117385546256: Diff = 24830
Foreach. Start = 117385546260, End = 117385567052: Diff = 20792
LINQ.    Start = 117385572791, End = 117385602149: Diff = 29358
Foreach. Start = 117385602156, End = 117385622367: Diff = 20211
LINQ.    Start = 117385623153, End = 117385652563: Diff = 29410
Foreach. Start = 117385652568, End = 117385673733: Diff = 21165
LINQ.    Start = 117385674403, End = 117385705028: Diff = 30625
Foreach. Start = 117385705035, End = 117385725552: Diff = 20517
LINQ.    Start = 117385726094, End = 117385753161: Diff = 27067
Foreach. Start = 117385753166, End = 117385771824: Diff = 18658
LINQ.    Start = 117385772341, End = 117385793726: Diff = 21385
Foreach. Start = 117385793733, End = 117385811332: Diff = 17599
LINQ.    Start = 117385811768, End = 117385837204: Diff = 25436
Foreach. Start = 117385837209, End = 117385852670: Diff = 15461
LINQ.    Start = 117385853003, End = 117385874410: Diff = 21407
Foreach. Start = 117385874416, End = 117385891874: Diff = 17458

Note that the foreach loop always performed better. What could be the reason for this?

Edit: An answer to this question has lot of good information about why the performance could be bad as compared to regular inline manipulations. But I am unable to see how exactly it could relate here.

It seems it is about what you are iterating. I slightly changed your code. First it is iterating over IEnumerable (which you say linq ), and then iterating over List (which you say foreach ). I got the same results with you.

Please check the methods withIEnumerable and withList . They do exactly the same thing except that signatures are different. LINQ extension methods gets IEnumerable as parameter.

Edit: Performance between Iterating through IEnumerable<T> and List<T> gives a good explanation why list enumerates faster.

class Program
{
    static void Main(string[] args)
    {
        for (int i = 0; i < 10; i++)
        {
            calculate();
        }

    }

    private static void calculate()
    {
        List<Double> numbers = new List<Double>();
        Double[] sums1 = new Double[1000];
        Double[] sums2 = new Double[1000];

        for (int i = 0; i < 1000; i++)
        {
            numbers.Add(i * i);
        }

        Int64 startTime1 = Stopwatch.GetTimestamp();
        for (int i = 0; i < 1000; i++)
        {
            sums1[i] = withIEnumerable(numbers);
        }
        Int64 endTime1 = Stopwatch.GetTimestamp();

        Int64 startTime2 = Stopwatch.GetTimestamp();
        for (int i = 0; i < 1000; i++)
        {
            sums2[i] = withList(numbers);
        }
        Int64 endTime2 = Stopwatch.GetTimestamp();


        Console.WriteLine("withIEnumerable.    Start = {0}, End = {1}: Diff = {2}", startTime1, endTime1, endTime1 - startTime1);
        Console.WriteLine("withList. Start = {0}, End = {1}: Diff = {2}", startTime2, endTime2, endTime2 - startTime2);
    }

    private static double withIEnumerable(IEnumerable<double> numbers)
    {
        double sum = 0;
        foreach (Double number in numbers)
        {
            sum += number;
        }

        return sum;
    }

    private static double withList(List<double> numbers)
    {
        double sum = 0;
        foreach (Double number in numbers)
        {
            sum += number;
        }

        return sum;
    }
}

This is not the correct answer, but I'm going to leave it here for reference instead of deleting it. Please read serdar's answer (the accepted one). Please let me know or edit if this is against the way the commnunity works.

Calling the methods have an added very prominent time overhead. From what I have found, this is the biggest contributor to the slower performance in this particular case.

I added an extension method to sum up the list with the same (faster) code, but inside an extension method.

public static class MyExtensionMethods
{
    public static Double SumX(this IEnumerable<Double> list)
    {
        Double sum = 0;
        foreach (Double number in list)
        {
            sum += number;
        }
        return sum;
    }
}

And then using the extension method I just created,

...
Int64 startTime2 = Stopwatch.GetTimestamp();
for (int i = 0; i < 1000; i++)
{
    Double sum = numbers.SumX();
    sums2[i] = sum;
}
Int64 endTime2 = Stopwatch.GetTimestamp();
...

The results were:

LINQ.    Start = 129087370675, End = 129087395309: Diff = 24634
Foreach. Start = 129087395312, End = 129087420320: Diff = 25008
LINQ.    Start = 129087422887, End = 129087447541: Diff = 24654
Foreach. Start = 129087447547, End = 129087465859: Diff = 18312
LINQ.    Start = 129087466278, End = 129087484777: Diff = 18499
Foreach. Start = 129087484784, End = 129087505378: Diff = 20594
LINQ.    Start = 129087506425, End = 129087526134: Diff = 19709
Foreach. Start = 129087526141, End = 129087552013: Diff = 25872
LINQ.    Start = 129087552500, End = 129087578445: Diff = 25945
Foreach. Start = 129087578451, End = 129087601858: Diff = 23407
LINQ.    Start = 129087602371, End = 129087630873: Diff = 28502
Foreach. Start = 129087630880, End = 129087674495: Diff = 43615
LINQ.    Start = 129087675028, End = 129087702841: Diff = 27813
Foreach. Start = 129087702849, End = 129087732360: Diff = 29511
LINQ.    Start = 129087732974, End = 129087760529: Diff = 27555
Foreach. Start = 129087760536, End = 129087785590: Diff = 25054
LINQ.    Start = 129087786096, End = 129087813331: Diff = 27235
Foreach. Start = 129087813336, End = 129087842947: Diff = 29611
LINQ.    Start = 129087843471, End = 129087870633: Diff = 27162
Foreach. Start = 129087870639, End = 129087896678: Diff = 26039

Bottom Line: LINQ is slower in this case because of the added overhead of the extension methods. I also tried using a simple static method instead of the extension method, and its weren't results any different that the results with extension method.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM