Freigeben über


Fallstudie: Leitfaden für Anfänger zum Optimieren von Code und zum Senken von Rechenkosten (C#, Visual Basic, C++, F#)

Wenn Sie Ihre Computezeit verringern, können Sie Kosten reduzieren. Durch die Optimierung Ihres Codes können Sie also Geld sparen. In dieser Fallstudie wird eine Beispielanwendung verwendet, die Leistungsprobleme aufweist. Es soll veranschaulicht werden, wie Sie Profilerstellungstools zur Steigerung der Effizienz verwenden können. Wenn Sie Profilerstellungstools vergleichen möchten, lesen Sie Welches Tool sollte ich auswählen?

In dieser Fallstudie werden die folgenden Themen behandelt:

  • Die Bedeutung der Optimierung von Code und wie sie sich auf die Reduzierung der Computekosten auswirkt.
  • Verwenden von Visual Studio-Profilerstellungstools, um die Anwendungsleistung zu analysieren.
  • Interpretieren von Daten, die diese Tools bereitstellen, um Leistungsengpässe zu identifizieren.
  • Anwenden von praktischen Strategien zum Optimieren von Code mit Fokus auf die CPU-Auslastung, die Arbeitsspeicherzuweisung und auf Datenbankinteraktionen.

Folgen Sie diesen Ratschlägen und wenden Sie diese Techniken anschließend auf Ihre eigenen Anwendungen an, um sie effizienter und kostengünstiger zu gestalten.

Fallstudie zur Optimierung

Die in dieser Fallstudie untersuchte Beispielanwendung ist eine .NET-Anwendung, die Abfragen in einer Blog-Datenbank und zugehörigen Blogbeiträgen ausführt. Dabei wird das Entity Framework verwendet – eine beliebte objektrelationale Zuordnung (Object-Relational Mapping, ORM) für .NET, um mit einer lokalen SQLite-Datenbank zu interagieren. Die Anwendung ist so strukturiert, dass eine große Anzahl von Abfragen ausgeführt und ein reales Szenario simuliert wird, in dem möglicherweise eine .NET-Anwendung erforderlich ist, um umfangreiche Datenabrufaufgaben zu verarbeiten. Bei der Beispielanwendung handelt es sich um eine abgeänderte Version von Beispiel für die ersten Schritte mit Entity Framework.

Das ausschlaggebende Problem, das die Beispielanwendung im Hinblick auf die Leistung aufweist, bezieht sich auf die Art und Weise, wie sie Computeressourcen verwaltet und mit der Datenbank interagiert. Die Anwendung weist einen Leistungsengpass auf, der ihre Effizienz und folglich die mit der Ausführung verbundenen Computekosten stark beeinträchtigt. Im Zusammenhang mit diesem Problem können folgende Symptome beobachtet werden:

  • Hohe CPU-Auslastung: Anwendungen können ineffiziente Berechnungen oder Verarbeitungsaufgaben durchführen, wobei unnötig viele CPU-Ressourcen verbraucht werden. Dies kann zu langsamen Reaktionszeiten und höheren Betriebskosten führen.

  • Ineffiziente Speicherzuweisung: Anwendungen können manchmal Probleme im Zusammenhang mit der Arbeitsspeicherauslastung und -zuordnung aufweisen. In .NET-Apps kann die ineffiziente Verwaltung von Arbeitsspeichern zu einer erhöhten Garbage Collection führen, was sich wiederum negativ auf die Anwendungsleistung auswirken kann.

  • Gemeinkosten für die Datenbankintegration: Anwendungen, die für eine Datenbank zahlreiche Abfragen ausführen, können Engpässe im Zusammenhang mit Datenbankinteraktionen verursachen. Hierzu gehören ineffiziente Abfragen, übermäßige Datenbankaufrufe und eine schlechte Verwendung von Entity Framework-Funktionen, die die Leistung verringern können.

In dieser Fallstudie soll dieses Problem mithilfe der Profilerstellungstools von Visual Studio behoben werden, die dazu dienen, die Leistung der Anwendung zu analysieren. Wenn Entwickler*innen verstehen, wo und wie die Leistung der Anwendung verbessert werden kann, können sie Optimierungen implementieren, um die CPU-Auslastung zu reduzieren, die Effizienz bei der Speicherzuweisung zu steigern, Datenbankinteraktionen zu optimieren und die Ressourcennutzung zu optimieren. Das ultimative Ziel ist es, die Gesamtleistung der Anwendung zu verbessern, sodass sie effizienter und kostengünstiger ausgeführt werden kann.

Herausforderung

Das Beheben der Leistungsprobleme in der .NET-Beispielanwendung bringt verschiedene Herausforderungen mit sich. Diese Herausforderungen sind auf die komplexe Diagnose von Leistungsengpässen zurückzuführen. Im Folgenden sind die größten Herausforderungen aufgeführt, die die Behebung der beschriebenen Probleme mit sich bringen kann:

  • Diagnose von Leistungsengpässen: Eine der größten Herausforderungen besteht in der genauen Identifizierung der Ursachen der Leistungsprobleme. Eine hohe CPU-Auslastung, eine ineffiziente Arbeitsspeicherzuweisung und ein Mehraufwand im Zusammenhang mit Datenbankinteraktionen können mehrere Einflussfaktoren haben. Developer müssen Profilerstellungstools effektiv verwenden, um diese Probleme zu diagnostizieren. Dies erfordert ein gewisses Verständnis davon, wie diese Tools funktionieren und wie ihre Ausgabe interpretiert werden kann.

  • Wissens- und Ressourceneinschränkungen: Schlussendlich kann es vorkommen, dass Teams nicht genügend Wissen, Fachwissen und Ressourcen haben. Die Profilerstellung und Optimierung einer Anwendung erfordert spezifische Fähigkeiten und Erfahrungen. Jedoch verfügen möglicherweise nicht alle Teams über sofortigen Zugriff auf diese Ressourcen.

Um diese Herausforderungen zu bewältigen, bedarf es eines strategischen Ansatzes, dessen Fokus auf der effektiven Verwendung der Profilerstellungstools, auf dem technischen Wissen, auf einer sorgfältigen Planung und auf Tests liegt. Das Ziel dieser Fallstudie ist es, Entwickler*innen durch diesen Prozess zu führen, Strategien und Erkenntnisse bereitzustellen, um diese Herausforderungen zu überwinden und die Leistung der Anwendung zu verbessern.

Strategie

Hier ist ein allgemeiner Überblick über den Ansatz in dieser Fallstudie:

  • Wir starten die Untersuchung mit einer Ablaufverfolgung der CPU-Auslastung. Das CPU-Auslastungstool von Visual Studio eignet sich oft dazu, um mit der Untersuchung der Leistung zu beginnen und Code zu optimieren, um die Kosten zu reduzieren.
  • Um weitere Erkenntnisse zu erhalten, die dabei helfen, Probleme zu isolieren oder die Leistung zu verbessern, erfassen wir als Nächstes eine Ablaufverfolgung mit einem der anderen Profilerstellungstools. Beispiel:
    • Werfen wir einen Blick auf die Arbeitsspeicherauslastung. Für .NET probieren wir zuerst das .NET-Tool für Objektzuordnungen aus. (Für .NET oder C++ können Sie sich stattdessen das Speicherauslastungstool ansehen.)
    • Für ADO.NET oder Entity Framework können wir das Datenbank-Tool verwenden, um SQL-Abfragen, die genaue Abfragezeit und mehr zu untersuchen.

Um Daten zu erfassen, müssen die folgenden Aufgaben ausgeführt werden:

  • Konfigurieren der App auf einen Releasebuild.
  • Auswählen des CPU-Auslastungstools aus dem Performance Profiler (Alt+F2). (Spätere Schritte umfassen einige der anderen Tools.)
  • Starten Sie die App im Leistungsprofiler, und erfassen Sie eine Ablaufverfolgung.

Untersuchen von Bereichen mit hoher CPU-Auslastung

Nachdem wir die Ablaufverfolgung mithilfe des Tools für die CPU-Auslastung erfasst und in Visual Studio geladen haben, überprüfen wir die anfängliche .diagsession-Berichtseite, auf der die zusammengefassten Daten angezeigt werden. Verwenden Sie den im Bericht angezeigten Link Details öffnen.

Screenshot: Öffnen von Details im CPU-Nutzungstool.

Öffnen Sie in der Berichtdetailansicht die Ansicht Aufrufstruktur. Der heiße Pfad zeigt den Codepfad mit der höchsten CPU-Auslastung in der App an. Mithilfe des Flammensymbols für den „heißen Pfad“ (Screenshot des Symbols für den „heißen Pfad“.) können Leistungsfehler, die sich möglicherweise beheben lassen, schnell identifiziert werden.

In der Aufrufstruktur-Ansicht sehen Sie eine hohe CPU-Auslastung für die GetBlogTitleX-Methode in der App, die etwa 60 % der CPU-Auslastung der App ausmacht. Allerdings ist der Eigen-CPU-Wert für GetBlogTitleX niedrig, nur etwa 0,10 %. Im Gegensatz zu CPU gesamt schließt der Eigen-CPU-Wert die in anderen Funktionen verbrachte Zeit aus, sodass wir wissen, dass der eigentliche Engpass weiter unten in der Ansicht „Aufrufstruktur“ zu finden ist.

Screenshot: Ansicht „Aufrufstruktur“ im CPU-Auslastungstool

GetBlogTitleX führt externe Aufrufe an zwei LINQ-DLLs aus, die die meiste CPU-Zeit beanspruchen, wie aus den sehr hohen Eigen-CPU-Werten ersichtlich ist. Dies ist der erste Hinweis darauf, dass die LINQ-Abfrage optimiert werden müsste.

Screenshot der Aufrufstrukturansicht im Tool CPU-Auslastung mit hervorgehobener eigener CPU.

Um die Aufrufstruktur zu visualisieren und eine andere Ansicht der Daten zu erhalten, öffnen Sie die Ansicht Flammendiagramm. (Oder klicken Sie mit der rechten Maustaste auf GetBlogTitleX und wählen Sie im Flammendiagramm anzeigen aus.) Auch hier sieht es so aus, als sei die GetBlogTitleX-Methode größtenteils für die CPU-Auslastung der App verantwortlich (gelb dargestellt). Externe Aufrufe der LINQ-DLLs werden unter dem GetBlogTitleX-Feld angezeigt, und sie verwenden die gesamte CPU-Zeit für die Methode.

Screenshot: Ansicht „Flammendiagramm“ im CPU-Auslastungstool

Sammeln weiterer Daten

Häufig können andere Tools zusätzliche Informationen bereitstellen, um die Analyse zu unterstützen und das Problem einzugrenzen. In dieser Fallstudie folgen wir dem nachstehenden Ansatz:

  • Sehen Sie sich zunächst die Arbeitsspeicherauslastung an. Möglicherweise besteht ein Zusammenhang zwischen einer hohen CPU-Nutzung und einer hohen Speicherauslastung, so dass es hilfreich sein kann, beide zu untersuchen, um das Problem einzugrenzen.
  • Da wir die LINQ-DLLs identifiziert haben, sehen wir uns auch das Datenbanktool an.

Überprüfen der Speicherauslastung

Um die Speicherauslastung der App zu überprüfen, erfassen wir mithilfe des Tools für die .NET-Objektzuordnung eine Ablaufverfolgung (für C++ können Sie stattdessen das Speicherauslastungstool verwenden). Die Aufrufstruktur-Ansicht in der Speicherablaufverfolgung zeigt den „heißen Pfad“ an und hilft uns, einen Bereich mit einer hohen Speicherauslastung zu identifizieren. Keine Überraschung: Die GetBlogTitleX-Methode scheint viele Objekte zu generieren! Tatsächlich über 900.000 Objektzuordnungen.

Screenshot: Ansicht „Aufrufstruktur“ im .NET-Objektzuordnungstool

Die meisten erstellten Objekte sind Zeichenketten, Objektarrays und Int32-Objekte. Wir sehen möglicherweise, wie diese Typen generiert werden, indem wir den Quellcode untersuchen.

Überprüfen der Abfrage im Datenbanktool

Im Performance Profiler wählen wir das Datenbank-Tool anstelle der CPU-Auslastung aus (oder wir wählen beides aus). Nachdem eine Ablaufverfolgung erfasst wurde, öffnen Sie auf der Diagnoseseite die Registerkarte Abfragen. Auf der Registerkarte „Abfragen“ für die Datenbankablaufverfolgung sehen Sie in der ersten Zeile die längste Abfrage, 2446 ms. Die Spalte Datensätze zeigt an, wie viele Datensätze die Abfrage liest. Sie können diese Informationen für einen späteren Vergleich heranziehen.

Screenshot: Datenbankabfragen im Datenbanktool

Durch Untersuchen der SELECT von LINQ generierten Anweisung in der Spalte „Abfrage“ identifizieren wir die erste Zeile als die Abfrage, die der GetBlogTitleX-Methode zugeordnet ist. Um die vollständige Abfragezeichenfolge anzuzeigen, erweitern wir die Spaltenbreite. Die vollständige Abfragezeichenfolge lautet:

SELECT "b"."Url", "b"."BlogId", "p"."PostId", "p"."Author", "p"."BlogId", "p"."Content", "p"."Date", "p"."MetaData", "p"."Title"
FROM "Blogs" AS "b" LEFT JOIN "Posts" AS "p" ON "b"."BlogId" = "p"."BlogId" ORDER BY "b"."BlogId"

Beachten Sie, dass die App viele Spaltenwerte abruft, vielleicht mehr als wir benötigen. Sehen wir uns den Quellcode an.

Code optimieren

Es ist an der Zeit, einen Blick auf den GetBlogTitleX-Quellcode zu werfen. Klicken Sie im Datenbanktool mit der rechten Maustaste auf die Abfrage, und wählen Sie Zur Quelldatei wechseln aus. Im Quellcode für GetBlogTitleX finden wir den folgenden Code, der LINQ zum Lesen der Datenbank verwendet.

foreach (var blog in db.Blogs.Select(b => new { b.Url, b.Posts }).ToList())
  {
    foreach (var post in blog.Posts)
    {
      if (post.Author == "Fred Smith")
      {
        Console.WriteLine($"Post: {post.Title}");
      }
  }
}

Dieser Code verwendet foreach-Schleifen, um die Datenbank nach Blogs mit „Fred Smith“ als Autor zu durchsuchen. Bei der Betrachtung sehen Sie, dass viele Objekte im Arbeitsspeicher generiert werden: ein neues Objektarray für jeden Blog in der Datenbank, zugeordnete Zeichenfolgen für jede URL und Werte für Eigenschaften der Beiträge, z. B. Blog-ID.

Wir recherchieren ein wenig und finden einige allgemeine Empfehlungen zum Optimieren von LINQ-Abfragen und erstellen diesen Code.

Tipp

Alternativ können wir Zeit sparen und Copilot mit der Recherche beauftragen.

foreach (var x in db.Posts.Where(p => p.Author.Contains("Fred Smith")).Select(b => b.Title).ToList())
{
  Console.WriteLine("Post: " + x);
}

In diesem Code haben wir mehrere Änderungen vorgenommen, um die Abfrage zu optimieren:

  • Fügen Sie die Where-Klausel hinzu, und entfernen Sie eine der foreach-Schleifen.
  • Projiziert nur die Title-Eigenschaft in der Anweisung Select. Das ist alles, was wir in diesem Beispiel brauchen.

Als Nächstes führen wir einen erneuten Test mit den Profilerstellungstools durch.

Optimieren von Code mit Copilot

Wenn wir Copilot verwenden, können wir ihn bitten, die Leistungsprobleme für uns zu untersuchen. Wählen Sie im Kontextmenü Copilot bitten aus und geben Sie die folgende Frage ein:

Can you make the LINQ query in this method faster?

Tipp

Sie können Slash-Befehle wie /optimize verwenden, um gute Fragen für Copilot zu bilden.

In diesem Beispiel schlägt Copilot die folgenden Codeänderungen vor (ähnlich unserer optimierten Abfrage) und liefert Erläuterungen dazu.

public void GetBlogTitleX()
{
    var posts = db.Posts
        .Where(post => post.Author == "Fred Smith")
        .Select(post => post.Title)
        .ToList();

    foreach (var postTitle in posts)
    {
        Console.WriteLine($"Post: {postTitle}");
    }
}

Ergebnisse

Nachdem der Code aktualisiert wurde führen wir das CPU-Auslastungstool erneut aus, um eine Ablaufverfolgung zu erfassen. Die Ansicht Aufrufstruktur zeigt, dass GetBlogTitleX nur 1.754 ms lang ausgeführt wird, wobei 37 % der gesamten CPU-Zeit der App verwendet werden, eine deutliche Verbesserung gegenüber 59 %.

Screenshot: Verbesserte CPU-Auslastung in der Ansicht „Aufrufstruktur“ des CPU-Auslastungstools

Wechseln Sie in die Flammendiagramm-Ansicht, um eine weitere Visualisierung der Verbesserung anzuzeigen. In dieser Ansicht verwendet GetBlogTitleX auch einen kleineren Teil der CPU.

Screenshot: Verbesserte CPU-Auslastung in der Ansicht „Flammendiagramm“ des CPU-Auslastungstools

Überprüfen Sie die Ergebnisse in der Datenbanktool-Ablaufverfolgung, und mit dieser Abfrage werden nur zwei Datensätze gelesen, anstatt 100.000! Außerdem ist die Abfrage stark vereinfacht und macht das zuvor generierte LEFT JOIN überflüssig.

Screenshot: Schnellere Abfragezeit im Datenbanktool

Als Nächstes überprüfen wir erneut die Ergebnisse im .NET-Objektzuordnungstool und stellen fest, dass GetBlogTitleX nur für 56.000 Objektzuordnungen verantwortlich ist, was im Vergleich zu 900.000 Zuordnungen einer Reduzierung von fast 95 % entspricht!

Screenshot: Reduzierte Speicherzuweisungen im .NET-Objektzuordnungstool

Durchlaufen

Es sind möglicherweise mehrere Optimierungen erforderlich und wir können weiterhin mit Codeänderungen iterieren, um zu sehen, welche Änderungen die Leistung verbessern und zur Reduzierung der Computekosten beitragen.

Nächste Schritte

Die folgenden Artikel und Blogbeiträge enthalten weitere Informationen, die Ihnen helfen, die Visual-Studio-Leistungstools effektiv zu verwenden.