wake-up-neo.com

Für vs. Linq - Leistung vs. Zukunft

Sehr kurze Frage. Ich habe ein zufällig sortiertes großes String-Array (100K + Einträge), in dem ich das erste Vorkommen eines gewünschten Strings finden möchte. Ich habe zwei Lösungen. 

Nachdem ich gelesen habe, kann ich vermuten, dass die 'for-Schleife' momentan etwas bessere Leistung bringt (diese Marge könnte sich jedoch immer ändern), aber ich finde auch die linq-Version viel lesbarer. Welche Methode wird im Allgemeinen als die derzeit beste Kodierungspraxis angesehen und warum?

string matchString = "dsf897sdf78";
int matchIndex = -1;
for(int i=0; i<array.length; i++)
{
    if(array[i]==matchString)
    {
        matchIndex = i;
        break;
    }
}

oder

int matchIndex = array.Select((r, i) => new { value = r, index = i })
                         .Where(t => t.value == matchString)
                         .Select(s => s.index).First();
32
Jaqq

Die beste Vorgehensweise hängt davon ab, was Sie benötigen:

  1. Entwicklungsgeschwindigkeit und Wartbarkeit: LINQ
  2. Leistung (gemäß Profilierungswerkzeug): Manueller Code

LINQ verlangsamt sich wirklich mit der ganzen Umleitung. Machen Sie sich keine Sorgen, da 99% Ihres Codes die Endbenutzerleistung nicht beeinträchtigen.

Ich habe mit C++ angefangen und wirklich gelernt, wie man einen Code optimiert. LINQ ist nicht dazu geeignet, die CPU optimal zu nutzen. Wenn Sie also eine LINQ-Abfrage als Problem messen, geben Sie sie einfach ab. Aber nur dann.

Für Ihr Codebeispiel würde ich eine dreifache Verlangsamung schätzen. Die Allokationen (und nachfolgende GC!) ​​Und die Indirektionen durch die Lambdas tun wirklich weh.

46
usr

Etwas bessere Leistung? Eine Schleife wird erheblich bessere Leistung bringen!

Betrachten Sie den Code unten. Auf meinem System für einen RELEASE-Build (nicht zum Debuggen) gibt es:

Found via loop at index 999999 in 00:00:00.2782047
Found via linq at index 999999 in 00:00:02.5864703
Loop was 9.29700432810805 times faster than linq.

Der Code ist absichtlich so eingerichtet, dass sich das zu findende Objekt am Ende befindet. Wenn es am Anfang richtig wäre, wären die Dinge ganz anders.

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

namespace Demo
{
    public static class Program
    {
        private static void Main(string[] args)
        {
            string[] a = new string[1000000];

            for (int i = 0; i < a.Length; ++i)
            {
                a[i] = "Won't be found";
            }

            string matchString = "Will be found";

            a[a.Length - 1] = "Will be found";

            const int COUNT = 100;

            var sw = Stopwatch.StartNew();
            int matchIndex = -1;

            for (int outer = 0; outer < COUNT; ++outer)
            {
                for (int i = 0; i < a.Length; i++)
                {
                    if (a[i] == matchString)
                    {
                        matchIndex = i;
                        break;
                    }
                }
            }

            sw.Stop();
            Console.WriteLine("Found via loop at index " + matchIndex + " in " + sw.Elapsed);
            double loopTime = sw.Elapsed.TotalSeconds;

            sw.Restart();

            for (int outer = 0; outer < COUNT; ++outer)
            {
                matchIndex = a.Select((r, i) => new { value = r, index = i })
                             .Where(t => t.value == matchString)
                             .Select(s => s.index).First();
            }

            sw.Stop();
            Console.WriteLine("Found via linq at index " + matchIndex + " in " + sw.Elapsed);
            double linqTime = sw.Elapsed.TotalSeconds;

            Console.WriteLine("Loop was {0} times faster than linq.", linqTime/loopTime);
        }
    }
}
27
Matthew Watson

LINQ drückt laut deklarativem Paradigma die Logik einer Berechnung aus, ohne ihren Steuerfluss zu beschreiben. Die Abfrage ist zielorientiert, selbsterklärend und somit leicht zu analysieren und zu verstehen. Ist auch knapp. Darüber hinaus hängt die Verwendung von LINQ stark von der Abstraktion der Datenstruktur ab. Dies erfordert eine hohe Wartbarkeit und Wiederverwendbarkeit.

Der Iterationsansatz befasst sich mit dem imperativen Paradigma. Es bietet eine feinkörnige Kontrolle und ermöglicht so eine leichtere Leistung. Der Code ist auch einfacher zu debuggen. Manchmal ist eine gut strukturierte Iteration besser lesbar als eine Abfrage.

4
Ryszard Dżegan

Es gibt immer ein Dilemma zwischen Leistung und Wartbarkeit. Und in der Regel (wenn es keine besonderen Anforderungen an die Leistung gibt) sollte die Wartbarkeit gewinnen. Nur wenn Sie Leistungsprobleme haben, sollten Sie die Anwendung profilieren, die Problemquelle finden und deren Leistung verbessern (durch gleichzeitige Verringerung der Wartbarkeit, ja in dieser Welt leben wir).

Über Ihre Probe. Linq ist hier keine sehr gute Lösung, da in Ihrem Code keine Übereinstimmungswartbarkeit hinzugefügt wird. Eigentlich sieht das Projizieren, Filtern und Projizieren noch schlimmer aus als eine einfache Schleife. Was Sie hier brauchen, ist das einfache Array.IndexOf, das leichter zu warten ist als die Schleife und fast dieselbe Leistung aufweist:

Array.IndexOf(array, matchString)
2

Nun, Sie haben selbst auf Ihre Frage geantwortet.

Gehen Sie zu einer For-Schleife, wenn Sie die beste Leistung erzielen möchten, oder gehen Sie zu Linq, wenn Sie die Lesbarkeit wünschen.

Denken Sie auch an die Möglichkeit der Verwendung von Parallel.Foreach (), die von Inline-Lambda-Ausdrücken profitieren würde (also näher an Linq), und das ist viel besser lesbar als Paralellisierung "manuell".

2
dutzu

Ich denke nicht, dass beides als Best Practice gilt. Manche Leute ziehen LINQ vor und andere nicht.

Wenn Leistung ein Problem ist, würde ich beide Codeteile für Ihr Szenario profilieren, und wenn der Unterschied vernachlässigbar ist, gehen Sie zu dem, mit dem Sie sich konformer fühlen, schließlich werden Sie höchstwahrscheinlich nur Sie den Code verwalten.

Haben Sie darüber nachgedacht, PLINQ zu verwenden oder die Schleife parallel laufen zu lassen? 

1
Lee Dale

Ein bisschen nicht beantwortet und eigentlich nur eine Erweiterung von https://stackoverflow.com/a/14894589 , aber ich habe ein- und ausgeschaltet an einem API-kompatiblen Ersatz für gearbeitet Linq-to-Objects für eine Weile. Es bietet immer noch nicht die Leistung einer handcodierten Schleife, ist jedoch in vielen (den meisten?) Linux-Szenarien schneller. Es erzeugt mehr Müll und hat einige etwas höhere Vorlaufkosten.

Der Code ist verfügbar https://github.com/manofstick/Cistern.Linq

Ein Nuget-Paket ist verfügbar https://www.nuget.org/packages/Cistern.Linq/ (Ich kann nicht behaupten, dass dies kampferprobt ist. Die Verwendung erfolgt auf eigenes Risiko.)

Nimmt man den Code aus Matthew Watsons Antwort ( https://stackoverflow.com/a/14894589 ) mit zwei kleinen Anpassungen, wird die Zeit auf "nur" ~ 3,5-mal schlechter als die der Hand. codierte Schleife. Auf meinem Computer dauert es ungefähr 1/3 der Zeit der ursprünglichen System.Linq-Version.

Die zwei zu ersetzenden Änderungen:

using System.Linq;

...

matchIndex = a.Select((r, i) => new { value = r, index = i })
             .Where(t => t.value == matchString)
             .Select(s => s.index).First();

Mit den folgenden:

// a complete replacement for System.Linq
using Cistern.Linq;

...

// use a value Tuple rather than anonymous type
matchIndex = a.Select((r, i) => (value: r, index: i))
             .Where(t => t.value == matchString)
             .Select(s => s.index).First();

Die Bibliothek selbst ist also in Arbeit. Einige Edge-Fälle aus der System.Linq-Testsuite von corefx schlagen fehl. Außerdem müssen noch einige Funktionen konvertiert werden (sie haben derzeit die corefx System.Linq-Implementierung, die aus API-Sicht kompatibel ist, wenn nicht aus Performance-Sicht). Aber wer weiter helfen, kommentieren usw. möchte wäre dankbar ....

0
Paul Westcott

Die beste Option ist die Verwendung der IndexOf-Methode der Array-Klasse. Da es auf Arrays spezialisiert ist, ist es wesentlich schneller als Linq und For Loop.

using System;
using System.Diagnostics;
using System.Linq;


namespace PerformanceConsoleApp
{
    public class LinqVsFor
    {

        private static void Main(string[] args)
        {
            string[] a = new string[1000000];

            for (int i = 0; i < a.Length; ++i)
            {
                a[i] = "Won't be found";
            }

            string matchString = "Will be found";

            a[a.Length - 1] = "Will be found";

            const int COUNT = 100;

            var sw = Stopwatch.StartNew();

            Loop(a, matchString, COUNT, sw);

            First(a, matchString, COUNT, sw);


            Where(a, matchString, COUNT, sw);

            IndexOf(a, sw, matchString, COUNT);

            Console.ReadLine();
        }

        private static void Loop(string[] a, string matchString, int COUNT, Stopwatch sw)
        {
            int matchIndex = -1;
            for (int outer = 0; outer < COUNT; ++outer)
            {
                for (int i = 0; i < a.Length; i++)
                {
                    if (a[i] == matchString)
                    {
                        matchIndex = i;
                        break;
                    }
                }
            }

            sw.Stop();
            Console.WriteLine("Found via loop at index " + matchIndex + " in " + sw.Elapsed);

        }

        private static void IndexOf(string[] a, Stopwatch sw, string matchString, int COUNT)
        {
            int matchIndex = -1;
            sw.Restart();
            for (int outer = 0; outer < COUNT; ++outer)
            {
                matchIndex = Array.IndexOf(a, matchString);
            }
            sw.Stop();
            Console.WriteLine("Found via IndexOf at index " + matchIndex + " in " + sw.Elapsed);

        }

        private static void First(string[] a, string matchString, int COUNT, Stopwatch sw)
        {
            sw.Restart();
            string str = "";
            for (int outer = 0; outer < COUNT; ++outer)
            {
                str = a.First(t => t == matchString);

            }
            sw.Stop();
            Console.WriteLine("Found via linq First at index " + Array.IndexOf(a, str) + " in " + sw.Elapsed);

        }

        private static void Where(string[] a, string matchString, int COUNT, Stopwatch sw)
        {
            sw.Restart();
            string str = "";
            for (int outer = 0; outer < COUNT; ++outer)
            {
                str = a.Where(t => t == matchString).First();

            }
            sw.Stop();
            Console.WriteLine("Found via linq Where at index " + Array.IndexOf(a, str) + " in " + sw.Elapsed);

        }

    }

}

Ausgabe:

Found via loop at index 999999 in 00:00:01.1528531
Found via linq First at index 999999 in 00:00:02.0876573
Found via linq Where at index 999999 in 00:00:01.3313111
Found via IndexOf at index 999999 in 00:00:00.7244812
0
Nachiket Saggam