Freigeben über


Fallstudie: Leitfaden für Anfänger zur Optimierung von Code und Zur Reduzierung der Berechnungskosten (C#, Visual Basic, C++, F#)

Durch die Optimierung des Codes wird die Berechnungszeit und die Kosten reduziert. In dieser Fallstudie wird die Verwendung von Visual Studio-Profilerstellungstools zum Identifizieren und Beheben von Leistungsproblemen in einer .NET-Beispielanwendung veranschaulicht. Wenn Sie Profilerstellungstools vergleichen möchten, lesen Sie Welches Tool sollte ich auswählen?

In diesem Leitfaden werden folgende Themen behandelt:

  • Verwenden von Visual Studio-Profilerstellungstools zum Analysieren und Verbessern der Leistung.
  • Praktische Strategien zur Optimierung der CPU-Auslastung, Speicherzuweisung und Datenbankinteraktionen.

Wenden Sie diese Techniken an, um Ihre eigenen Anwendungen effizienter zu gestalten.

Optimierungsfallstudie

Die .NET-Beispielanwendung führt Abfragen für eine SQLite-Datenbank mit Blogs und Beiträgen mithilfe von Entity Framework aus. Es führt viele Abfragen aus und simuliert ein reales Datenabrufszenario. Die App basiert auf dem Beispiel für die ersten Schritte von Entity Framework, verwendet jedoch ein größeres Dataset.

Zu den wichtigsten Leistungsproblemen gehören:

  • Hohe CPU-Auslastung: Ineffiziente Berechnungen oder Verarbeitungsaufgaben erhöhen den CPU-Verbrauch und die Kosten.
  • Ineffiziente Speicherzuordnung: Schlechte Speicherverwaltung führt zu übermäßiger Garbage Collection und reduzierter Leistung.
  • Datenbankaufwand: Ineffiziente Abfragen und übermäßige Datenbankaufrufe beeinträchtigen die Leistung.

In dieser Fallstudie werden Visual Studio-Profilerstellungstools zum Anheften und Beheben dieser Probleme verwendet, um die Anwendung effizienter und kostengünstiger zu gestalten.

Herausforderung

Das Beheben dieser Leistungsprobleme erfordert mehrere Herausforderungen:

  • Diagnoseengpässe: Das Identifizieren von Ursachen für hohen CPU-, Arbeitsspeicher- oder Datenbankaufwand erfordert eine effektive Verwendung von Profilerstellungstools und eine korrekte Interpretation der Ergebnisse.
  • Wissen und Ressourceneinschränkungen: Profilerstellung und Optimierung erfordern spezifische Fähigkeiten und Erfahrungen, die möglicherweise nicht immer verfügbar sind.

Ein strategischer Ansatz, der Profilerstellungstools, technische Kenntnisse und sorgfältige Tests kombiniert, ist unerlässlich, um diese Herausforderungen zu überwinden.

Strategie

Hier ist eine generelle Übersicht der Vorgehensweise in dieser Fallstudie:

  • Beginnen Sie mit einer CPU-Auslastungsablaufverfolgung mit dem CPU-Auslastungstool von Visual Studio. Das Tool für die CPU-Auslastung von Visual Studio ist ein guter Ausgangspunkt für Leistungsuntersuchungen.
  • Sammeln zusätzlicher Traces zur Analyse der Speicherauslastung und Datenbankanalyse:

Für die Datensammlung sind die folgenden Aufgaben erforderlich:

  • Legen Sie die App auf release build fest.
  • Wählen Sie das CPU-Nutzungstool im Performance Profiler (ALT+F2) aus.
  • 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 Link " Details öffnen " im Bericht.

Screenshot des Öffnens von Details im CPU-Auslastungswerkzeug.

Öffnen Sie in der Berichtsdetailsansicht die Anrufstrukturansicht . Der Codepfad mit der höchsten CPU-Auslastung in der App wird als hot pathbezeichnet. Das Symbol "Hot Path-Flamme" (Screenshot mit dem Symbol ) kann dazu beitragen, Leistungsprobleme schnell zu erkennen, die möglicherweise verbessert 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 zur Gesamt-CPU schließt der Self-CPU-Wert die in anderen Funktionen aufgewendete Zeit aus, sodass wir wissen, dass wir tiefer im Aufrufbaum nach dem tatsächlichen Engpass suchen müssen.

Screenshot der Ansicht

GetBlogTitleX führt externe Aufrufe an zwei LINQ-DLLs durch, die die meiste CPU-Zeit verwenden, wie durch die sehr hohen Selbst-CPU-Werte belegt. Dies ist der erste Hinweis darauf, dass eine LINQ-Abfrage möglicherweise ein Bereich ist, der optimiert werden kann.

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

Um eine visualisierte Anrufstruktur und eine andere Ansicht der Daten zu erhalten, öffnen Sie die Flame Graph-Ansicht . (Oder klicken Sie mit der rechten Maustaste GetBlogTitleX , und wählen Sie "In Flammendiagramm anzeigen" aus.) Hier sieht es wieder so aus, als ob die GetBlogTitleX Methode für die CPU-Auslastung der App verantwortlich ist (in Gelb dargestellt). Externe Aufrufe der LINQ-DLLs werden unterhalb des GetBlogTitleX Felds angezeigt, und sie verwenden die gesamte CPU-Zeit für die Methode.

Screenshot der Ansicht

Sammeln zusätzlicher Daten

Häufig können andere Tools zusätzliche Informationen bereitstellen, um die Analyse zu unterstützen und das Problem zu isolieren. In dieser Fallstudie gehen wir wie folgt vor:

  • Sehen Sie sich zunächst die Speicherauslastung an. Es kann eine Korrelation zwischen hoher CPU-Auslastung und hoher Arbeitsspeicherauslastung geben, daher kann es hilfreich sein, beides zu betrachten, um das Problem zu isolieren.
  • Da wir die LINQ-DLLs identifiziert haben, sehen wir uns auch das Datenbanktool an.

Überprüfen der Speicherauslastung

Um zu sehen, was mit der App im Hinblick auf die Speicherauslastung passiert, sammeln wir eine Ablaufverfolgung mithilfe des .NET Object Allocation-Tools (Für C++ können Sie stattdessen das Tool für die Speicherauslastung verwenden). Die Anrufstrukturansicht in der Speicherablaufverfolgung zeigt den heißen Pfad an und hilft uns, einen Bereich mit hoher Speicherauslastung zu identifizieren. Keine Überraschung an diesem Punkt, die GetBlogTitleX Methode scheint viele Objekte zu generieren! Über 900.000 Objektzuweisungen tatsächlich.

Screenshot der Aufrufbaumansicht im .NET-Objektzuweisungstool.

Die meisten erstellten Objekte sind Zeichenfolgen, Objektarrays und Int32s. Möglicherweise können wir sehen, wie diese Typen durch Untersuchen des Quellcodes generiert werden.

Überprüfen der Abfrage im Datenbanktool

Im Performance Profiler wählen wir das Datenbanktool anstelle der CPU-Auslastung (oder beides) aus. Nachdem eine Ablaufverfolgung erfasst wurde, öffnen Sie auf der Diagnoseseite die Registerkarte Abfragen. Auf der Registerkarte "Abfragen" für die Datenbankablaufverfolgung können Sie sehen, dass die erste Zeile die längste Abfrage mit 2446 ms anzeigt. In der Spalte "Datensätze " wird gezeigt, wie viele Datensätze die Abfrage liest. Sie können diese Informationen für einen späteren Vergleich verwenden.

Screenshot der Datenbankabfragen im Datenbanktool.

Durch Untersuchen der SELECT von LINQ in der Spalte "Abfrage" generierten Anweisung identifizieren wir die erste Zeile als die Abfrage, die der GetBlogTitleX Methode zugeordnet ist. Um die vollständige Abfragezeichenfolge anzuzeigen, erweitern Sie 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 hier viele Spaltenwerte abruft, vielleicht mehr als wir benötigen. Sehen wir uns den Quellcode an.

Optimieren von Code

Es ist an der Zeit, sich den GetBlogTitleX Quellcode anzusehen. Klicken Sie im Datenbanktool mit der rechten Maustaste auf die Abfrage, und wählen Sie "Zur Quelldatei wechseln" aus. Im Quellcode für GetBlogTitleXfinden 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 beliebigen Blogs mit "Fred Smith" als Autor zu durchsuchen. Wenn Sie dies betrachten, können Sie sehen, 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, die in den Beiträgen enthalten sind, z. B. Blog-ID.

Wir recherchieren ein wenig und finden einige häufige Empfehlungen für die Optimierung von LINQ-Abfragen. Alternativ können wir Zeit sparen und Copilot die Forschung für uns durchführen lassen.

Wenn wir Copilot verwenden, wählen wir "Copilot fragen" aus dem Kontextmenü 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 gibt Copilot die folgenden vorgeschlagenen Codeänderungen zusammen mit einer Erläuterung.

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}");
    }
}

Dieser Code enthält mehrere Änderungen, um die Abfrage zu optimieren:

  • Die Where Klausel wurde hinzugefügt, und eine der foreach Schleifen wurde entfernt.
  • Projiziert nur die Title-Eigenschaft in der Anweisung Select. Das ist alles, was wir in diesem Beispiel brauchen.

Als Nächstes testen wir die Profilerstellungstools erneut.

Ergebnisse

Nach dem Aktualisieren des Codes führen wir das CPU-Leistungstool erneut aus, um eine Aufzeichnung zu erstellen. Die Anrufstrukturansicht zeigt, dass GetBlogTitleX nur 1754 ms läuft und 37% der gesamten CPU-Leistung der App verwendet, was eine erhebliche Verbesserung gegenüber 59%darstellt.

Screenshot einer verbesserten CPU-Auslastung in der Aufrufstruktur-Ansicht des CPU-Auslastungstools.

Wechseln Sie zur Flame Graph-Ansicht , um eine weitere Visualisierung anzuzeigen, die die Verbesserung zeigt. In dieser Ansicht nutzt GetBlogTitleX auch einen kleineren Teil der CPU.

Screenshot der verbesserten CPU-Auslastung in der Flame Graph-Ansicht des Tools für die CPU-Auslastung.

Überprüfen Sie die Ergebnisse im Datenbank-Tool-Trace, und es werden nur zwei Datensätze mit dieser Abfrage gelesen, statt 100.000! Außerdem ist die Abfrage stark vereinfacht und macht das zuvor generierte LEFT JOIN überflüssig.

Screenshot der schnelleren Abfragezeit im Datenbanktool.

Als Nächstes überprüfen wir die Ergebnisse im .NET Object Allocation-Tool und sehen, dass GetBlogTitleX nur für 56.000 Objektzuweisungen verantwortlich ist, fast eine Reduzierung von 95% von 900.000!

Screenshot der reduzierten Speicherzuweisungen im .NET-Objektzuweisungstool.

Durchlaufen

Möglicherweise sind mehrere Optimierungen erforderlich, und wir können weiterhin Codeänderungen iterieren, um zu sehen, welche Änderungen die Leistung verbessern und dazu beitragen, die Rechenkosten zu reduzieren.

Nächste Schritte

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