Performance "Where in foreach vs if"

Asked

Viewed 146 times

4

Which case would perform best?

var chaves = new list<string>();
foreach(var item in lista)
{
   if(!string.IsNullOrEmpty(item.Chave))
   {
      chaves.Add(item.Chave);
   }
}

Or

listaValida = lista.Where(x => !string.IsNullOrEmpty(x.Chave));
foreach(var item in listaValida)
{
    chaves.Add(item.Chave);
}

There is a difference in performance between the two cases ?

  • The second code is faster and more readable.

3 answers

6


The 1 code was faster (but I find it much uglier to read) Follow what I used to test

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

namespace SpeedTest
{
    public class ItemChave
    {
        public string Chave { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {

            List<ItemChave> lista = new List<ItemChave>();
            lista.Add(new ItemChave() { Chave = "a" });
            lista.Add(new ItemChave() { Chave = "b" });
            lista.Add(new ItemChave() { Chave = "" });
            lista.Add(new ItemChave() { Chave = "c" });
            lista.Add(new ItemChave() { Chave = "" });
            lista.Add(new ItemChave() { Chave = "d" });
            lista.Add(new ItemChave() { Chave = "a" });
            lista.Add(new ItemChave() { Chave = "b" });
            lista.Add(new ItemChave() { Chave = "" });
            lista.Add(new ItemChave() { Chave = "c" });
            lista.Add(new ItemChave() { Chave = "" });
            lista.Add(new ItemChave() { Chave = "d" });
            lista.Add(new ItemChave() { Chave = "a" });
            lista.Add(new ItemChave() { Chave = "b" });
            lista.Add(new ItemChave() { Chave = "" });
            lista.Add(new ItemChave() { Chave = "c" });
            lista.Add(new ItemChave() { Chave = "" });
            lista.Add(new ItemChave() { Chave = "d" });
            lista.Add(new ItemChave() { Chave = "a" });
            lista.Add(new ItemChave() { Chave = "b" });
            lista.Add(new ItemChave() { Chave = "" });
            lista.Add(new ItemChave() { Chave = "c" });
            lista.Add(new ItemChave() { Chave = "" });
            lista.Add(new ItemChave() { Chave = "d" });
            lista.Add(new ItemChave() { Chave = "a" });
            lista.Add(new ItemChave() { Chave = "b" });
            lista.Add(new ItemChave() { Chave = "" });
            lista.Add(new ItemChave() { Chave = "c" });
            lista.Add(new ItemChave() { Chave = "" });
            lista.Add(new ItemChave() { Chave = "d" });
            lista.Add(new ItemChave() { Chave = "a" });
            lista.Add(new ItemChave() { Chave = "" });
            lista.Add(new ItemChave() { Chave = "" });
            lista.Add(new ItemChave() { Chave = "c" });
            lista.Add(new ItemChave() { Chave = "" });
            lista.Add(new ItemChave() { Chave = "d" });
            lista.Add(new ItemChave() { Chave = "a" });
            lista.Add(new ItemChave() { Chave = "b" });
            lista.Add(new ItemChave() { Chave = "" });
            lista.Add(new ItemChave() { Chave = "c" });
            lista.Add(new ItemChave() { Chave = "" });
            lista.Add(new ItemChave() { Chave = "d" });



            Stopwatch sw = new Stopwatch();
            sw.Start();
            var chaves = new List<string>();
            foreach (var item in lista)
            {
                if (!string.IsNullOrEmpty(item.Chave))
                {
                    chaves.Add(item.Chave);
                }
            }
            sw.Stop();
            Console.WriteLine("Tempo 1 ={0}", sw.Elapsed);

            Stopwatch sw2 = new Stopwatch();
            sw2.Start();

            var listaValida = lista.Where(x => !string.IsNullOrEmpty(x.Chave));
            foreach (var item in listaValida)
            {
                chaves.Add(item.Chave);
            }
            sw2.Stop();
            Console.WriteLine("Tempo 2 ={0}", sw2.Elapsed);
            Console.ReadKey();

        }
    }
}

6

The right answer is: it depends.

jbueno already explained how it works in case your list was created based on data available only in the application memory space, i.e.: that your application assembled alone.

However! LINQ can also be used to obtain data from a database, if you want to completely abstract yourself from the SQL language.

If your list was uploaded in real time from a database, via Entity Framework, the situation would be quite different.

In that case:

var chaves = new list<string>();
foreach(var item in lista)
{
    if(!string.IsNullOrEmpty(item.Chave))
    {
       chaves.Add(item.Chave);
    }
}

You may have done the equivalent of a SELECT without WHERE, that can carry millions of records to memory. Hence you select the records that interest you in C#. It can be a waste of resources.

In that case:

listaValida = lista.Where(x => !string.IsNullOrEmpty(x.Chave));
foreach(var item in listaValida)
{
    chaves.Add(item.Chave);
}

LINQ generates an SQL command more or less like this:

SELECT * FROM tabela WHERE chave is not null AND LEN(chave) > 0

This way, you can potentially avoid loading and transit millions of registrations. This form would undoubtedly be faster for most cases.


In both cases, if you use the method .ToList() your code gets cleaner.

  • There’s this point too, although I think he’s talking about a list in memory.

5

There is not much mystery. Without knowing the details of how the language works is just measuring and making an average to have a base of which is faster.

In my trials, with three million records, first case was faster (Caso1()), taking an average of 40ms each execution, the second (Caso2()) was with an average of 55ms per run.

Using .ToList() (Caso3()) each execution took an average of 90ms.

The code I used to test was this:

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

public class Program 
{
    static List<string> lista = new List<string>();
    public static void Main (string[] args)
    {
        PopularLista();

        Console.WriteLine(lista.Count);

        Caso1();
        Caso2();
        Caso3();
    }

    private static void Caso1()
    {
        var watch = Stopwatch.StartNew();

        var chaves = new List<string>();
        foreach(var item in lista)
        {
            if(!string.IsNullOrEmpty(item))
            {
                chaves.Add(item);
            }
        }

        watch.Stop();
        var ms = watch.ElapsedMilliseconds;
        Console.WriteLine("Caso 1: " + ms + " ms");
    }

    private static void Caso2()
    {
        var watch = Stopwatch.StartNew();

        var chaves = new List<string>();
        var listaValida = lista.Where(x => !string.IsNullOrEmpty(x));
        foreach(var item in listaValida)
        {
            chaves.Add(item);
        }

        watch.Stop();
        var ms = watch.ElapsedMilliseconds;
        Console.WriteLine("Caso 2: " + ms + " ms");
    }

    private static void Caso3()
    {
        var watch = Stopwatch.StartNew();

        var listaValida = lista.Where(x => !string.IsNullOrEmpty(x)).ToList();   

        watch.Stop();
        var ms = watch.ElapsedMilliseconds;
        Console.WriteLine("Caso 3: " + ms + " ms");
    }

    private static void PopularLista()
    {
        for(int i = 0; i < 3000000; i++)
            lista.Add(i % 2 == 0 ? "Teste" : "");
    }
}

You can run the tests in repl.it.

  • It would be impossible for the abstraction of LINQ to be faster. I just don’t see why you need to do the ToList().

  • @bigown is I know, but I want to find the source of Linq to have a base to say this. Where you see the sources of the . net framework same?

  • http://referencesource.microsoft.com/. If the ToList() even makes the comparison unfair because it makes the loop twice. I just won’t say it because there may be something I don’t know, but it’s almost certain it will be worse unjustly.

  • Yeah, I thought about it just now. I’ll look up the source and confirm.

  • Now that I’ve seen the code, it’s actually comparing different things and it’s not using its code in case 2. That’s unfair. So in the code you’re only doing one loop, but it’s not quite what he wants, although the result will end up being the same.

  • I did it with both of his and put the ToList() as a third case.

  • Nice to have a parameter, after which I saw that you replaced the foreach by ``Tolist()` not causing double processing. Tested on your machine with nothing else running, especially Vms, Dockers and the like? I imagine you have a lot of memory. Hj is difficult to test without external influences.

  • I had a Docker and a VM running. I stopped them and tested again with 3M of records. I updated the answer.

  • Did it run a few times? They were consistent. Are you sure GC ñ has taken action in any case? : ) I’m not saying there’s anything wrong, just putting some care that it’s always good to have qdo measure performance. Nor is it all.

  • I drove 20 times to average. I’m just not sure if the GC "went into action" or not, how can I guarantee it?

  • I doubt I got in, and I don’t know if it pays, but you can force a collection before every start and you can use https://msdn.microsoft.com/en-us/library/dn906201(v=vs.110).aspx. But there’s no guarantee, to know if you rode or you wouldn’t have to do one Profiling, But I think it’s an exaggeration that you need the information 100% correct and that will be very relevant, which is far from being the case. But then I would have to have other concerns too.

Show 6 more comments

Browser other questions tagged

You are not signed in. Login or sign up in order to post.