2 Punkte von GN⁺ 2024-10-20 | 1 Kommentare | Auf WhatsApp teilen

-.NET 9.0 verkürzt in mehreren allgemeinen LINQ-Szenarien die Ausführungszeit gegenüber .NET 8 deutlich und beseitigt in einigen Benchmarks sogar Allokationen

  • Eine der zentralen Verbesserungen ist, dass beim Iterieren über Arrays oder List<T> mit TryGetSpan() ein ReadOnlySpan<T> geholt wird, um die Kosten der Iteration zu senken
  • TryGetSpan() erkennt TSource[] und List<TSource> per Typvergleich, aber die Methode, für List<T> einen Span auf das interne Array zu holen, ist eine Unsafe-basierte Optimierung, die bei Kapazitätsänderungen ungültig werden kann
  • Das LINQ in .NET 9 erkennt gängige Aufrufketten, erstellt dafür spezielle Iteratoren und wendet in terminalen Methoden wie Count(), First(), Last(), ElementAt() und Sum() zusätzliche Optimierungen an
  • Schon durch einfache Migration und Neukompilierung lassen sich einige LINQ-Performancegewinne erzielen; außerdem sind Optimierungen wie SIMD-Nutzung und frühes Erkennen leerer Sequenzen enthalten

Warum Arrays und Listen schneller iteriert werden

  • Der erste Benchmark speichert Enumerable.Range(1, 10_000).ToArray() als IEnumerable<int> und führt dann Count, All, Any, First, Single und Last aus, um .NET 8 und .NET 9 zu vergleichen
  • Verwendet wird BenchmarkDotNet; das Projekt sollte auf net8.0;net9.0 zielen und im Release-Modus kompiliert werden
  • In .NET 9 sinkt bei mehreren Methoden die Laufzeit deutlich, und Allokationen verschwinden
    • LinqCount: von 16,198.490 ns auf 3,043.563 ns, von 32 B Allokation auf keine Allokation
    • LinqAny: von 17,096.735 ns auf 2,483.927 ns, von 32 B Allokation auf keine Allokation
    • LinqFirst: von 15,289.747 ns auf 2,243.341 ns, von 32 B Allokation auf keine Allokation
    • LinqSingle: von 21,684.114 ns auf 4,884.329 ns, von 32 B Allokation auf keine Allokation
    • LinqAll: von 10.588 ns auf 2.562 ns, von 32 B Allokation auf keine Allokation
    • LinqLast: von 15.967 ns auf 6.918 ns

Den Unterschied macht TryGetSpan()

  • Ein Hauptgrund für den Performancegewinn ist die Verwendung von TryGetSpan()
  • Wenn das zu durchlaufende Enumerable ein Array oder eine Liste ist, gibt TryGetSpan() ein ReadOnlySpan<T> zurück und ermöglicht dadurch schnellere Iteration
  • Der zentrale Verzweigungscode prüft source.GetType() == typeof(TSource[]) oder source.GetType() == typeof(List<TSource>) und holt dann den Span
    • Arrays werden mit Unsafe.As<TSource[]>(source) verarbeitet
    • Listen verwenden CollectionsMarshal.AsSpan(Unsafe.As<List<TSource>>(source)), um einen Span auf das interne Array zu holen
  • Im Code wird source.GetType() zweimal aufgerufen und nicht per Cast mit anschließender Nullprüfung gearbeitet, aber .NET-Performanceexperten haben diesen Ansatz mit Blick auf Optimierungen des C#-Compilers und des JIT gewählt
  • In einem hochoptimierten .NET-Stack können Mikro-Optimierungen zu Ergebnissen führen, die der äußeren Erscheinung widersprechen

Einschränkungen von CollectionsMarshal.AsSpan()

  • List<TSource> referenziert intern ein Array, und wenn die Kapazität der Liste wächst oder schrumpft, wird ein neues Array erzeugt und referenziert
  • CollectionsMarshal.AsSpan(Unsafe.As<List<TSource>>(source)) holt ein Span<TSource> aus diesem internen Array
  • Wenn sich die Kapazität der Liste auf irgendeine Weise ändert, kann das so ermittelte Array ungültig werden
  • Wegen dieser Einschränkung können sich einige Enumerable-Operationen mit verzögerter Iteration wie yield nur schwer auf diese Optimierung stützen
  • Schon der Name System.Runtime.CompilerServices.Unsafe macht dieses Risiko deutlich

Wo TryGetSpan() aufgerufen wird

  • Mit NDepend wurde System.Linq.dll gescannt, um direkte und indirekte Aufrufer von TryGetSpan() zu ermitteln
  • Der analysierte Assembly-Pfad lautet C:\Program Files\dotnet\shared\Microsoft.NETCore.App\9.0.0-rc.1.24431.7\System.Linq.dll
  • Von TryGetSpan() aus wurde eine Code-Abfrage erstellt, um Aufrufer zu identifizieren, und 56 passende Methoden wurden in einen Abhängigkeitsgraphen exportiert
  • Viele Standardmethoden von Enumerable versuchen Span-Iteration, wenn die Collection ein Array oder eine Liste ist
  • Da das Festhalten am internen Array einer Liste jedoch nicht sicher ist, bleiben für Operationen mit notwendiger verzögerter Ausführung Einschränkungen bestehen

Optimierung auf Basis spezieller Iteratoren

  • Der zweite Benchmark verwendet Beispiele aus dem PR Consolidate LINQ’s internal IIListProvider/IPartition into base Iterator class
  • Getestet werden unter anderem Distinct().First(), Append().Select().Last(), Reverse().Count(), DefaultIfEmpty().Select().ElementAt(), Skip().Take().ElementAt(), Union().First() und Select().Where().Select().Sum()
  • In .NET 9 werden einige Aufrufketten extrem viel schneller
    • DistinctFirst: von 65.318 ns auf 11.192 ns, von 328 B Allokation auf keine Allokation
    • AppendSelectLast: von 4,122.007 ns auf 2.661 ns, von 144 B Allokation auf keine Allokation
    • DefaultIfEmptySelectElementAt: von 4,090.818 ns auf 5.724 ns, von 144 B Allokation auf keine Allokation
    • RangeUnionFirst: von 66.309 ns auf 6.193 ns, von 344 B Allokation auf keine Allokation
    • ListSkipTakeElementAt: von 6.268 ns auf 2.916 ns
    • RangeReverseCount: von 11.024 ns auf 6.134 ns
  • Umgekehrt wurde SelectWhereSelectSum langsamer: von 3,959.622 ns unter .NET 8 auf 4,460.008 ns unter .NET 9; auch die Allokation von 112 B bleibt bestehen

Erkennung häufiger LINQ-Ketten

  • Das .NET-Performance-Team hat den Code so entworfen, dass häufige LINQ-Aufrufketten erkannt werden
  • Wird eine bestimmte Kette erkannt, wird ein spezieller Iterator erzeugt, der den Ablauf effizienter verarbeitet
  • Endet die Kette mit Methoden wie Count(), First(), Last(), ElementAt() oder Sum(), sind zusätzliche Optimierungen möglich
  • So kann zum Beispiel OrderBy(criteria).First() so optimiert werden, dass es wie Min(criteria) ausgeführt wird

Struktur von Iterator<T> und abgeleiteten Klassen

  • Innerhalb von LINQ gibt es die abstrakte Basisklasse Iterator<T> und 40 abgeleitete Klassen
  • Diese Klassen sind alle in die Klasse Enumerable geschachtelt
  • Iterator<T> ist zwar eine abstrakte Klasse, die Methoden sind jedoch virtual, sodass abgeleitete Klassen nur die jeweils benötigten Methoden überschreiben
  • Diese Struktur bildet die Grundlage dafür, spezialisiertes Verhalten pro Aufrufkette abzulegen

Beispiel ListWhereSelectIterator<TSource, TResult>

  • ListWhereSelectIterator<TSource, TResult> verarbeitet die Kette Where(...).Select(...) auf Listen mit einem einzigen Iterator
  • Dieser Iterator wird im Select()-Override von ListWhereIterator<TSource, TResult> erzeugt
  • ListWhereIterator<TSource> wird erstellt, wenn Enumerable.Where() prüft, ob die Quelle ein List<TSource> ist
  • ListWhereSelectIterator<TSource, TResult> überschreibt keine Methoden wie TryGetFirst() oder TryGetLast()
  • Der Kern des Performancegewinns ist, dass die auf Listen sehr häufige Kette Where(...).Select(...) nicht mit zwei Iteratoren, sondern mit einem einzigen Iterator zusammengeführt wird
    • In MoveNext() werden die beiden Delegates _predicate und _selector gemeinsam aufgerufen

Beispiel IListSkipTakeIterator<TSource>

  • IListSkipTakeIterator<TSource> ist ein spezieller Iterator, der erzeugt wird, wenn dies möglich ist
  • MoveNext() verwendet _state - 1 als nullbasierten Index der Liste
  • Ein separates Indexfeld wäre besser lesbar, aber um die Größe der Iterator-Felder zu verringern, wird der Wert mit Bias in _state gespeichert
  • Die Optimierung dieses Iterators besteht darin, keine unnötigen Elemente außerhalb des Bereichs _minIndexInclusive bis _maxIndexInclusive zu durchlaufen

Zusätzliche Optimierungen allein durch Migration

  • In .NET 9 werden viele allgemeine LINQ-Szenarien schneller
  • Um von den Verbesserungen der neuen .NET-Version zu profitieren, sind nur Migration und Neukompilierung nötig
  • LINQ wird auch auf andere Weise optimiert
    • Beim Summieren von Integer-Sequenzen wird, wo möglich, SIMD verwendet
    • Leere Sequenzen werden früh erkannt, wodurch die Kosten der Enumeration sinken
  • DeepDotnet videos können als .NET-Lernmaterial mit Scott Hanselman und Stephen Toub angesehen werden

1 Kommentare

 
GN⁺ 2024-10-20
Hacker-News-Kommentare
  • Der nützlichste Teil von LINQ sind meiner Ansicht nach nicht die auf Syntaxbäumen basierende Erweiterungsstruktur von IQueryable oder die in die Sprache eingebaute Syntax, sondern die Erweiterungsmethoden von IEnumerable
    Früher wurden sie etwas verwirrend als „LINQ to Objects“ bezeichnet, und sie ermöglichen es, C# in einem funktionalen Stil knapp zu schreiben
    Der Originalartikel behandelt hauptsächlich die Optimierung dieser Erweiterungsmethoden
    Richtig nachvollziehen konnte ich diesen Ansatz erst, nachdem ich Haskell gelernt hatte; dabei teilt er auch einige der Vor- und Nachteile von Haskell, etwa die verzögerte Auswertung
    Wenn man es gedankenlos verwendet, kann daraus schwer lesbarer und langsamer Code werden, daher würde ich es nicht empfehlen, wenn im Team niemand grundlegende funktionale Idiome und Lazy Evaluation versteht

    • Ich bevorzuge ebenfalls den funktionalen Aspekt der LINQ-Erweiterungen für IEnumerable und IQueryable
      Es ist leichter, darüber zu schlussfolgern, und auch wenn es in Dingen wie Entity Framework nicht immer die schnellste Wahl ist, ist es meistens eine ziemlich gute Option
      Ich nutze auch lieber Dapper als EF
      Allerdings neigen C#-Projekte dazu, absurd viele Abstraktionsschichten anzuhäufen, und „Enterprise“-Entwicklung ist meistens schwer anzusehen
    • So nutze ich LINQ ebenfalls
      Die Benennung ist an einigen Stellen etwas unüblich, aber es hat alles, was man braucht
      Eric Lippert hat eine hervorragende Artikelreihe geschrieben, in der er Monaden anhand von LINQ erklärt: https://ericlippert.com/2013/04/02/monads-part-twelve/
    • Ich habe bei LINQ immer nur die Methodensyntax verwendet
      Ich mag es nicht, wenn innerhalb der Hostsprache noch eine weitere „eingebaute“ Sprache steckt, und das Endergebnis muss am Ende ohnehin wieder auf C# zurückgeführt werden
      Auch wenn man keine ORMs wie Entity Framework oder Dapper verwendet, ist es besser, Datenzugriffslogik einschließlich SQL in einem separaten abstrahierten Projekt unterzubringen
      So verteilt sie sich nicht über die ganze Anwendung, und wenn irgendwann ein anderes RDBMS nötig wird, kann man es austauschen
      In 20 Jahren ist das in der Praxis allerdings nur einmal passiert
      Wenn Junior-Entwickler LINQ verwenden, hilft es, einen Profiler und Debugger dazuzunehmen, damit sie verstehen, was intern passiert
      Manchmal ist es auch sinnvoll, sie zuerst eine Lösung mit for-Schleifen und normaler C#-Logik schreiben zu lassen und diese dann mit einer LINQ-Implementierung zu vergleichen, damit sie die Vor- und Nachteile beider Ansätze sehen
    • Wenn man Haskell mag, könnte einem auch eine andere Anwendung gefallen, etwa das Zusammenstellen von Kombinator-Parsers mithilfe der LINQ-Abfragesyntax
      Die Abfragesyntax ist nicht fest auf IEnumerable verdrahtet; das ist nur das Standardverhalten, und man kann sie fast überall einsetzen
      Sie funktioniert ein wenig ähnlich wie Operator Overloading
      [1]: https://github.com/acple/ParsecSharp/blob/da8d0cb9ec39e28dd9...
    • Wenn die LINQ-Syntax morgen verschwinden würde, würde ich sie kaum vermissen, aber funktionale Komposition ist wirklich mächtig und auch leichter wartbar
  • Ich verstehe nicht, warum das dotnet-Team nicht mehr Ressourcen und Zeit in die Tooling-Landschaft investiert
    Es braucht doctest und Dokumentationsgenerierung, bessere und schnellere Unit-Tests direkt neben dem eigentlichen Code, besseren Zugang zum Source Code, eine Umgebung, in der man mit F12 nicht erst DLLs dekompilieren muss, sowie einen zentralen Hub für Pakete und Dokumentation wie pkg.go.dev oder docs.rs
    Die meisten NuGet-Pakete haben entweder gar keine Dokumentation, nur ein GitHub-README oder höchstens ein kurzes Wiki
    In solchen Punkten sind andere Umgebungen wie Rust, Go, Java und Python Lichtjahre voraus

    • Das führt zu dem Scherz, Microsoft habe in OpenAI investiert, weil das die einzige halbwegs sinnvolle Methode sei, sich durch die Dokumentation von .NET-/NuGet-Paketen zu navigieren
      Erschreckenderweise habe ich aber auch das Gefühl, dass das ziemlich nah an der Wahrheit ist
    • In der Microsoft-Dokumentation gibt es inzwischen Links, die direkt zum Source Code der gerade betrachteten Methode führen
      Beispiel: https://learn.microsoft.com/en-us/dotnet/api/system.string.s...
      Für NuGet-Paketquellen ist das mit aktiviertem Source Link ebenfalls leicht möglich, aber das ist noch eine relativ neue Funktion, daher ist sie noch nicht in allen Paketen umgesetzt
    • Ich stimme zu, aber ein möglicher Grund ist auch, dass Open-Source-C# noch eine vergleichsweise junge Entwicklung ist
      Der Großteil des C#-Codes dürfte noch immer in Unternehmen als Closed Source geschrieben werden
      Wenn Microsoft den offenen Kurs der letzten Jahre beibehält, wird es mit der Zeit wohl besser werden
      Einige dieser Funktionen werden von Tools wie Resharper bereitgestellt, und ich frage mich, ob es eine ausdrückliche oder stillschweigende Übereinkunft gibt, sich nicht gegenseitig ins Revier zu kommen
      Ehrlich gesagt war der Großteil der Dokumentation, die ich in C#-Projekten gesehen habe, von geringer Qualität, sodass ich am Ende doch den Source Code lese
      Meine Erfahrung ist, dass viele Autocomplete-Tools beim Lesen kaum helfen und nur beim Schreiben nützlich sind
    • Sandcastle Help File Builder gibt es schon seit sehr langer Zeit; soweit ich mich erinnere, begann es als internes Microsoft-Projekt, aber erstaunlich wenige Bibliotheken nutzen es
      https://github.com/EWSoftware/SHFB
    • Es gibt auch eine Möglichkeit, Tests direkt neben den Code zu schreiben: https://clipperhouse.com/go-test-csharp/
      Ob ich das empfehlen würde, weiß ich nicht
      Ich habe es selbst ausprobiert und später wieder rückgängig gemacht; offenbar wurden die Tests langsamer, möglicherweise wegen schlechterem Caching der Build-Artefakte
  • Treffender wäre wohl nicht „LINQ-Performanceverbesserungen“, sondern „Performanceverbesserungen der eigenen List-Implementierung“
    Microsoft scheint seine Zeit eher darauf zu verwenden, die Teile zu verbessern, die es selbst braucht, statt allgemeine Verbesserungen voranzutreiben
    LINQ, insbesondere die Abfragesyntax und nicht nur Methodenerweiterungen, braucht Investitionen
    Vor allem sind weniger Lambda-Allokationen nötig und, wenn möglich, eine Reduzierung von Lambdas bereits zur Compile-Zeit
    Es braucht Strategien wie lokale Lambdas als Werttypen oder zumindest Ansätze, bei denen Lambda-Allokationen nicht weiter so viel Overhead verursachen wie heute
    Auch LINQ-Variablen sollten inzwischen Wildcards (_) unterstützen, aber das wurde bei der Einführung für Lambdas komplett ignoriert
    Außerdem sollte man als letztes Element eines LINQ-Ausdrucks statt select ... auch geliftete Typen wie IEnumerable oder Option verwenden können
    In bestimmten Anwendungsfällen erzeugt select unnötigen Overhead und schränkt auch Dinge wie LINQ-Ausdrücke mit Endrekursion ein
    Bibliotheken wie meine, die voll auf LINQ setzen, aber weder IEnumerable, IQueryable noch LINQ-Erweiterungen verwenden, werden weiterhin ignoriert
    Der Grund ist, dass Microsoft sich nur auf Performanceverbesserungen für die eigenen Projekte konzentriert
    Ein gutes Beispiel dafür ist die verbesserte Lambda-Inferenz
    Sie wurde vorgezogen, weil sie für die Minimal API von ASP.NET Core gebraucht wurde
    Ein erheblicher Teil der Sprach- und Framework-Features scheint eher von Microsofts internen Anforderungen getrieben zu sein als von den Bedürfnissen der Community
    Am schlimmsten ist, dass die Menge an magischen Methoden immer weiter wächst, nicht nur bei LINQ-Erweiterungen wie Select, SelectMany und Where, sondern auch bei Dingen wie GetAwaiter
    Statt die Magie durch wirklich notwendige Eigenschaften höherer Kinds aufzulösen, fügt Microsoft eher Features für sich selbst hinzu, vor allem für den Compiler
    Deshalb bleibt alles schwach typisiert, und der Compiler kann es nur ungefähr erkennen
    LINQ ist eines der zentralen Unterscheidungsmerkmale zwischen Sprachen, wurde aber seit C# 3 fast völlig vernachlässigt
    Es ist wirklich schade, dass LINQ noch immer nur als nützlich für Listeniteration angesehen wird, insbesondere für die Iteration über die eigene Listenimplementierung
    Für die Performanceverbesserungen an sich bin ich dankbar, und sie werden vielen Nutzern helfen, aber der Fokus ist immer so eng gesetzt, dass das Potenzial begrenzt bleibt
    [1] https://github.com/louthy/language-ext/

    • Wenn du nützliches Feedback hast, wäre es gut, ein Issue in dotnet/runtime zu eröffnen oder einen PR zu schicken
      Ein großer Teil der im Artikel behandelten LINQ-Performanceverbesserungen ist genau auf diesem Weg eingeflossen
    • Die Bibliothek wirkt sehr interessant, aber sie scheint auch in einer Form angelegt zu sein, die leicht schon im Vorfeld ignoriert wird
      Es gibt viele using-Anweisungen, was für Menschen, die verstehen, wie man Projekte in kleinere Einheiten aufteilt und Verantwortlichkeiten trennt, kein großes Problem ist
      Die meisten Entwickler strukturieren ihre Projekte aber nicht so, und solche Kleinigkeiten können für durchschnittliche Entwickler eine Hürde sein
      Gerade Junior-Entwickler tun sich oft schon mit der Standard-LINQ-Syntax und den Methoden schwer, insbesondere auch bei der Performance
      Gut, dass das im README erwähnt wird
      Oft sind Leute nur damit beschäftigt, eine Bibliothek zu „verkaufen“, aber mir gefällt, dass hier tatsächlich steht, worin ihre Stärken liegen und wofür sie gedacht ist
      Auch der Hinweis, dass sie unidiomatisch ist, kann für Leute, die C#/.NET lernen, ein Problem sein
      Microsoft wird vermutlich wollen, dass Tools und Sprache bestimmten Konventionen folgen, und eine Benennung, die sich natürlicher an funktionaler Programmierung orientiert, könnte aus Microsofts Sicht ein ziemlich großes Hindernis sein, wenn über Verbesserungen nachgedacht wird
      Ich habe dem Repository einen Stern gegeben und großes Interesse an dem, was du gebaut hast
      In einigen größeren Anwendungen, die ich kürzlich erstellt habe, habe ich einen Result-Typ verwendet, der Option grob zu ähneln scheint
      Als ich mir die Bibliothek dann erneut ansah, hatte ich das Gefühl, dass ich zwar dachte, über funktionale Programmierung ziemlich viel zu wissen, es in Wirklichkeit aber doch nicht tue
      In C# bin ich ziemlich gut und habe auch komplexe Dinge umgesetzt, aber funktionale Programmierung ist trotz vieler Lektüre immer noch schwer zu verinnerlichen, und F# For Fun And Profit war für mich noch am verständlichsten
      Das heißt am Ende nicht, dass du etwas falsch machst
      Microsoft wird sich an durchschnittlichen oder eher unerfahrenen Entwicklern orientieren, weil sie den Großteil des eigenen Ökosystems ausmachen
      Ich hoffe, dass diese Bibliothek interne Verbesserungen bekommen kann
      Man sieht, wie viel Zeit hineingeflossen ist, und allein die Zahl der GitHub-Sterne ist ein starkes Signal dafür, dass es Menschen gibt, die sie tatsächlich verwenden und davon profitieren
      Falls es so klang, als würde ich das abwerten, tut mir das leid, aber die Arbeit ist interessant, und es sieht so aus, als gäbe es genug Dokumentation, um unbekannte Konzepte langsam zu lernen
  • Je mehr C# aus F# übernimmt, desto besser
    Ich warte darauf, dass discriminated unions endlich in C# kommen, damit man Domain-Modeling richtig machen kann

    • Interessant, dass man so etwas in der .NET-Community oft hört
      Jedes Mal denke ich dann: Warum nicht einfach F# verwenden?
      C# hat über Jahre hinweg nur aufgeholt
      Wenn F# viele Innovationen im .NET-Ökosystem vorangetrieben hat und bei Features jahrelang voraus war, frage ich mich, warum man diese Arbeit nicht durch tatsächliche Nutzung belohnt
      Wenn man eine bestimmte Entwicklungsrichtung für Sprachen möchte, sollte man sie durch echte Entscheidungen fördern
      Im Java-Ökosystem hat das auf diese Weise funktioniert, und inzwischen wird auch Java besser
      Wenn der Markt wächst, kann ein positiver Rückkopplungseffekt entstehen, bei dem auch mehr Engineering-Aufwand investiert wird
      Wenn man über Jahre Foren liest, hat man im C#-Lager oft den starken Eindruck, dass die Leute „einfach“ in ihrem Lager bleiben und warten wollen
      Es wirkt fast ein wenig tribalistisch, als wäre ihr Team einfach „C#“
      In anderen Sprachökosystemen sehe ich diese Kultur kaum, und ich habe den Eindruck, dass F# längst aufgeblüht wäre, wenn es nicht Teil von .NET, sondern eines anderen Ökosystems wäre
    • Maßeinheitentypen hätte ich auch wirklich gern
      Das würde die Wartbarkeit von Ingenieur- und Wissenschaftscode deutlich verbessern
    • Mit OneOf[0] und Dunet[1] lassen sich discriminated unions schon jetzt ziemlich leicht einführen
      Ein praktisches Beispiel: https://chrlschn.dev/blog/2024/07/csharp-discriminated-union...
      [0] https://github.com/mcintyre321/OneOf
      [1] https://github.com/domn1995/dunet
    • Dass F# das Experimentierfeld für Features in C# und VB.NET ist, ist ein offenes Geheimnis, und auch offizielle Figuren wie Hanselman haben das mehrfach so gesagt
  • Wenn ich in anderen Sprachen oder Ökosystemen arbeite, vermisse ich am meisten LINQ.
    So eine Funktionalität in der Standardbibliothek zu haben, ist wirklich großartig, und sie ist innerhalb der gegebenen Einschränkungen elegant entworfen.

  • Es gibt einen entsprechenden Abschnitt in einem jährlichen, buchlangen Beitrag, der alle Performance-Verbesserungen in .NET 9 behandelt.
    https://devblogs.microsoft.com/dotnet/performance-improvemen...
    Seltsamerweise erlaubt HN keine erneute Einreichung, daher schaffte es der Beitrag nicht auf die Startseite und ging unter.

  • Wenn man sich einmal an LINQ gewöhnt hat und gewöhnlich in einem Bereich arbeitet, in dem LINQ glänzt, will man nicht mehr zu anderen Ansätzen zurück.

    • Freunde, werdet nicht abhängig von LINQ.
      LINQ wird euch festhalten, und ihr werdet Umgebungen ohne LINQ verfluchen.
    • Trotzdem ist es weniger mächtig als so etwas wie polars.
  • Ich frage mich, ob es ein gutes umfassendes Buch oder Tutorial gibt, um End-to-End-Webentwicklung mit dotnet zu lernen.
    Das meiste, was ich gefunden habe, war entweder zu oberflächlich, veraltet oder von geringer Qualität.

    • Das neue und gerade angesagte Thema in der .NET-Webentwicklung ist Blazor, aber außerhalb der Microsoft-Blogosphäre ist es nicht besonders populär, und das wird vermutlich auch so bleiben.
      Persönlich denke ich, dass es einen ähnlichen Weg wie Silverlight gehen wird.
      Auch ältere Technologien sind in .NET 9 weiterhin vorhanden, funktionieren und werden gepflegt.
      Heutzutage bedeutet Webentwicklung mit .NET meistens, HTTP/JSON/REST-APIs zu bauen und sie mit einem Frontend-Framework nach Wahl zu verbinden.
      Ich verwende dafür React oder NextJS.
      Als Suchbegriffe eignen sich ASP.NET WebApi oder moderner ASP.NET Minimal API.
      Weiterhin ist auch serverseitiges Rendering mit .NET MVC unter Verwendung von Razor möglich.
      Da es sich um die Markup-Sprache von ASP.NET MVC handelt, sollte man nach „ASP.NET MVC Razor“ suchen.
    • Ich habe mich in letzter Zeit für Webentwicklung mit C# interessiert.
      Ob gut oder schlecht, ASP.NET scheint praktisch die offensichtliche Antwort darauf zu sein, wie man Webanwendungen in .NET erstellt.
      Dass es nur wenige Alternativen gibt, wirkt allerdings etwas verdächtig.
      Ich habe einen Podcast mit Andrew Lock, dem Autor von „ASP.NET Core in Action“, gehört, und er wirkte wie jemand, der das Thema wirklich gut kennt.
      Ich habe das Buch noch nicht gelesen, aber es könnte genau das sein, wonach du suchst.
      1: https://dotnetcore.show/season-6/navigating-the-aspnet-core-...
      2: https://www.manning.com/books/asp-net-core-in-action-third-e...
    • Etwas abseits des Mainstreams, aber die Kombination aus F# und Fable ist sehr mächtig.
      Auf dem Server kann man Giraffe auf ASP.NET laufen lassen, eine funktionale Programmierschicht mit ähnlicher Performance wie C#.
      Im Frontend kann man React in einer echten funktionalen Programmiersprache schreiben.
      Natürlich kann man auch F#-Code zwischen Frontend und Backend teilen.
    • Ich habe es durch Ausprobieren gelernt, aber hier sind ein paar nützliche Quellen.
      Als Bücher gibt es Mark J Price, „C# 12 and .NET 8 - Modern Cross-Platform Development Fundamentals - Eighth Edition: Start building websites and services with ASP.NET Core 8, Blazor, and EF Core 8“, sowie Xiaodi Yan, „Web API Development with ASP.NET Core 8: Learn techniques, patterns, and tools for building high-performance, robust, and scalable web APIs“.
      Als Tutorials gibt es die Serien von IAmTimCorey und Shawn Wildermuth auf YouTube.
    • Für serverseitig gerenderte UIs sollte man nach Material zu Razor suchen und am Anfang Material zu Blazor besser meiden.
      Wenn man .NET-Backend plus JS-Frontend kombinieren will, sollte man nach Material zu Minimal API suchen.
      MVC ist auch gut, aber wegen der vielen Altlasten bei der Abwärtskompatibilität wurde Minimal API überhaupt erst eingeführt.
  • Es muss doch einen besseren Weg geben als diesen Kommentar-Nudelhaufen.
    Jedes Mal, wenn ich modernen .NET-Code sehe, tun mir die Augen weh.

    • Diese Attribute entsprechen der im Artikel verwendeten Benchmarking-Bibliothek.
      Unit-Tests und Benchmarking-Code sehen meistens zwangsläufig etwas nach Spaghetti aus.
      Trotzdem würde ich einen PR mit so etwas im eigentlichen Business-Code nicht durchwinken.
      Wenn du es wirklich hasst, kannst du auch so etwas wie AspNetCore verwenden, ohne auch nur ein einziges Attribut anzufassen.
    • Ich weiß nicht, welchen .NET-Code du dir ansiehst.
      Ich verwende fast nie Attribute.
  • Der Punkt, dass bei Methoden wie Count(), First(), Last(), ElementAt() und Sum() am Ende einer Chain mehr Optimierungen möglich sind und dass zum Beispiel OrderBy(criteria).First() so optimiert werden kann, dass es sich wie Min(criteria) verhält, kann nützlich sein.
    Aber eigentlich sollte man von vornherein besseren Code schreiben.
    Für dynamisch erzeugte Chains ist das vielleicht interessant, aber wenn man solche Operationen in selbst geschriebenem Code hat, fühlt es sich wie eine schiefe Form positiver Verstärkung an.
    Die Bibliothek erkennt ineffiziente Muster und korrigiert sie für einen.
    Ich hoffe zumindest auf Feedback, das eine Verbesserung des zugrunde liegenden Codes vorschlägt.