Watch Out For LINQ Performance Issues

Everyone agrees that LINQ is "very cool" and brings much-needed elegance and utility to querying collections and other objects. However, there are some real bottlenecks that developers should be aware of when using LINQ.

In particular, if you are querying over a large number of items in a collection or array, regardless of the item type, LINQ can be much slower than a typical foreach loop with  the appropriate "if tests".

I put together some sample code to test the performance of LINQ vs foreach loops over a large array of strings, and as well, over a large collection of integers. Let's look at the code and then the output and draw some conclusions:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace LINQPerformance
{
    class LINQPerfTest
    {
         static string[] strArray;
        static string[] strTest;
        private const int number = 1000; //Change this number to size the Array

        public static void Main()
        {
            FillArrayWithGuids();
            FindTenElements();
           Console.WriteLine("Using For Each loop with Strings:");
            UseForEachLoop();
          
           Console.WriteLine("Using LINQ With Strings:");
            UseLinq();
           Console.WriteLine("Use Foreach with int:");
            UseForeachWithIf();
           Console.WriteLine("Use LINQ Where with int:");
            UseLinqWhereQuery();
           Console.ReadKey();
        }

         private static void UseForEachLoop()
        {
            Stopwatch sw = new Stopwatch();
             for (int i = 0; i < 10; i++)
             {
                 sw.Start();
                foreach (var item in strArray)
                 {
                     if (item == strTest[i])
                    {
                        
                        sw.Stop();
                       Console.Write(" Item found in {0} ticks\n", sw.ElapsedTicks.ToString());
                          break;
                     }
                 }
            }
        }

         private static void UseLinq()
        {
            Stopwatch sw = new Stopwatch();
             for (int i = 0; i < 10; i++)
             {
                 sw.Start();
                var qry = from x in strArray
                            where x == strTest[i]
                          select x;

                foreach (var item in qry)
                {
                    
                }
                sw.Stop();
               Console.Write(" Item found in {0} ticks\n", sw.ElapsedTicks.ToString());
             }
        }

        // Initialize Array and retrieve 10 elements for comparison
        private static void FillArrayWithGuids()
        {
            //Fill the string Array with GUIDs
            strArray = new string[number * 10];
             for (int i = 0; i < number * 10; i++)
            {
                strArray[i] = System.Guid.NewGuid().ToString();
             }
         }

        private static void FindTenElements()
        {
            //Find 10 elements from the big array for comparison
            //We are finding the elements from different locations to see if that has any affect on performance
            strTest = new string[10];
            strTest[0] = strArray[0 * number];
            strTest[1] = strArray[1 * number];
            strTest[2] = strArray[2 * number];
            strTest[3] = strArray[3 * number];
            strTest[4] = strArray[4 * number];
            strTest[5] = strArray[5 * number];
            strTest[6] = strArray[6 * number];
            strTest[7] = strArray[7 * number];
            strTest[8] = strArray[8 * number];
            strTest[9] = strArray[9 * number];
        }

        // Work with List<int> performing standard foreach with "if test"
        private static void UseForeachWithIf()
        {
          
            int ctr = 0;
           List<int> ints = new List<int>();
             for (var i = 0; i < 10000000; i++)
             {
                 ints.Add(i);
            }
            Stopwatch sw = Stopwatch.StartNew();
            foreach (var item in ints)
            {
                 if (item % 2 == 0) ctr++;
            }
            sw.Stop();
           Console.Write(" Foreach took {0} ticks\n", sw.ElapsedTicks.ToString());
        }

        // Work with List<int> performing LINQ Where test
        private static void UseLinqWhereQuery()
        {
            int ctr = 0;
           List<int> ints = new List<int>();
             for (var i = 0; i < 10000000; i++)
             {
                 ints.Add(i);
            }
            Stopwatch sw = Stopwatch.StartNew();
            foreach (var item in ints.Where( x=>x%2==0))
            {
                ctr++;
            }
            sw.Stop();
           Console.Write(" LINQ Where took {0} ticks\n", sw.ElapsedTicks.ToString());
        }
    }
}

Here is sample Console Output:



As can be seen, in both the string and the int cases, LINQ with a where clause or query is significantly slower than a standard foreach loop with "if tests" in the loop -- so much so that it behooves developers to be aware of this impediment whenever attempting to do iterations over an array or collection.  LINQ queries to find strings in a large collection or array can be from 2 to  as much as 32 times slower than using a standard foreach loop, depending on how the LINQ query is constructed.

If you are going to use LINQ, it is important to test. There are many ways to write a LINQ query. For example, examine the two queries below:

                var qry = from x in strArray   where x == strTest[i]  select x;

                var qry = strArray.First(x => x == strTest[i]);

Both queries will return the same result in this demo , but one of them, if used in the demo code above, is significantly faster. Do you know which one?

Where raw performance is an issue, I would be inclined not to use LINQ unless there are other overriding reasons to do so.

You can download the sample test solution here.

By Peter Bromberg   Popularity  (5938 Views)