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 Które 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 z programu Entity Framework, popularnego ORM (Object-Relational Mapping) dla .NET, aby komunikować się 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 Entity Framework (samouczek wprowadzający).
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 zadania przetwarzania 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ększonego odśmiecania pamięci, co z kolei może mieć wpływ na wydajność aplikacji.
Obciążenia związane z interakcją z bazą danych: Aplikacje wykonujące dużą liczbę zapytań względem bazy danych mogą doświadczać wąskich gardeł podczas komunikacji 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. Dzięki zrozumieniu, gdzie i jak można poprawić wydajność aplikacji, deweloperzy mogą wdrażać optymalizacje w celu zmniejszenia użycia procesora, poprawy efektywności alokacji pamięci, usprawnienia interakcji z bazą danych oraz optymalizacji wykorzystania zasobów. Ostatecznym celem jest zwiększenie ogólnej wydajności aplikacji, co czyni ją bardziej wydajną i opłacalną do uruchomienia.
Wyzwanie
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 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: W końcu zespoły mogą napotkać ograniczenia związane z wiedzą, doświadczeniem 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:
- Rozpoczynamy badanie, wykonując ślad użycia procesora. Narzędzie użycia procesora CPU w programie 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, które pomogą w odizolowaniu problemów lub poprawie wydajności, zbieramy dane śledzenia przy użyciu jednego z wybranych narzędzi profilowania. Na przykład:
- Przyjrzymy się użyciu pamięci. W przypadku platformy .NET najpierw używamy narzędzia alokacji obiektów .NET . (W przypadku platformy .NET lub C++można zamiast tego zapoznać się z narzędziem Użycie pamięci).
- W przypadku platformy ADO.NET lub Entity Framework możemy użyć narzędzia Database do badania zapytań SQL, dokładnego czasu wykonywania zapytań i nie tylko.
Zbieranie danych wymaga następujących zadań:
- Ustawianie aplikacji na wersję produkcyjną.
- Wybieranie narzędzia Użycie 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.
Inspekcja obszarów wysokiego użycia procesora
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 w raporcie linku Otwórz szczegóły.
W widoku szczegółów raportu otwórz widok drzewa 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 () może pomóc w szybkim zidentyfikowaniu problemów z wydajnością, które mogą zostać ulepszone.
W widoku drzewa wywołań można zobaczyć wysokie użycie procesora dla metody GetBlogTitleX
w aplikacji, która zużywa około 60% procent całościowego użycia procesora aplikacji. Jednak wartość Self CPU dla GetBlogTitleX
jest niska, tylko około .10%. W przeciwieństwie do Total CPU, wartość Self CPU wyklucza czas spędzony w innych funkcjach, więc powinniśmy przyjrzeć się dalej w dół drzewa wywołań, aby znaleźć rzeczywiste wąskie gardło.
GetBlogTitleX
wykonuje wywołania zewnętrzne do dwóch bibliotek DLL LINQ, które wykorzystują większość czasu procesora, co potwierdzają bardzo wysokie wartości własnego czasu CPU . Jest to pierwsza wskazówka, że zapytanie LINQ może być obszarem do optymalizacji.
Aby uzyskać zwizualizowane drzewo wywołań i inny widok danych, otwórz widok Flame Graph. (Lub kliknij prawym przyciskiem myszy GetBlogTitleX
i wybierz Widok w grafie płomienia.) W tym miejscu wygląda na to, że metoda GetBlogTitleX
jest odpowiedzialna za duże użycie procesora CPU aplikacji (pokazane na żółto). Zewnętrzne wywołania bibliotek DLL LINQ są wyświetlane pod polem GetBlogTitleX
i używają całego czasu procesora w metodzie.
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 drzewa wywołań w śladzie pamięci pokazuje gorącą ścieżkę i pomaga nam zidentyfikować obszar dużego użycia pamięci. Nic dziwnego, że w tym momencie metoda GetBlogTitleX
wydaje się generować wiele obiektów! W rzeczywistości ponad 900 000 alokacji obiektów.
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. W kolumnie Records pokazano, ile rekordów odczytuje zapytanie. Te informacje można użyć do późniejszego porównania.
Sprawdzając instrukcję SELECT
wygenerowaną przez LINQ w kolumnie Query, identyfikujemy pierwszy wiersz jako zapytanie skojarzone z metodą GetBlogTitleX
. 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 pozycję Przejdź do pliku źródłowego. W kodzie źródłowym dla GetBlogTitleX
znajduje się następujący kod, który używa LINQ do odczytu bazy danych.
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 pętli foreach
do wyszukiwania bazy danych pod kątem dowolnych 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 Ask 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ń z ukośnikiem, takich jak /optimize, aby pomóc w sformułowaniu dobrych pytań do Copilota.
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 pętliforeach
. - 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, w celu zebrania śladu. Widok drzewa wywołań pokazuje, że GetBlogTitleX
działa tylko 1754 ms, używając 37% z całkowitego wykorzystania procesora aplikacji, co stanowi znaczną poprawę w stosunku do 59%.
Przejdź do widoku Flame Graph, aby zobaczyć inną wizualizację przedstawiającą poprawę. W tym widoku GetBlogTitleX
również używa mniejszej części CPU.
Sprawdź wyniki w śladzie narzędzia bazy danych; za pomocą tego zapytania odczytywane są tylko dwa rekordy, zamiast 100 000! Ponadto zapytanie jest znacznie uproszczone i eliminuje niepotrzebny element LEFT JOIN, który został wygenerowany wcześniej.
Następnie ponownie sprawdzamy wyniki w narzędziu alokacji obiektów platformy .NET i widzimy, że GetBlogTitleX
odpowiada tylko za 56 000 alokacji obiektów, co stanowi redukcję o prawie 95% z 900 000!
Iteruj
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.
- Analiza przypadku: Izolowanie problemu z wydajnością
- Analiza przypadku: podwójna wydajność w czasie poniżej 30 minut
- poprawianie wydajności programu Visual Studio przy użyciu nowego narzędzia Instrumentation Tool