Sag schlechtes Wort für LINQ

Während meines kurzen Lebens als C # -Programmierer dachte ich, dass es bei LINQ nicht in erster Linie um die Leistung des Codes geht, sondern um die Leistung des Programmierers, wie schnell er den Code schreibt und nicht wie schnell der Prozessor diesen Code ausführt. Und die Leistungsprobleme sowohl des Codes als auch des Programmierers werden behoben, nachdem die Engpässe identifiziert wurden. Daher schreibe var groupedByDate = lst.GroupBy(item => item.IssueTime.Date).Select(…).ToList()und mache ich dies oft nicht aufgrund von Schaden oder böswilliger Absicht, aber es ist einfach einfacher, den Code zu debuggen. Platzieren Sie für die Liste einfach den Mauszeiger über dem Text der Variablen und Sie können sofort sehen, ob etwas vorhanden ist oder nicht.


Start


Nachdem ich den Artikel „ Erfolgloser Artikel über das Beschleunigen der Reflexion “ und das Beruhigen von Emotionen über „Jemand ist im Internet falsch “ gelesen hatte , fragte ich mich, ob es möglich ist, den Code „LINQ to Objects“ in der Leistung nahe an „manuell“ zu bringen. Warum genau LINQ zu Objekten? In meiner Arbeit verwende ich häufig nur die Anbieter LINQ to Objects und LINQ to Entities. Die Leistung von LINQ to Entities ist für mich nicht kritisch. Es ist wichtig, wie optimal die generierte SQL-Abfrage für den Zieldatenbankserver ist und wie schnell der Server sie ausführt.


Als Grundlage habe ich mich für das Projekt des Autors des Artikels entschieden. Im Gegensatz zu den Beispielen aus dem Internet, bei denen ein Code hauptsächlich wie integerList.Where(item => item > 5).Sum()ein „manueller“ Code verglichen wird foreach, der ifusw. enthält , erschien mir das Beispiel aus dem Artikel interessant und wichtig.


Als erstes habe ich eine einzelne String-Vergleichsfunktion verwendet. Im Quellcode werden in Methoden, die dieselbe Funktion ausführen, sich jedoch in gegenüberliegenden Ecken des Rings befinden, in einem Fall variable0.Equals(constant0, StringComparison.InvariantCultureIgnoreCase)und in einem anderen Fall unterschiedliche Methoden verwendet variable0.ToUpperInvariant() ==constant0.ToUpperInvariant(). Es ist besonders ärgerlich für die Konstante, dass jeder Methodenaufruf in Großbuchstaben konvertiert wird. Ich habe in beiden Fällen die dritte Option gewählt variable0.ToUpperInvariant() ==constant0InUpperCaseInvariant.


Dann wurde der gesamte Code verworfen, der nicht direkt mit dem Vergleich der LINQ-Leistung und des manuellen Codes zusammenhängt. Der erste, der abgefangen wurde, war der Code, der das Klassenobjekt erstellt und initialisiert Mock<HabraDbContext>. Was bringt es, für jeden Test einmal eine Datenbankverbindung herzustellen? Es wurde über Leistungstests hinaus verschoben.


IStorage _db;

[GlobalSetup]
public void Setup()
{
    _db = MockHelper.InstanceDb();
}

private IStorage DBInstance => _db;

… — , ! Linq «» !


Bild


« - » . LINQ . , — .



, , LINQ vs «» . . . .


, , , ([Params(1, 10, 100, 1000)]). . . . StatisticColumnRelStdDev.


FastHydrationLinq, ManualHydrationLinq — . , , (Fast)(Manual)(Slow)HydrationLinq vs (Fast)(Manual)(Slow)Hydration, ManualHydrationLinq. FastHydrationLinq - . .


ToArray, ToDictionary IEnumerable<T> . , FastContactHydrator2. - Action<Contact, string> c ConcurrentDictionary<string, Action<Contact, string>> IEnumerable<KeyValuePair<string, Action<Contact, string>>>. GetContact2, Sum, .


protected override Contact GetContact2(IEnumerable<PropertyToValueCorrelation> correlations)
{
    var contact = new Contact();
    int dummySum = _propertySettersArray.Join(correlations, propItem => propItem.Key, corrItem => corrItem.PropertyName, (prop, corr) => { prop.Value(contact, corr.Value); return 1; }).Sum();
    return contact;
}

ParseWithLinq


public static IEnumerable<KeyValuePair<string, string>> ParseWithLinq2(string rawData, string keyValueDelimiter = ":", string pairDelimiter = ";")
    => rawData.Split(pairDelimiter)
    .Select(x => x.Split(keyValueDelimiter, StringSplitOptions.RemoveEmptyEntries))
    .Select(x => x.Length == 2 ? new KeyValuePair<string, string>(x[0].Trim(), x[1].Trim())
                                : new KeyValuePair<string, string>(_unrecognizedKey, x[0].Trim()));

.


FastContactHydrator2, , , , -, ( ParseWithLinq ParseWithoutLinq). . , , ToArray. 10 var result = new List<PropertyToValueCorrelation>(10). 10? 42 , 10 . Fair .


. . GetFakeData . , .
, , « » (RelStdDev). N=1000.


:


-  `ManualHydration`, `SlowHydration`, `FastHydration`, `ManualHydrationLinq`, `SlowHydrationLinq`, `FastHydrationLinq` -     ,  ;

  • ManualHydrationFair, ManualHydrationFairLinq, FastHydrationFairLinq — , ;
  • FastHydrationLinq2 — , , - , LINQ;
  • N , ;
  • Ratio FastHydrationLinq2.

Bild
Linq-


? — FastHydrationLinq2 ManualHydrationLinq Ratio. 26,361.37 μs 26,422.17 μs N=1000. / N. ManualHydrationFairLinq, 8% , . FastHydrationFairLinq, 1% -.


Fair-. , ManualHydrationFairLinq 8% . FastHydrationFairLinq FastHydrationLinq 12%. Linq .


, . . , MockHelper.InstanceDb() Setup, . — , GetFakeData, . , — . MockHelper.InstanceDb() .


N, MakeGarbage. False , True — .


Bild


N

image


Voilà, , . — N=1. 10% 82% .


… .



, , «» — Linq-, :


Bild
N=1 MakeGarbage=True «»


, , , LINQ .
«Gen X» «Allocated» N=1 MakeGarbage=True o , FastHydrationLinq2


|                                |      Gen 0 |   Gen 1 | Gen 2 |   Allocated |
|«»     |    20.5078 |  0.4883 |     - |    63.95 KB |
|Linq-        |    20.7520 |       - |     - |    63.69 KB |

ManualHydrationFairLinq .


. , , — .
, ? . , N [Params(1, 100)] MakeGargabe=True. «» — Linq-, . — , — Linq-, Linq — — — Linq. — . .


, , — Linq- . , . paint.net, . paint.net — paint.net . paint.net — «» , — Linq- .


N 1, 10, 100, 1000 MakeGarbage [ParamsAllValues]. paint.net, — «» . — Linq- . paint.net Visual Studio — «» . , 80- . , Linq- 2%.



Nachdem ich die Tests geschrieben und die Ergebnisse analysiert habe, hat sich meine Meinung zu LINQ nicht geändert. Wenn ich es verwende, ist meine Produktivität beim Schreiben von Code höher als ohne. Die Leistung von LINQ to Objects ist in Ordnung. Die verzögerte Ausführung von LINQ-Code als Mittel zur Leistungsverbesserung ist wenig sinnvoll.


Wenn in einem Projekt Leistungsprobleme aufgrund des Garbage Collectors auftreten - der .net-Geburtsverletzung -, war die Wahl dieser Technologie wahrscheinlich nicht die beste Lösung.


Mein Testcode ist hier verfügbar .


All Articles