Udostępnij za pośrednictwem


Analiza przypadku: Przewodnik dla początkujących dotyczący optymalizowania kodu i obniżania kosztów obliczeń (C#, Visual Basic, C++, F#)

Skrócenie czasu obliczeniowego oznacza zmniejszenie kosztów, dzięki czemu optymalizacja kodu może zaoszczędzić pieniądze. W tym badaniu przypadku użyto przykładowej aplikacji z problemami z wydajnością, aby zademonstrować sposób używania narzędzi profilowania w celu zwiększenia wydajności. Jeśli chcesz porównać narzędzia profilowania, zobacz Jakie narzędzie należy wybrać?

W tym badaniu przypadku omówiono następujące tematy:

  • Znaczenie optymalizacji kodu i jego wpływu na obniżenie kosztów obliczeń.
  • Jak używać narzędzi profilowania programu Visual Studio do analizowania wydajności aplikacji.
  • Sposób interpretowania danych dostarczonych przez te narzędzia w celu zidentyfikowania wąskich gardeł wydajności.
  • Jak zastosować praktyczne strategie optymalizacji kodu, koncentrując się na użyciu procesora CPU, alokacji pamięci i interakcji z bazą danych.

Postępuj zgodnie z instrukcjami, a następnie zastosuj te techniki do własnych aplikacji, aby uczynić je bardziej wydajnymi i ekonomicznymi.

Analiza przypadku optymalizacji

Przykładowa aplikacja zbadana w tym badaniu przypadku jest aplikacją platformy .NET, która uruchamia zapytania względem bazy danych blogów i wpisów w blogu. Korzysta ona z platformy Entity Framework, popularnego orm (mapowania obiektowo-relacyjnego) dla platformy .NET, aby wchodzić w interakcje z lokalną bazą danych SQLite. Aplikacja ma strukturę do wykonywania dużej liczby zapytań, symulując rzeczywisty scenariusz, w którym aplikacja platformy .NET może być wymagana do obsługi rozległych zadań pobierania danych. Przykładowa aplikacja to zmodyfikowana wersja przykładu z wprowadzeniem do programu Entity Framework.

Podstawowym problemem z wydajnością przykładowej aplikacji jest sposób zarządzania zasobami obliczeniowymi i interakcji z bazą danych. Aplikacja ma wąskie gardło wydajności, które znacząco wpływa na wydajność, a w związku z tym koszty obliczeniowe związane z jego uruchomieniem. Problem obejmuje następujące objawy:

  • Wysokie użycie procesora CPU: aplikacje mogą wykonywać nieefektywne obliczenia lub przetwarzać zadania w sposób, który niepotrzebnie zużywa dużą ilość zasobów procesora CPU. Może to prowadzić do spowolnienia czasu odpowiedzi i zwiększenia kosztów operacyjnych.

  • Nieefektywna alokacja pamięci: aplikacje mogą czasami napotykać problemy związane z użyciem pamięci i alokacją. W aplikacjach platformy .NET nieefektywne zarządzanie pamięcią może prowadzić do zwiększenia ilości pamięci, co z kolei może mieć wpływ na wydajność aplikacji.

  • Obciążenia związane z interakcją z bazą danych: aplikacje, które wykonują dużą liczbę zapytań względem bazy danych, mogą mieć wąskie gardła związane z interakcjami z bazą danych. Obejmuje to niewydajne zapytania, nadmierne wywołania bazy danych i słabe wykorzystanie możliwości platformy Entity Framework, z których wszystkie mogą obniżyć wydajność.

Analiza przypadku ma na celu rozwiązanie tych problemów dzięki zastosowaniu narzędzi profilowania programu Visual Studio w celu przeanalizowania wydajności aplikacji. Zrozumienie, gdzie i jak można poprawić wydajność aplikacji, deweloperzy mogą implementować optymalizacje w celu zmniejszenia użycia procesora CPU, zwiększenia wydajności alokacji pamięci, usprawnienia interakcji z bazą danych i optymalizacji wykorzystania zasobów. Ostatecznym celem jest zwiększenie ogólnej wydajności aplikacji, co czyni ją bardziej wydajną i opłacalną do uruchomienia.

Zadanie

Rozwiązywanie problemów z wydajnością w przykładowej aplikacji .NET stanowi kilka wyzwań. Te wyzwania wynikają ze złożoności diagnozowania wąskich gardeł wydajności. Kluczowe wyzwania związane z rozwiązywaniem opisanych problemów są następujące:

  • Diagnozowanie wąskich gardeł wydajności: Jednym z głównych wyzwań jest dokładne zidentyfikowanie głównych przyczyn problemów z wydajnością. Wysokie użycie procesora CPU, nieefektywna alokacja pamięci i nakłady pracy związane z interakcją z bazą danych mogą mieć wiele czynników mających wpływ. Deweloperzy muszą skutecznie używać narzędzi profilowania do diagnozowania tych problemów, co wymaga zrozumienia sposobu działania tych narzędzi i interpretowania danych wyjściowych.

  • Ograniczenia wiedzy i zasobów: na koniec zespoły mogą napotkać ograniczenia związane z wiedzą, wiedzą i zasobami. Profilowanie i optymalizowanie aplikacji wymaga określonych umiejętności i doświadczenia, a nie wszystkich zespołów może mieć natychmiastowy dostęp do tych zasobów.

Rozwiązanie tych wyzwań wymaga strategicznego podejścia, które łączy skuteczne wykorzystanie narzędzi profilowania, wiedzy technicznej i starannego planowania i testowania. Analiza przypadku ma na celu poprowadzenie deweloperów przez ten proces, zapewnienie strategii i szczegółowych informacji w celu przezwyciężenia tych wyzwań i poprawy wydajności aplikacji.

Strategia

Oto ogólny widok podejścia w tym badaniu przypadku:

  • Rozpoczniemy badanie, wykonując ślad użycia procesora CPU. Narzędzie użycie procesora CPU programu Visual Studio jest często przydatne do rozpoczęcia badań wydajności i optymalizacji kodu w celu obniżenia kosztów.
  • Następnie, aby uzyskać dodatkowe szczegółowe informacje, aby pomóc w odizolowaniu problemów lub poprawić wydajność, zbieramy dane śledzenia przy użyciu jednego z innych narzędzi profilowania. Na przykład: .
    • Przyjrzymy się użyciu pamięci. W przypadku platformy .NET najpierw wypróbujemy narzędzie alokacji obiektów platformy .NET. (W przypadku platformy .NET lub C++można zamiast tego zapoznać się z narzędziem Użycie pamięci).
    • W przypadku ADO.NET lub platformy Entity Framework możemy użyć narzędzia Baza danych do badania zapytań SQL, dokładnego czasu zapytania i nie tylko.

Zbieranie danych wymaga następujących zadań:

  • Ustawianie aplikacji na kompilację wydania.
  • Wybranie narzędzia Użycie procesora CPU z profilera wydajności (Alt+F2). (Późniejsze kroki obejmują kilka innych narzędzi).
  • Z poziomu profilera wydajności uruchom aplikację i zbierz ślad.

Sprawdzanie obszarów wysokiego użycia procesora CPU

Po zebraniu śladu za pomocą narzędzia Użycie procesora CPU i załadowaniu go do programu Visual Studio najpierw sprawdzimy początkową stronę raportu diagsession zawierającą podsumowane dane. Użyj linku Otwórz szczegóły w raporcie.

Zrzut ekranu przedstawiający otwieranie szczegółów w narzędziu Użycie procesora CPU.

W widoku szczegółów raportu otwórz widok Drzewo wywołań. Ścieżka kodu o najwyższym użyciu procesora CPU w aplikacji jest nazywana ścieżką gorącą. Ikona płomienia ścieżki gorącej (Zrzut ekranu przedstawiający ikonę Ścieżka gorąca.) może pomóc w szybkim identyfikowaniu problemów z wydajnością, które mogą zostać ulepszone.

W widoku Drzewo wywołań można zobaczyć wysokie użycie procesora CPU dla GetBlogTitleX metody w aplikacji przy użyciu około 60% udziału użycia procesora CPU aplikacji. Jednak wartość Self CPU dla GetBlogTitleX parametru jest niska, tylko około 10%. W przeciwieństwie do całkowitego użycia procesora CPU wartość Self CPU nie obejmuje czasu spędzonego w innych funkcjach, więc wiemy, że przyjrzymy się dalej drzewu wywołań dla rzeczywistego wąskiego gardła.

Zrzut ekranu przedstawiający widok Drzewa wywołań w narzędziu Użycie procesora CPU.

GetBlogTitleX Wykonuje wywołania zewnętrzne do dwóch bibliotek DLL LINQ, które korzystają z większości czasu procesora CPU, co wynika z bardzo wysokich wartości procesora CPU . Jest to pierwsza wskazówka, że zapytanie LINQ może być obszarem do optymalizacji.

Zrzut ekranu przedstawiający widok Drzewa wywołań w narzędziu Użycie procesora CPU z wyróżnionym własnym procesorem CPU.

Aby uzyskać zwizualizowane drzewo wywołań i inny widok danych, otwórz widok Flame Graph . (Lub kliknij prawym przyciskiem myszy GetBlogTitleX i wybierz pozycję Widok w grafie płomienia). W tym miejscu wygląda na to, że GetBlogTitleX metoda jest odpowiedzialna za wiele użycia procesora CPU aplikacji (pokazano na żółtym rysunku). Zewnętrzne wywołania bibliotek DLL LINQ są wyświetlane pod polem GetBlogTitleX i używają całego czasu procesora CPU dla metody .

Zrzut ekranu przedstawiający widok Wykres płomienia w narzędziu Użycie procesora CPU.

Zbieranie dodatkowych danych

Często inne narzędzia mogą dostarczać dodatkowe informacje ułatwiające analizę i izolowanie problemu. W tym badaniu przypadku bierzemy następujące podejście:

  • Najpierw przyjrzyj się użyciu pamięci. Może istnieć korelacja między wysokim użyciem procesora CPU a wysokim użyciem pamięci, dlatego warto przyjrzeć się obu tym problemom.
  • Ponieważ zidentyfikowaliśmy biblioteki DLL LINQ, przyjrzymy się również narzędziu Baza danych.

Sprawdzanie użycia pamięci

Aby zobaczyć, co dzieje się z aplikacją pod względem użycia pamięci, zbieramy ślad przy użyciu narzędzia alokacji obiektów platformy .NET (w przypadku języka C++, możesz zamiast tego użyć narzędzia Użycie pamięci). Widok Drzewo wywołań w śladzie pamięci pokazuje ścieżkę gorącą i pomaga nam zidentyfikować obszar wysokiego użycia pamięci. W tym momencie nie ma zaskoczenia, GetBlogTitleX że metoda wydaje się generować wiele obiektów! W rzeczywistości ponad 900 000 alokacji obiektów.

Zrzut ekranu przedstawiający widok Drzewa wywołań w narzędziu alokacji obiektów platformy .NET.

Większość utworzonych obiektów to ciągi, tablice obiektów i int32s. Możemy zobaczyć, jak te typy są generowane, sprawdzając kod źródłowy.

Sprawdzanie zapytania w narzędziu Baza danych

W profilerze wydajności wybieramy narzędzie Baza danych zamiast użycia procesora CPU (lub wybierz oba). Po zebraniu śladu otwórz kartę Zapytania na stronie diagnostyki. Na karcie Zapytania dla śledzenia bazy danych można zobaczyć pierwszy wiersz przedstawiający najdłuższe zapytanie, 2446 ms. Kolumna Rekordy pokazuje liczbę rekordów odczytanych przez zapytanie. Te informacje można użyć do późniejszego porównania.

Zrzut ekranu przedstawiający zapytania bazy danych w narzędziu Baza danych.

Sprawdzając instrukcję SELECT wygenerowaną przez LINQ w kolumnie Query, identyfikujemy pierwszy wiersz jako zapytanie skojarzone z GetBlogTitleX metodą . Aby wyświetlić pełny ciąg zapytania, rozwiń szerokość kolumny. Pełny ciąg zapytania to:

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"

Zwróć uwagę, że aplikacja pobiera tutaj wiele wartości kolumn, być może więcej niż potrzebujemy. Przyjrzyjmy się kodowi źródłowego.

Optymalizowanie kodu

Nadszedł czas, aby przyjrzeć się kodowi źródłowemu GetBlogTitleX . W narzędziu Baza danych kliknij prawym przyciskiem myszy zapytanie i wybierz polecenie Przejdź do pliku źródłowego. W kodzie źródłowym dla GetBlogTitleXprogramu znajduje się następujący kod, który odczytuje bazę danych przy użyciu linQ.

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

Ten kod używa foreach pętli do wyszukiwania bazy danych dla wszystkich blogów z "Fred Smith" jako autor. Patrząc na to, widać, że wiele obiektów jest generowanych w pamięci: nowa tablica obiektów dla każdego bloga w bazie danych, skojarzone ciągi dla każdego adresu URL i wartości właściwości zawartych w wpisach, takich jak identyfikator blogu.

Przeprowadzamy trochę badań i znajdujemy kilka typowych zaleceń dotyczących sposobu optymalizowania zapytań LINQ. Alternatywnie możemy zaoszczędzić czas i pozwolić Copilot zrobić badania dla nas.

Jeśli używamy narzędzia Copilot, wybierzemy pozycję Zapytaj Copilot z menu kontekstowego i wpisz następujące pytanie:

Can you make the LINQ query in this method faster?

Napiwek

Możesz użyć poleceń ukośnika, takich jak /optimize , aby pomóc w utworzeniu dobrych pytań dla Copilot.

W tym przykładzie copilot udostępnia następujące sugerowane zmiany kodu wraz z wyjaśnieniem.

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

Ten kod zawiera kilka zmian, które ułatwiają optymalizowanie zapytania:

  • Dodano klauzulę Where i wyeliminowano jedną z foreach pętli.
  • Przewidywano tylko właściwość Title w instrukcji Select , która jest wszystkim, czego potrzebujemy w tym przykładzie.

Następnie ponownie przetestujemy przy użyciu narzędzi profilowania.

Wyniki

Po zaktualizowaniu kodu uruchomimy ponownie narzędzie Użycie procesora CPU, aby zebrać ślad. Widok Drzewo wywołań pokazuje, że GetBlogTitleX działa tylko 1754 ms, używając 37% całkowitej sumy procesora CPU aplikacji, co znacznie poprawia się z 59%.

Zrzut ekranu przedstawiający ulepszone użycie procesora CPU w widoku Drzewa wywołań narzędzia Użycie procesora CPU.

Przejdź do widoku Wykres płomienia , aby wyświetlić inną wizualizację przedstawiającą poprawę. W tym widoku GetBlogTitleX jest również używana mniejsza część procesora CPU.

Zrzut ekranu przedstawiający ulepszone użycie procesora CPU w widoku Wykres płomienia narzędzia Użycie procesora CPU.

Sprawdź wyniki w śladzie narzędzia Baza danych, a tylko dwa rekordy są odczytywane przy użyciu tego zapytania, a nie 100 000! Ponadto zapytanie jest znacznie uproszczone i eliminuje niepotrzebny element LEFT JOIN, który został wygenerowany wcześniej.

Zrzut ekranu przedstawiający krótszy czas wykonywania zapytań w narzędziu Baza danych.

Następnie ponownie sprawdzimy wyniki w narzędziu alokacji obiektów platformy .NET i zobaczymy, że GetBlogTitleX odpowiada tylko za 56 000 alokacji obiektów, czyli prawie 95% redukcji z 900 000!

Zrzut ekranu przedstawiający ograniczone alokacje pamięci w narzędziu alokacji obiektów platformy .NET.

Powtarzanie

Może być konieczne wiele optymalizacji i możemy nadal iterować ze zmianami kodu, aby zobaczyć, które zmiany zwiększają wydajność i pomagają zmniejszyć koszt obliczeń.

Następne kroki

Poniższe artykuły i wpisy w blogu zawierają więcej informacji, aby ułatwić efektywne korzystanie z narzędzi do wydajności programu Visual Studio.