사례 연구: 코드를 최적화하고 컴퓨팅 비용을 줄이기 위한 초보자 가이드(C#, Visual Basic, C++, F#)

컴퓨팅 시간을 줄이면 비용이 절감되므로 코드를 최적화하면 비용을 절감할 수 있습니다. 이 사례 연구에서는 성능 문제가 있는 샘플 애플리케이션을 사용하여 프로파일링 도구를 사용하여 효율성을 개선하는 방법을 보여 줍니다. 어떤 도구를 선택해야 합니까?를 참조하여 프로파일링 도구를 비교하세요.

이 사례 연구에서는 다음 토픽을 다룹니다.

  • 코드 최적화의 중요성과 코드 최적화가 컴퓨팅 비용 절감에 미치는 영향.
  • 애플리케이션 성능을 분석하기 위해 Visual Studio 프로파일링 도구를 사용하는 방법.
  • 이러한 도구에서 성능 병목 현상을 파악하기 위해 제공하는 데이터를 해석하는 방법.
  • CPU 사용량, 메모리 할당, 데이터베이스 상호 작용에 중점을 두고 코드를 최적화하는 실용적인 전략을 적용하는 방법.

이러한 기술을 따른 다음, 사용자 고유 애플리케이션에 적용하여 보다 효율적이고 비용 효율적으로 만들기.

최적화 사례 연구

이 사례 연구에서 살펴본 샘플 애플리케이션은 블로그 및 블로그 게시물 데이터베이스에 대해 쿼리를 실행하는 .NET 애플리케이션입니다. .NET용으로 널리 사용되는 ORM(객체 관계 매핑)인 Entity Framework를 사용하여 SQLite 로컬 데이터베이스와 상호 작용합니다. 이 애플리케이션은 광범위한 데이터 검색 작업을 처리하기 위해 .NET 애플리케이션이 필요할 수 있는 실제 시나리오를 시뮬레이션하여 수많은 쿼리를 실행하도록 구성되어 있습니다. 샘플 애플리케이션은 Entity Framework 시작 샘플의 수정된 버전입니다.

샘플 애플리케이션의 주요 성능 문제는 컴퓨팅 리소스를 관리하고 데이터베이스와 상호 작용하는 방식에 있습니다. 이 애플리케이션은 성능 병목 현상으로 인해 효율성에 영향을 미치며 결과적으로 애플리케이션 실행과 관련된 컴퓨팅 비용에 큰 영향을 미칩니다. 이 문제에는 다음과 같은 증상이 포함됩니다.

  • 높은 CPU 사용량: 애플리케이션이 불필요하게 많은 양의 CPU 리소스를 소모하는 방식으로 비효율적인 계산이나 처리 작업을 수행할 수 있습니다. 이로 인해 응답 시간이 느려지고 운영 비용이 증가할 수 있습니다.

  • 비효율적인 메모리 할당: 애플리케이션은 때때로 메모리 사용량 및 할당과 관련된 문제에 직면할 수 있습니다. .NET 앱에서 비효율적인 메모리 관리는 가비지 컬렉션 증가로 이어져 애플리케이션 성능에 영향을 줄 수 있습니다.

  • 데이터베이스 상호 작용 오버헤드: 데이터베이스에 대해 수많은 쿼리를 실행하는 애플리케이션은 데이터베이스 상호 작용과 관련된 병목 현상을 경험할 수 있습니다. 여기에는 비효율적인 쿼리, 과도한 데이터베이스 호출, Entity Framework 기능의 잘못된 사용 등이 포함되며, 이 모든 것이 성능을 저하시킬 수 있습니다.

사례 연구는 Visual Studio의 프로파일링 도구를 사용하여 애플리케이션의 성능을 분석함으로써 이러한 문제를 해결하는 것을 목표로 합니다. 개발자는 애플리케이션의 성능을 개선할 수 있는 부분과 방법을 이해함으로써 CPU 사용량을 줄이고, 메모리 할당 효율성을 개선하고, 데이터베이스 상호 작용을 간소화하고, 리소스 활용도를 최적화하는 최적화를 구현할 수 있습니다. 궁극적인 목표는 애플리케이션의 전반적인 성능을 향상시켜 보다 효율적이고 비용 효율적으로 실행하는 것입니다.

과제

샘플 .NET 애플리케이션의 성능 문제를 해결하는 데는 몇 가지 어려움이 있습니다. 이러한 문제는 성능 병목 현상 진단의 복잡성에서 비롯됩니다. 설명한 문제를 해결하기 위한 주요 과제는 다음과 같습니다.

  • 성능 병목 현상 진단: 주요 과제 중 하나는 성능 문제의 근본 원인을 정확하게 파악하는 것입니다. 높은 CPU 사용량, 비효율적인 메모리 할당, 데이터베이스 상호 작용 오버헤드에는 여러 가지 원인이 있을 수 있습니다. 개발자는 이러한 문제를 진단하기 위해 프로파일링 도구를 효과적으로 사용해야 하며, 이를 위해서는 이러한 도구의 작동 방식과 결과를 해석하는 방법을 어느 정도 이해해야 합니다.

  • 지식 및 리소스 제약: 마지막으로 팀은 지식, 전문 지식 및 리소스와 관련된 제약에 직면할 수 있습니다. 애플리케이션을 프로파일링하고 최적화하려면 특정 기술과 경험이 필요하며, 모든 팀이 이러한 리소스에 즉시 액세스할 수 있는 것은 아닙니다.

이러한 문제를 해결하려면 프로파일링 도구, 기술 지식, 신중한 계획 및 테스트의 효과적인 사용을 결합하는 전략적 접근 방식이 필요합니다. 사례 연구는 이러한 문제를 극복하고 애플리케이션의 성능을 개선하기 위한 전략과 인사이트를 제공하여 이 프로세스를 통해 개발자를 안내하는 것을 목표로 합니다.

전략

다음은 이 사례 연구의 접근 방식에 대한 개략적인 보기입니다.

  • CPU 사용량 추적을 수행하여 조사를 시작합니다. Visual Studio의 CPU 사용량 도구는 성능 조사를 시작하고 코드를 최적화하여 비용을 절감하는 데 도움이 되는 경우가 많습니다.
  • 다음으로, 문제를 격리하거나 성능을 개선하는 데 도움이 되는 추가 인사이트를 얻기 위해 다른 프로파일링 도구 중 하나를 사용하여 추적을 수집합니다. 예:
    • 메모리 사용량을 살펴봅니다. .NET의 경우 먼저 .NET 개체 할당 도구를 사용해 봅니다. (.NET 또는 C++의 경우 메모리 사용량 도구를 대신 살펴볼 수 있습니다.)
    • ADO.NET 또는 Entity Framework의 경우 데이터베이스 도구를 사용하여 SQL 쿼리, 정확한 쿼리 시간 등을 검사할 수 있습니다.

데이터 컬렉션에는 다음 작업이 필요합니다.

  • 앱을 릴리스 빌드로 설정.
  • 성능 프로파일러(Alt+F2)에서 CPU 사용량 도구 선택. (이후 단계에는 몇 가지 다른 도구가 포함됩니다.)
  • 성능 프로파일러에서 앱을 시작하고 추적을 수집합니다.

높은 CPU 사용량 영역 검사

CPU 사용량 도구로 추적을 수집하고 이를 Visual Studio에 로드한 후, 먼저 요약된 데이터를 보여주는 초기 .diagsession 보고서 페이지를 확인합니다. 보고서에서 세부 정보 열기 링크를 사용합니다.

CPU 사용량 도구에서 세부 정보를 여는 스크린샷.

보고서 세부 정보 보기에서 호출 트리 보기를 엽니다. 앱에서 CPU 사용량이 가장 높은 코드 경로를 실행 부하 과다 경로라고 합니다. 실행 부하 과다 경로 불꽃 아이콘(실행 부하 과다 경로 아이콘을 보여 주는 스크린샷.)은 개선될 수 있는 성능 문제를 빠르게 식별하는 데 도움이 될 수 있습니다.

호출 트리 보기에서 CPU 사용량의 약 60%를 차지하는 앱의 GetBlogTitleX 메서드에 대한 높은 CPU 사용량을 볼 수 있습니다. 그러나 자체 CPUGetBlogTitleX 은 낮으며 약 .10%에 불과합니다. 총 CPU와 달리 자체 CPU 값은 다른 함수에서 소요된 시간을 제외하므로 실제 병목 현상에 대한 호출 트리를 더 자세히 살펴보는 것을 알고 있습니다.

CPU 사용량 도구의 호출 트리 뷰 스크린샷.

GetBlogTitleX 는 매우 높은 자체 CPU 값에 의해 입증된 대로 대부분의 CPU 시간을 사용하는 두 개의 LINQ DLL에 대한 외부 호출을 만듭니다. 이는 LINQ 쿼리가 최적화할 영역일 수 있음을 보여주는 첫 번째 단서입니다.

자체 CPU가 하이라이트 표시된 CPU 사용량 도구의 호출 트리 뷰 스크린샷.

시각화된 호출 트리와 다른 데이터 뷰를 얻으려면 플레임 그래프 뷰를 엽니다. (또는 GetBlogTitleX을(를) 마우스 오른쪽 버튼으로 클릭하고 플레임 그래프에서 보기 선택) 여기서도 GetBlogTitleX 메서드가 앱의 CPU 사용량(노란색으로 표시)의 많은 부분을 담당하는 것처럼 보입니다. LINQ DLL에 대한 외부 호출은 GetBlogTitleX 상자 아래에 표시되며 메서드에 대한 모든 CPU 시간을 사용합니다.

CPU 사용량 도구의 플레임 그래프 뷰 스크린샷.

추가 데이터 수집

종종 다른 도구는 분석을 돕고 문제를 격리하는 데 도움이 되는 추가 정보를 제공할 수 있습니다. 이 사례 연구에서는 다음과 같은 접근 방식을 취합니다.

  • 먼저 메모리 사용량을 확인합니다. 높은 CPU 사용량과 높은 메모리 사용량 간에 상관 관계가 있을 수 있으므로 문제를 격리하기 위해 둘 다 살펴보는 것이 유용할 수 있습니다.
  • LINQ DLL에 대해 알아봤으므로, 데이터베이스 도구도 살펴보겠습니다.

메모리 사용량 확인

메모리 사용량 측면에서 앱에서 무슨 일이 일어나고 있는지 확인하려면 .NET 개체 할당 도구를 사용하여 추적을 수집합니다(C++의 경우 메모리 사용량 도구를 대신 사용합니다). 메모리 추적의 호출 트리 보기는 핫 경로를 표시하고 메모리 사용량이 많은 영역을 식별하는 데 도움이 됩니다. 이 시점에서는 메서드가 GetBlogTitleX 많은 개체를 생성하는 것처럼 보입니다. 실제로 900,000개 이상의 개체 할당이 있습니다.

.NET 개체 할당 도구의 호출 트리 뷰 스크린샷.

대부분의 개체는 문자열, 개체 배열 및 Int32입니다. 소스 코드를 검사하여 이러한 형식이 어떻게 생성되는지 확인할 수 있습니다.

데이터베이스 도구에서 쿼리 확인

성능 프로파일러에서 CPU 사용량 대신 데이터베이스 도구를 선택합니다(또는 둘 다 선택 가능). 추적을 수집한 경우 진단 페이지에서 쿼리 탭을 엽니다. 데이터베이스 추적에 대한 쿼리 탭에서 첫 번째 행에 가장 긴 쿼리 2446ms가 표시되는 것을 볼 수 있습니다. 레코드 열은 쿼리가 읽는 레코드 수를 보여줍니다. 나중에 비교할 때 이 정보를 사용할 수 있습니다.

데이터베이스 도구의 데이터베이스 쿼리 스크린샷.

쿼리 열에서 LINQ에서 생성된 SELECT 문을 검사하여 첫 번째 행을 GetBlogTitleX 메서드와 연결된 쿼리로 식별합니다. 전체 쿼리 문자열을 보려면 열 너비를 확장합니다. 전체 쿼리 문자열입니다:

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"

여기서는 앱이 필요한 것보다 많은 열 값을 검색하고 있습니다. 소스 코드를 살펴보겠습니다.

최적화 코드

이제 소스 코드를 살펴볼 차례입니다 GetBlogTitleX . 데이터베이스 도구에서 쿼리를 마우스 오른쪽 버튼으로 클릭하고 원본 파일로 이동을 선택합니다. 원본 코드 GetBlogTitleX에서 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}");
      }
  }
}

이 코드는 루프를 사용하여 foreach "Fred Smith"를 작성자로 사용하여 데이터베이스에서 블로그를 검색합니다. 이를 살펴보면 데이터베이스의 각 블로그에 대한 새 개체 배열, 각 URL에 대한 연결된 문자열, 블로그 ID와 같은 게시물에 포함된 속성 값 등 많은 개체가 메모리에 생성되는 것을 볼 수 있습니다.

약간의 연구를 수행하고 LINQ 쿼리를 최적화하고 이 코드를 마련하는 방법에 대한 몇 가지 일반적인 권장 사항을 찾을 수 있습니다.

또는 시간을 절약하고 Copilot이 대신 조사하도록 할 수 있습니다.

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

이 코드에서는 쿼리를 최적화하는 데 도움이 되도록 몇 가지 변경 작업을 수행했습니다.

  • Where 절을 추가하고 foreach 루프 중 하나를 제거합니다.
  • Select 문에 Title 속성만 프로젝션되었으며 이 예시에서는 이것으로 충분합니다.

다음으로 프로파일링 도구를 사용하여 다시 테스트합니다.

Copilot으로 코드 최적화

Copilot을 사용하는 경우 Copilot에 성능 문제를 조사하도록 요청할 수 있습니다. 컨텍스트 메뉴에서 Copilot에 물어보기를 선택하고 다음 질문을 입력합니다.

Can you make the LINQ query in this method faster?

/optimize와 같은 슬래시 명령을 사용하여 Copilot에게 좋은 질문을 작성할 수 있습니다.

이 예제에서 Copilot은 설명과 함께 최적화된 쿼리와 유사한 다음과 같은 제안된 코드 변경 내용을 제공합니다.

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

결과

코드를 업데이트한 후 CPU 사용량 도구를 다시 실행하여 추적을 수집합니다. 호출 트리 보기는 앱 CPU 총의 37%를 사용하여 1754ms만 실행되고 있음을 보여 GetBlogTitleX 하며, 이는 59%에서 크게 향상되었습니다.

CPU 사용량 도구의 호출 트리 뷰에서 개선된 CPU 사용량의 스크린샷.

개선 사항에 대한 또 다른 시각화 표시를 확인하려면 플레임 그래프 보기로 전환합니다. 이 보기 GetBlogTitleX 에서는 CPU의 더 작은 부분도 사용합니다.

CPU 사용량 도구의 플레임 그래프 뷰에서 개선된 CPU 사용량의 스크린샷.

데이터베이스 도구 추적의 결과를 확인하고 100,000개 대신 이 쿼리를 사용하여 두 개의 레코드만 읽습니다. 또한 쿼리는 훨씬 간소화되어 이전에 생성된 불필요한 LEFT JOIN을 제거합니다.

데이터베이스 도구의 더 빠른 쿼리 시간 스크린샷.

다음으로 .NET 개체 할당 도구에서 결과를 다시 확인해보니 GetBlogTitleX이(가) 90만 개에서 거의 95% 감소한 56,000개의 개체 할당만 담당하는 것을 확인할 수 있습니다!

.NET 개체 할당 도구의 감소된 메모리 할당 스크린샷.

Iterate

여러 최적화가 필요할 수 있으며 코드 변경 내용을 계속 반복하여 성능을 향상시키고 컴퓨팅 비용을 줄여줄 수 있는 변경 내용을 확인할 수 있습니다.

다음 단계

다음 문서 및 블로그 게시물은 Visual Studio 성능 도구를 효과적으로 사용하는 방법을 배우는 데 도움이 되는 자세한 정보를 제공합니다.