LINQ. Any VS .Exists - Jaka jest różnica?


413

Używając LINQ w kolekcjach, jaka jest różnica między następującymi wierszami kodu?

if(!coll.Any(i => i.Value))

i

if(!coll.Exists(i => i.Value))

Aktualizacja 1

Po rozłożeniu .Existswygląda na to, że nie ma kodu.

Aktualizacja 2

Czy ktoś wie, dlaczego nie ma tam kodu?


9
Jak wygląda skompilowany kod? Jak zdemontowałeś? dziwactwo? Czego się spodziewałeś, ale czego nie znalazłeś?
Meinersbur

Odpowiedzi:


423

Zobacz dokumentację

List.Exists (Metoda obiektowa - MSDN)

Określa, czy lista (T) zawiera elementy pasujące do warunków określonych przez określony predykat.

Istnieje od wersji .NET 2.0, a więc przed LINQ. Przeznaczony do użytku z delegatem Predicate , ale wyrażenia lambda są kompatybilne wstecz. Ponadto, tylko Lista ma to (nawet IList)

IEnumerable.Any (Metoda rozszerzenia - MSDN)

Określa, czy dowolny element sekwencji spełnia warunek.

Jest to nowość w .NET 3.5 i używa jako argumentu Func (TSource, bool), więc było to przeznaczone do użycia z wyrażeniami lambda i LINQ.

W zachowaniu są identyczne.


4
Później napisałem post w innym wątku, w którym wymieniłem wszystkie „odpowiedniki” Linq List<>metod instancji .NET 2 .
Jeppe Stig Nielsen,

201

Różnica polega na tym, że Any jest metodą rozszerzenia dowolnej IEnumerable<T>zdefiniowanej w System.Linq.Enumerable. Można go użyć w dowolnej IEnumerable<T>instancji.

Istnienie nie wydaje się być metodą rozszerzenia. Domyślam się, że coll jest typu List<T>. Jeśli tak, istnieje jest instancją, która działa bardzo podobnie do Any.

Krótko mówiąc , metody są zasadniczo takie same. Jedno jest bardziej ogólne niż drugie.

  • Każdy ma również przeciążenie, które nie przyjmuje parametrów i po prostu szuka dowolnego elementu w wyliczeniu.
  • Istnieje nie ma takiego przeciążenia.

13
Dobrze postawiony (+1). Lista <T> .Istnieje już od .Net 2, ale działa tylko w przypadku list ogólnych. IEnumerable <T>. Każdy został dodany w .Net 3 jako rozszerzenie, które działa na dowolnej kolekcji zliczalnej. Istnieją również podobne elementy, takie jak List <T> .Count, która jest właściwością i IEnumerable <T> .Count () - metoda.
Keith

51

TLDR; Wydajność Anywydaje się być wolniejsza (jeśli odpowiednio to skonfigurowałem, aby oszacować obie wartości w tym samym czasie)

        var list1 = Generate(1000000);
        var forceListEval = list1.SingleOrDefault(o => o == "0123456789012");
        if (forceListEval != "sdsdf")
        {
            var s = string.Empty;
            var start2 = DateTime.Now;
            if (!list1.Exists(o => o == "0123456789012"))
            {
                var end2 = DateTime.Now;
                s += " Exists: " + end2.Subtract(start2);
            }

            var start1 = DateTime.Now;
            if (!list1.Any(o => o == "0123456789012"))
            {
                var end1 = DateTime.Now;
                s +=" Any: " +end1.Subtract(start1);
            }

            if (!s.Contains("sdfsd"))
            {

            }

generator listy testów:

private List<string> Generate(int count)
    {
        var list = new List<string>();
        for (int i = 0; i < count; i++)
        {
            list.Add( new string(
            Enumerable.Repeat("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 13)
                .Select(s =>
                {
                    var cryptoResult = new byte[4];
                    new RNGCryptoServiceProvider().GetBytes(cryptoResult);
                    return s[new Random(BitConverter.ToInt32(cryptoResult, 0)).Next(s.Length)];
                })
                .ToArray())); 
        }

        return list;
    }

Z 10 milionami rekordów

„Dowolny: 00: 00: 00.3770377 Istnieje: 00: 00: 00.2490249”

Z rekordami 5 mln

„Dowolny: 00: 00: 00.0940094 Istnieje: 00: 00: 00.1420142”

Z rekordami 1 mln

„Dowolny: 00: 00: 00.0180018 Istnieje: 00: 00: 00.0090009”

Z 500k (zmieniłem też kolejność, w jakiej są oceniane, aby sprawdzić, czy nie ma żadnej dodatkowej operacji związanej z tym, który biegnie pierwszy).

„Istnieje: 00: 00: 00.0050005 Dowolny: 00: 00: 00.0100010”

Ze 100 000 rekordów

„Istnieje: 00: 00: 00.0010001 Dowolny: 00: 00: 00.0020002”

Wydaje Anysię być wolniejszy o 2 wielkości.

Edycja: W przypadku rekordów 5 i 10M zmieniłem sposób, w jaki generuje listę i Existsnagle zwolniłem, Anyco sugeruje, że coś jest nie tak w trakcie testowania.

Nowy mechanizm testowania:

private static IEnumerable<string> Generate(int count)
    {
        var cripto = new RNGCryptoServiceProvider();
        Func<string> getString = () => new string(
            Enumerable.Repeat("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 13)
                .Select(s =>
                {
                    var cryptoResult = new byte[4];
                    cripto.GetBytes(cryptoResult);
                    return s[new Random(BitConverter.ToInt32(cryptoResult, 0)).Next(s.Length)];
                })
                .ToArray());

        var list = new ConcurrentBag<string>();
        var x = Parallel.For(0, count, o => list.Add(getString()));
        return list;
    }

    private static void Test()
    {
        var list = Generate(10000000);
        var list1 = list.ToList();
        var forceListEval = list1.SingleOrDefault(o => o == "0123456789012");
        if (forceListEval != "sdsdf")
        {
            var s = string.Empty;

            var start1 = DateTime.Now;
            if (!list1.Any(o => o == "0123456789012"))
            {
                var end1 = DateTime.Now;
                s += " Any: " + end1.Subtract(start1);
            }

            var start2 = DateTime.Now;
            if (!list1.Exists(o => o == "0123456789012"))
            {
                var end2 = DateTime.Now;
                s += " Exists: " + end2.Subtract(start2);
            }

            if (!s.Contains("sdfsd"))
            {

            }
        }

Edit2: Ok, więc aby wyeliminować wpływ generowania danych testowych, zapisałem wszystko do pliku, a teraz go czytam.

 private static void Test()
    {
        var list1 = File.ReadAllLines("test.txt").Take(500000).ToList();
        var forceListEval = list1.SingleOrDefault(o => o == "0123456789012");
        if (forceListEval != "sdsdf")
        {
            var s = string.Empty;
            var start1 = DateTime.Now;
            if (!list1.Any(o => o == "0123456789012"))
            {
                var end1 = DateTime.Now;
                s += " Any: " + end1.Subtract(start1);
            }

            var start2 = DateTime.Now;
            if (!list1.Exists(o => o == "0123456789012"))
            {
                var end2 = DateTime.Now;
                s += " Exists: " + end2.Subtract(start2);
            }

            if (!s.Contains("sdfsd"))
            {
            }
        }
    }

10 mln

„Dowolny: 00: 00: 00.1640164 Istnieje: 00: 00: 00.0750075”

5 mln

„Dowolny: 00: 00: 00.0810081 Istnieje: 00: 00: 00.0360036”

1 mln

„Dowolny: 00: 00: 00.0190019 Istnieje: 00: 00: 00.0070007”

500 tys

„Dowolny: 00: 00: 00.0120012 Istnieje: 00: 00: 00.0040004”

wprowadź opis zdjęcia tutaj


3
Nie dyskredytuję cię, ale jestem sceptyczny wobec tych testów. Spójrz na liczby: w każdym wyniku ma miejsce rekurencja (3770377: 2490249). Przynajmniej dla mnie to pewny znak, że coś jest nie tak. Nie jestem w stu procentach pewny co do matematyki, ale myślę, że prawdopodobieństwo tego powtarzającego się wzoru wynosi 1 na 999 ^ 999 (lub 999! Może?) Na wartość. Zatem szansa, że ​​wydarzy się 8 razy z rzędu, jest nieskończenie mała. Myślę, że to dlatego, że używasz DateTime do testów porównawczych .
Jerri Kangasniemi

@JerriKangasniemi Powtarzanie tej samej operacji w izolacji powinno zawsze zajmować tyle samo czasu, to samo dotyczy wielokrotnego powtarzania. Co sprawia, że ​​mówisz, że to DateTime?
Matas Vaitkevicius

Oczywiście, że tak. Problemem jest nadal to, że jest bardzo mało prawdopodobne, aby zajęło na przykład 0120012 sekund dla połączeń na 500 tys. A jeśli byłoby idealnie liniowe, a więc tak ładnie wyjaśniało liczby, wywołania 1M zajęłyby 0240024 sekund (dwa razy więcej), ale tak nie jest. Połączenia 1M trwają 58, (3)% dłużej niż 500 tys., A 10M zajmuje 102,5% dłużej niż 5 mln. Więc nie jest to funkcja liniowa, a zatem nie jest rozsądne, aby liczby się powtarzały. Wspomniałem o DateTime, ponieważ sam miałem z nim problemy w przeszłości, ponieważ DateTime nie korzystał z precyzyjnych timerów.
Jerri Kangasniemi

2
@JerriKangasniemi Czy mogę zaproponować rozwiązanie tego problemu i opublikowanie odpowiedzi
Matas Vaitkevicius

1
Jeśli poprawnie czytam twoje wyniki, zgłosiłeś, że Any ma tylko około 2 do 3 razy większą prędkość niż Istnienie. Nie rozumiem, w jaki sposób dane nawet w niewielkim stopniu potwierdzają twoje twierdzenie, że „Wydawałoby się, że Dowolny byłby o 2 wielkości”. Z pewnością trochę wolniej, a nie rzędów wielkości.
Suncat2000

16

Jako kontynuacja odpowiedzi Matasa na temat testów porównawczych.

TL / DR : Exists () i Any () są równie szybkie.

Po pierwsze: Benchmarking przy użyciu stopera nie jest precyzyjny ( patrz odpowiedź series0ne na inny, ale podobny temat ), ale jest znacznie bardziej precyzyjny niż DateTime.

Sposobem na uzyskanie bardzo precyzyjnych odczytów jest profilowanie wydajności. Ale jednym ze sposobów, aby dowiedzieć się, w jaki sposób wydajność obu metod mierzy się względem siebie, jest wykonanie obu metod mnóstwo razy, a następnie porównanie najszybszego czasu wykonania każdej z nich. W ten sposób naprawdę nie ma znaczenia, że ​​JITing i inne szumy dają nam złe odczyty (i tak się dzieje ), ponieważ obie egzekucje są w pewnym sensie „ równie mylące ”.

static void Main(string[] args)
    {
        Console.WriteLine("Generating list...");
        List<string> list = GenerateTestList(1000000);
        var s = string.Empty;

        Stopwatch sw;
        Stopwatch sw2;
        List<long> existsTimes = new List<long>();
        List<long> anyTimes = new List<long>();

        Console.WriteLine("Executing...");
        for (int j = 0; j < 1000; j++)
        {
            sw = Stopwatch.StartNew();
            if (!list.Exists(o => o == "0123456789012"))
            {
                sw.Stop();
                existsTimes.Add(sw.ElapsedTicks);
            }
        }

        for (int j = 0; j < 1000; j++)
        {
            sw2 = Stopwatch.StartNew();
            if (!list.Exists(o => o == "0123456789012"))
            {
                sw2.Stop();
                anyTimes.Add(sw2.ElapsedTicks);
            }
        }

        long existsFastest = existsTimes.Min();
        long anyFastest = anyTimes.Min();

        Console.WriteLine(string.Format("Fastest Exists() execution: {0} ticks\nFastest Any() execution: {1} ticks", existsFastest.ToString(), anyFastest.ToString()));
        Console.WriteLine("Benchmark finished. Press any key.");
        Console.ReadKey();
    }

    public static List<string> GenerateTestList(int count)
    {
        var list = new List<string>();
        for (int i = 0; i < count; i++)
        {
            Random r = new Random();
            int it = r.Next(0, 100);
            list.Add(new string('s', it));
        }
        return list;
    }

Po wykonaniu powyższego kodu 4 razy (co z kolei daje 1 000 Exists()i Any()na liście zawierającej 1 000 000 elementów), nietrudno zauważyć, że metody są prawie równie szybkie.

Fastest Exists() execution: 57881 ticks
Fastest Any() execution: 58272 ticks

Fastest Exists() execution: 58133 ticks
Fastest Any() execution: 58063 ticks

Fastest Exists() execution: 58482 ticks
Fastest Any() execution: 58982 ticks

Fastest Exists() execution: 57121 ticks
Fastest Any() execution: 57317 ticks

Jest to niewielka różnica, ale jest zbyt mała różnica, aby nie być wyjaśnione przez szum tła. Domyślam się, że gdyby zrobić 10 000 lub 100 000, Exists()a Any()zamiast tego ta niewielka różnica zniknąłaby mniej więcej.


Czy mogę zasugerować wykonanie 10 000 i 100 000 i 1000000, żeby być metodycznym w tej sprawie, a także dlaczego wartość minimalna, a nie średnia?
Matas Vaitkevicius

2
Minimalna wartość wynika z tego, że chcę porównać najszybsze wykonanie (= prawdopodobnie najmniejszą ilość szumu tła) każdej metody. Mogę to zrobić z większą liczbą iteracji, chociaż będzie to później (wątpię, żeby mój szef chciał mi zapłacić za to, zamiast pracować nad zaległościami)
Jerri Kangasniemi

Zapytałem Paula Lindberga, a on powiedział, że jest w porządku;) w odniesieniu do minimum widzę twoje rozumowanie, jednak bardziej ortodoksyjnym podejściem jest użycie przeciętnej en.wikipedia.org/wiki/Alametermic_fficiency#Practice
Matas Vaitkevicius

9
Jeśli kod, który opublikowałeś, jest tym, który faktycznie wykonałeś, nie jest zaskakujące, że otrzymujesz podobne wyniki, ponieważ wywołujesz Exists w obu pomiarach. ;)
Simon Touchtech

Hej, tak, widziałem to też teraz, kiedy to mówisz. Jednak nie w mojej egzekucji. To miało być po prostu uproszczoną koncepcją tego, co porównywałem. : P
Jerri Kangasniemi

4

Dodatkowo zadziała to tylko wtedy, gdy wartość jest typu bool. Zwykle stosuje się to w predykatach. Jakikolwiek predykat byłby ogólnie używany do stwierdzenia, czy jest jakiś element spełniający dany warunek. Tutaj właśnie tworzysz mapę ze swojego elementu i na właściwość bool. Wyszukuje „i”, którego właściwość Value ma wartość true. Po zakończeniu metoda zwróci wartość true.


3

Kiedy poprawisz pomiary - jak wspomniano powyżej: Any i Exists, i dodając średnią - otrzymamy następujące dane wyjściowe:

Executing search Exists() 1000 times ... 
Average Exists(): 35566,023
Fastest Exists() execution: 32226 

Executing search Any() 1000 times ... 
Average Any(): 58852,435
Fastest Any() execution: 52269 ticks

Benchmark finished. Press any key.
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.