Looking into LINQ Performance

Sometimes performance can be a key factor for the code we write, and that’s when LINQ may not always be the best choice. This post looks at LINQ performance compared to the more traditional ways of writing code.

Code written in LINQ is short, effective and reader friendly, and therefore often the preferred way to query collections of objects in .NET. But if performance is a main concern, it can be necessary to look at just how fast LINQ executes compared to more traditional approaches based on “old fashioned” code loops and if-conditions. Have a look at the below figure, which shows a LINQ approach and a traditional approach to sum up an array of numbers with values less than or equal to 3.

Figure 1: First example uses LINQ and second uses loop and if-condition to achieve the same.

The output in both cases is this: "The sum is: 8"

In the following I will be looking at 3 different scenarios of rising complexity, where I compare performance between code written with LINQ and the equivalent functionality written with tradional code. This way we'll see just how big a difference there are between the two approaches. Code execution speed will of course vary from computer to computer, so the interesting point is to see how the results of each scenario relate to each other.

The hardware used to execute the code in this tutorial is as follows: Lenovo ThinkPad T540p running Windows 7 with 8Gb of RAM.

Scenario 1

Scenario 1 is a code example which counts all numbers with a value of 12 from an array with random numbers.

Code complexity: LINQ with 2 chains (Where and Count).

Code description:

Set up array with 10,000,000 random integer values between 0-20.

LINQ approach to take all numbers from array with a value of 12, and count these.

Traditional approach to take all numbers from array with a value of 12, and count these.

Performance measures of both approaches are outputted.

// Create array with 10,000,000 random integer values between 0-20 Random randNum = new Random(); int[] numbers = Enumerable .Repeat(0, 10000000) .Select(i => randNum.Next(0, 20)) .ToArray(); // Start stopwatch Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); // LINQ approach var cntLinq = numbers .Where(n => n == 12) .Count(); // Stop stopwatch stopwatch.Stop(); // Print result Console.WriteLine(" Time elapsed for LINQ approach: {0} ms", stopwatch.ElapsedMilliseconds); // Restart stopwatch stopwatch.Reset(); stopwatch.Start(); // Traditional approach int cntTraditional = 0; foreach (int n in numbers) if (n == 12) ++cntTraditional; // Stop stopwatch stopwatch.Stop(); // Print result Console.WriteLine(" Time elapsed for traditional approach: {0} ms", stopwatch.ElapsedMilliseconds); // Make sure both approaches give same result Debug.Assert(cntLinq == cntTraditional);

Output is:

Thus the traditional approach in this case is approximately 4 times faster than that of LINQ.

Scenario 2

Scenario 2 is a code example which first finds values larger than or equal to 5 from an array with random numbers, multiplies each of the found values by themselves, and then calculates the sum.

Code complexity: LINQ with 3 chains (Where, Select and Sum).

Code description:

Set up array with 10,000,000 random integer values between 0-20.

LINQ approach to take all numbers from array with a value of 5 or more, multiply value by itself, and lastly sum all values.

Traditional approach to take all numbers from array with a value of 5 or more, multiply value by itself, and lastly sum all values.

Performance measures of both approaches are outputted.

// Create array with 10,000,000 random integer values between 0-20 Random randNum = new Random(); int[] numbers = Enumerable .Repeat(0, 10000000) .Select(i => randNum.Next(0, 20)) .ToArray(); // Start stopwatch Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); // LINQ approach var sumLinq = numbers .Where(n => n >= 5) .Select(n => n * n) .Sum(); // Stop stopwatch stopwatch.Stop(); // Print result Console.WriteLine(" Time elapsed for LINQ approach: {0} ms", stopwatch.ElapsedMilliseconds); // Restart stopwatch stopwatch.Reset(); stopwatch.Start(); // Traditional approach int sumTraditional = 0; foreach (int n in numbers) if (n >= 5) sumTraditional += n * n; // Stop stopwatch stopwatch.Stop(); // Print result Console.WriteLine(" Time elapsed for traditional approach: {0} ms", stopwatch.ElapsedMilliseconds); // Make sure both approaches give same result Debug.Assert(sumLinq == sumTraditional);

Output is:

Thus the traditional approach is this time approximately 4 times faster.

Scenario 3

Scenario 3 is a code example which first finds values between 1-5 from an array with random numbers, joins with array of anonymously typed elements by number, takes spelled out numbers from join result being exactly three letters of length, and finally counts these.

Code complexity: LINQ with 4 chains (Where, Join, Select and Count).

Code description:

Set up array called numbers with 10,000,000 random integer values between 0-5.

with 10,000,000 random integer values between 0-5. Set up array called words of anonymously typed elements, consisting of numbers 1-5 and corresponding spelled out values.

of anonymously typed elements, consisting of numbers 1-5 and corresponding spelled out values. LINQ approach to take all numbers from numbers array with a value between 1-5, join by words array, take only words which are exactly three letters in length, and then count these.

array with a value between 1-5, join by array, take only words which are exactly three letters in length, and then count these. Traditional approach to take all numbers from numbers array with a value between 1-5, join by words array, take only words which are exactly three letters in length, and then count these.

array with a value between 1-5, join by array, take only words which are exactly three letters in length, and then count these. Performance measures of both approaches are outputted.

// Create array with 10.000.000 random integer values between 0-20 Random randNum = new Random(); int[] numbers = Enumerable .Repeat(0, 10000000) .Select(i => randNum.Next(0, 20)) .ToArray(); // Create an array of anonymously typed elements with numbers 1-5, and spelled out values var words = new[] { new { number = 1, word = "one" }, new { number = 2, word = "two" }, new { number = 3, word = "three" }, new { number = 4, word = "four" }, new { number = 5, word = "five" }, }; // Start stopwatch Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); // LINQ approach var cntLinq = numbers .Where(n => n >= 1 && n <= 5) .Join(words, n => n, w => w.number, (n, w) => new { w.word }) .Select(w => w.word.Length == 3) .Count(); // Stop stopwatch stopwatch.Stop(); // Print result Console.WriteLine(" Time elapsed for LINQ approach: {0} ms", stopwatch.ElapsedMilliseconds); // Restart stopwatch stopwatch.Reset(); stopwatch.Start(); // Traditional approach int cntTraditional = 0; List lstWords = new List (); foreach (int n in numbers) if (n >= 1 && n <= 5) { lstWords.Add(words[n - 1].word); ++cntTraditional; } // Stop stopwatch stopwatch.Stop(); // Print result Console.WriteLine(" Time elapsed for traditional approach: {0} ms", stopwatch.ElapsedMilliseconds); // Make sure both approaches give same result Debug.Assert(cntLinq == cntTraditional);

Output is:

Hence the traditional approach is this time approximately 3 times faster.

Summary

For comparison, here are the results from all three scenarios.

Scenario Complexity LINQ approach Traditional approach Difference Scenario 1 Simple (3 LINQ chains: Where and Count) 121 ms 33 ms 4 : 1 Scenario 2 Medium (4 LINQ chains: Where, Select and Sum) 235 ms 57 ms 4 : 1 Scenario 3 Advanced (5 LINQ chains: Where, Join, Select and Count) 366 ms 119 ms 3 : 1 Average - 240 ms 70 ms 3½ : 1

In all 3 scenarios we found a performance gain of between 3-4 times by the use of the traditional code approach. Even though the results will naturally vary depending on the code style of the programmer and the execution speed of the computer, a clear tendency surdenly does show. This is worth considering, if you are to develop a performance critical piece of code.