Jak zbadać wydajność krótkiego kodu?

0

Chcialem się z ciekawości dowiedzieć czy np:

var c = g.Where(o => o > 2 && o < 5).ToList();

jest szybsze od:

var c = g.Where(o => o > 2).Where(o=> o < 5).ToList();

Użyłem klasy Stopwatch, ale co uruchomienie programu to dostaje inne wyniki. Przy milionie wywołań widoczna jest różnica na korzyść pojedynczego where.
Wkleiłem kod na dotnetfiddle, podejrzałem kod IL. Za wiele on mi nie powiedział poza tym ze wynikowy kod z pojedynczym Where ma 20 linii kodu mniej. Czy można uznać ze ten pojedynczy Where jest szybszy?
Jak inaczej można to zbadać?

0

To też zależy od kontekstu. Gdy uruchamiasz LINQ na obiektach w pamięci rzeczywiście pojedynczy Where może być szybszy, ponieważ każde wywołanie Where powoduje iterację po całej kolekcji. W przypadku gdybyś uruchamiał LINQ na przykład używając Entity Frameworka to pewnie data provider przetłumaczyłby to na jednego WHERE w SQLu zamiast jakiegoś podzapytania i różnicy by nie było.

Według mnie te milion wywołań, które sprawdziłeś to wystarczające potwierdzenie w tym przypadku.

4

Tylko i wyłącznie za pomocą : https://benchmarkdotnet.org/

 [Config(typeof(BenchmarkDotNetConfig))]
    public class ArraySum
    {
        private long[] data;
        private long expectedResult; 


        [Params(1000, 10_000, 100_000)]
        public int Size
        {
            get;
            set;
        }


        [GlobalSetup]
        public void GlobalSetup()
        {
            data = new long[Size];
            for (int i = 0; i < Size; ++i)
            {
                data[i] = i % 100;
            }
            expectedResult = data.Sum();
        }
     
        [Benchmark]
        public IEnumerable<long> Case1()
        {
            var c = data.Where(o => o > 2 && o < 5).ToList();
            return c;
        }  
        [Benchmark]
        public IEnumerable<long> Case2()
        {
            var c = data.Where(o => o > 2).Where(o => o < 5).ToList();           
            return c;
        } 
        [Benchmark]
        public IEnumerable<long> Case3()
        {
            var result = new List<long>(data.Length/10);

            foreach(long i in data)
            {
                if ((i > 2) && (i < 5))
                {
                    result.Add(i);
                }
            }

            return result;
        }         
    }
Method Size Mean Error StdDev Gen 0 Gen 1 Allocated
Case1 1000 6.122 us 5.413 us 0.3058 us 0.4196 - 672 B
Case2 1000 10.937 us 6.525 us 0.3687 us 0.5188 - 824 B
Case3 1000 2.469 us 1.083 us 0.0612 us 0.5455 - 864 B
Case1 10000 66.598 us 151.150 us 8.5403 us 2.7466 - 4330 B
Case2 10000 230.682 us 501.066 us 28.3111 us 2.4414 - 4482 B
Case3 10000 23.826 us 16.956 us 0.9581 us 5.0964 - 8066 B
Case1 100000 980.513 us 873.224 us 49.3388 us 19.5313 - 33136 B
Case2 100000 1,119.897 us 1,055.583 us 59.6424 us 19.5313 - 33304 B
Case3 100000 225.888 us 144.071 us 8.1403 us 35.8887 8.7891 80344 B
Fuffu napisał(a):

ponieważ każde wywołanie Where powoduje iterację po całej kolekcji.

Guzik prawda, wywołanie where w ogóle nie powoduje itreracji po kolekcji, całe linqu jest zbudowane na yieldach, iteracja następuję dopiero wtedy gdy użyjemy jakiegoś operatora powodującego materializację wyników.

0

Dzięki. Tylko coś mi ten kod powyżej nie chce działać. Nie mam klasy BenchmarkDotNetConfig - wykomentowałem to. Ale przy uruchomieniu dostaje:

Benchmark ArraySum.Case1 returns a deferred execution result (IEnumerable<Int64>). You need to either change the method declaration to return a materialized result or consume it on your own. You can use .Consume() extension method to do that.

Zmieniłem IEnumarable na List i działa.

A do czego ta zmienna expectedResult ma służyć?

0

Próbowałeś kiedyś używać NCruncha? Głównie jest używany do pokazywania code coverage kodu i łatwiejszego wykrywania błędów, ale ma też funkcję pokazywania ile dokładnie czasu zajmuje poszczególna linijka kodu. Nawet ładnie to koloruje:
NCrunch
Co prawda jest płatny, ale można sobie potestować za darmo przez jakiś czas.

0

A czy to przypadkiem nie wygląda tak, że w przypadku EF linq buduje zapytanie dla jednego where:
Select ... Where exp1 && exp2
A dla dwóch where:
Select .. from (select... Where exp1) where exp2

Nie wiem czy dobrze kombinuję.

1 użytkowników online, w tym zalogowanych: 0, gości: 1