다음을 통해 공유


시작 시 확장 영향 측정

Visual Studio 2017의 확장 성능에 집중

고객 피드백에 따라 Visual Studio 2017릴리스의 중점 영역 중 하나는 시작 및 솔루션 로드 성능이었습니다. 저희 Visual Studio 플랫폼 팀은 시작 및 솔루션 로드 성능을 개선하기 위해 노력하고 있습니다. 일반적으로 측정 결과에 따르면 설치된 확장 또한 이러한 시나리오에 상당한 영향을 주는 것으로 확인되었습니다.

사용자가 이러한 영향을 이해할 수 있도록 Visual Studio에서 사용자에게 느린 확장에 대한 알림을 제공하는 새로운 기능이 추가되었습니다. 때때로 Visual Studio에서는 솔루션 로드 또는 시작을 느리게 만드는 새로운 확장이 감지됩니다. 속도 저하가 감지되면 사용자가 새로운 “Visual Studio 성능 관리” 대화 상자로 이동할 수 있는 알림이 IDE에 표시됩니다. 또한 언제든지 도움말 메뉴에서 이 대화 상자에 액세스하여 이전에 감지된 확장을 찾아볼 수도 있습니다.

manage Visual Studio performance

이 문서에서는 확장 개발자를 지원하기 위해 확장 영향의 계산 방법을 설명합니다. 이 문서에서는 또한 확장 영향을 로컬로 분석하는 방법을 설명합니다. 확장 영향을 로컬로 분석하여 특정 확장을 성능에 영향을 주는 확장으로 표시할지 결정합니다.

참고 항목

이 문서에서는 시작 및 솔루션 로드에 대한 확장의 영향에 중점을 둡니다. 또한 확장으로 UI가 응답하지 않을 경우 Visual Studio 성능에 영향을 줍니다. 이 주제에 대한 자세한 내용은 방법: 확장으로 인해 발생한 UI 지연 진단을 참조하세요.

확장이 시작에 미치는 영향

확장이 시작 성능에 영향을 주는 가장 일반적인 방법 중 하나는 NoSolutionExists 또는 ShellInitialized와 같은 알려진 시작 UI 컨텍스트 중 하나에서 자동 로드를 선택하는 것입니다. 이러한 UI 컨텍스트는 시작 중에 활성화됩니다. 이 때 이러한 컨텍스트와 함께 해당 정의에 ProvideAutoLoad 특성이 포함된 모든 패키지가 로드되고 초기화됩니다.

확장 영향을 측정할 때는 주로 위 컨텍스트에서 자동 로드를 선택하는 확장에 소요된 시간에 초점을 둡니다. 측정된 시간은 다음을 포함하지만 이에 국한되지는 않습니다.

  • 동기 패키지에 대한 확장 어셈블리 로드
  • 동기 패키지에 대한 패키지 클래스 생성자에 소요된 시간
  • 동기 패키지에 대한 패키지 초기화(또는 SetSite) 메서드에 소요된 시간
  • 비동기 패키지의 경우 위 작업이 백그라운드 스레드에서 실행됩니다. 따라서 작업이 모니터링에서 제외됩니다.
  • 기본 스레드에서 실행되도록 패키지 초기화 중 예약된 모든 비동기 작업에 소비된 시간
  • 특히 셸 초기화 컨텍스트 활성화 또는 셸 좀비 상태 변경과 같은 이벤트 처리기에 소비된 시간
  • Visual Studio 2017 업데이트 3부터는 셸이 초기화되기 전 유휴 호출에 소비된 시간에 대해서도 모니터링이 시작됩니다. 유휴 처리기에서 작업이 장기화되면 IDE가 응답하지 않고 사용자가 인식하는 시작 시간에 영향을 줍니다.

Visual Studio 2015부터 많은 기능이 추가되었습니다. 이러한 기능은 패키지 자동 로드 요구를 없애는 데 도움이 됩니다. 또한 이러한 기능은 패키지가 더 구체적인 사례를 로드할 필요성을 지연시켜 줍니다. 이러한 사례에는 사용자가 더 확실하게 확장을 사용하거나 자동 로드 시 확장 영향을 줄일 수 있는 예시가 포함됩니다.

이러한 기능에 대한 자세한 내용은 다음 문서에서 확인할 수 있습니다.

규칙 기반 UI 컨텍스트: UI 컨텍스트를 기반으로 빌드된 다양한 기능의 규칙 기반 엔진을 통해 프로젝트 형식, 버전 및 특성을 기반으로 사용자 지정 컨텍스트를 만들 수 있습니다. 사용자 지정 컨텍스트를 사용하여 보다 구체적인 시나리오를 진행하는 동안 패키지를 로드할 수 있습니다. 이러한 구체적인 시나리오에는 시작 대신 특정 기능을 포함하는 프로젝트의 존재가 포함됩니다. 사용자 지정 컨텍스트에서는 또한 프로젝트 구성 요소 또는 기타 사용 가능한 조건에 따라 명령 가시성을 사용자 지정 컨텍스트에 연결할 수 있습니다. 이 기능을 사용하면 명령 상태 쿼리 처리기를 등록하기 위해 패키지를 로드할 필요가 없습니다.

비동기 패키지 지원: Visual Studio 2015의 새로운 AsyncPackage 기본 클래스를 사용하면 패키지 로드가 자동 로드 특성 또는 비동기 서비스 쿼리에 의해 등록된 경우 Visual Studio 패키지를 백그라운드에서 비동기로 로드할 수 있습니다. 이러한 백그라운드 로드를 통해 IDE의 응답성을 유지할 수 있습니다. 확장이 백그라운드에서 초기화되는 동안에도 IDE가 응답성을 유지하고 시작 및 솔루션 로드와 같은 중요 시나리오는 영향을 받지 않습니다.

비동기 서비스: 비동기 패키지 지원과 함께 서비스를 비동기적으로 쿼리하고 비동기 서비스를 등록할 수 있는 지원이 추가되었습니다. 더 중요한 것은 비동기 쿼리의 작업 대부분이 백그라운드 스레드에서 수행되도록 비동기 쿼리를 지원하는 핵심 Visual Studio 서비스 변환을 진행하고 있다는 것입니다. SComponentModel(Visual Studio MEF 호스트)은 확장이 비동기 로드를 완전히 지원하도록 허용하는 비동기 쿼리를 현재 지원하는 주요 서비스 중 하나입니다.

자동 로드된 확장의 영향 감소

여전히 시작 시에 패키지를 자동 로드해야 할 경우에는 패키지 초기화 중 수행되는 작업을 최소화하는 것이 중요합니다. 패키지 초기화 작업을 최소화하면 확장이 시작에 영향을 미치는 가능성을 줄일 수 있습니다.

패키지 초기화로 인한 비용이 커질 수 있는 일부 예시는 다음과 같습니다.

비동기 패키지 로드 대신 동기 패키지 로드 사용

기본적으로 동기 패키지가 기본 스레드에 로드되기 때문에 앞에서 언급한 대로 패키지가 자동 로드되는 확장 소유자가 비동기 패키지 기본 클래스를 대신 사용하는 것이 좋습니다. 비동기 로드를 지원하도록 자동 로드된 패키지를 변경하면 아래 표시된 다른 문제들도 더 쉽게 해결할 수 있습니다.

동기 파일/네트워크 IO 요청

이상적으로 기본 스레드에서는 동기 파일 또는 네트워크 IO 요청을 사용하지 않는 것이 좋습니다. 이러한 영향은 컴퓨터 상태에 따라 달라지며 일부 경우에 장시간 동안 차단될 수 있습니다.

이러한 경우에 비동기 패키지 로드 및 비동기 IO API를 사용하면 패키지 초기화가 기본 스레드를 차단하지 않습니다. 또한 I/O 요청이 백그라운드에서 수행되는 동안 사용자가 Visual Studio와 계속 상호 작용할 수 있습니다.

서비스, 구성 요소에 대한 조기 초기화

패키지 초기화에서 일반적인 패턴 중 하나는 패키지 constructor 또는 initialize 메서드의 패키지에서 사용되거나 제공되는 서비스를 초기화하는 것입니다. 이렇게 하면 서비스 사용을 위한 준비 상태가 보장되지만 서비스가 즉시 사용되지 않는 경우 패키지 로드에 불필요한 비용이 추가됩니다. 대신 이러한 서비스는 패키지 초기화 시 수행되는 작업을 최소화하기 위해 필요에 따라 초기화됩니다.

패키지에서 제공되는 전역 서비스의 경우 구성 요소의 요청이 있을 때만 함수를 사용해서 서비스를 느리게 초기화하는 AddService 메서드를 사용할 수 있습니다. 패키지 내에 사용된 서비스의 경우 Lazy<T> 또는 AsyncLazy<T>를 사용하여 처음 사용 시에 서비스가 초기화/쿼리되도록 할 수 있습니다.

활동 로그를 사용하여 자동 로드되는 확장의 영향 측정

Visual Studio 2017 업데이트 3부터는 Visual Studio 활동 로그에 시작 및 솔루션 로드 중 패키지의 성능 영향에 대한 항목이 포함됩니다. 이러한 측정값을 보려면 /log 스위치를 사용하여 Visual Studio를 열고 ActivityLog.xml 파일을 열어야 합니다.

활동 로그에서 이러한 항목은 “Visual Studio 성능 관리” 원본에 있으며 다음 예제와 같이 표시됩니다.

Component: 3cd7f5bf-6662-4ff0-ade8-97b5ff12f39c, Inclusive Cost: 2008.9381, Exclusive Cost: 2008.9381, Top Level Inclusive Cost: 2008.9381

이 예제에서는 Visual Studio 시작 시 GUID가 "3cd7f5bf-6662-4ff0-ade8-97b5ff12f39c"인 패키지에 2008 밀리초가 소비된 것을 보여줍니다. Visual Studio에서는 패키지의 영향을 계산할 때 최상위 비용을 주요 숫자로 간주합니다. 이는 사용자가 해당 패키지에 대해 확장을 사용하지 않도록 설정할 때 표시되는 절감액이기 때문입니다.

PerfView를 사용하여 자동 로드되는 확장의 영향 측정

코드 분석이 패키지 초기화를 느리게 만들 수 있는 코드 경로를 식별하는 데 도움이 될 수 있지만, PerfView와 같은 애플리케이션을 사용하여 추적을 활용함으로써 Visual Studio 시작 시 패키지 로드 영향을 파악할 수도 있습니다.

PerfView는 시스템 전반의 추적 도구입니다. 이 도구는 CPU 사용량 또는 시스템 호출 차단으로 인해 애플리케이션에서 발생하는 실행 부하 과다 경로를 이해하는 데 도움이 됩니다. 다음은 PerfView를 사용하여 샘플 확장을 분석하는 간단한 예제입니다.

예제 코드:

이 예제는 몇 가지 일반적인 지연 원인을 보여 주도록 설계된 아래의 샘플 코드를 기반으로 합니다.

protected override void Initialize()
{
    // Initialize a class from another assembly as an example
    MakeVsSlowServiceImpl service = new MakeVsSlowServiceImpl();

    // Costly work in main thread involving file IO
    string systemPath = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
    foreach (string file in Directory.GetFiles(systemPath))
    {
        DateTime creationDate = File.GetCreationTime(file);
    }

    // Costly work after shell is initialized. This callback executes on main thread
    KnownUIContexts.ShellInitializedContext.WhenActivated(() =>
    {
        DoMoreWork();
    });

    // Start async work on background thread
    DoAsyncWork().Forget();
}

private async Task DoAsyncWork()
{
    // Switch to background thread to do expensive work
    await TaskScheduler.Default;
    System.Threading.Thread.Sleep(500);
}

private void DoMoreWork()
{
    // Costly work
    System.Threading.Thread.Sleep(500);
    // Blocking call to an asynchronous work.
    ThreadHelper.JoinableTaskFactory.Run(async () => { await DoAsyncWork(); });
}

PerfView로 추적 레코딩:

확장이 설치된 Visual Studio 환경을 설정한 후 PerfView를 열고 수집 메뉴에서 수집 대화 상자를 열어 시작 추적을 레코드할 수 있습니다.

perfview collect menu

기본 옵션은 CPU 소비에 대한 호출 스택을 제공하지만 여기에서는 차단 시간도 중요하므로 스레드 시간 스택을 사용하도록 설정해야 합니다. 설정이 완료되었으면 수집 시작을 클릭한 다음, 레코딩이 시작된 후 Visual Studio를 열 수 있습니다.

수집을 중지하려면 먼저 Visual Studio가 완전히 초기화되었고 기본 창이 완전히 표시되는지 그리고 확장에 자동으로 표시되는 UI 조각이 포함된 경우 이것도 표시되는지 확인해야 합니다. Visual Studio가 완전히 로드되었고 확장이 초기화되었으면 추적을 분석하기 위해 레코딩을 중지할 수 있습니다.

PerfView로 추적 분석:

레코딩이 완료된 후 PerfView가 자동으로 추적을 열고 옵션을 확장합니다.

이 예제에서는 고급 그룹에서 찾을 수 있는 스레드 시간 스택 보기에 대해 주로 살펴봅니다. 이 보기에는 디스크 IO 또는 핸들 대기와 같은 CPU 시간 및 차단 시간을 모두 포함하여 메서드에서 스레드에 소비된 총 시간이 표시됩니다.

thread time stacks

스레드 시간 스택 보기를 열 때는 분석을 시작하기 위해 devenv 프로세스를 선택해야 합니다.

PerfView에는 자세한 분석을 위해 자체 도움말 메뉴에서 스레드 시간 스택을 읽는 방법에 대한 세부 안내가 포함되어 있습니다. 이 예제에서는 패키지 모듈 이름 및 시작 스레드가 있는 스택만 포함하여 이 보기를 추가로 필터링합니다.

  1. GroupPats를 빈 텍스트로 설정하여 기본적으로 추가되는 그룹화를 제거합니다.
  2. 기존 프로세스 필터 외에도 어셈블리 이름 및 시작 스레드 부분을 포함하도록 IncPats를 설정합니다. 여기에서는 devenv;Startup Thread;MakeVsSlowExtension이어야 합니다.

이제 보기에 확장 관련 어셈블리와 연관된 비용만 표시됩니다. 이 보기에서 시작 스레드의 Inc(포괄 비용) 열에 나열된 시간은 필터링된 확장과 관련이 있으며 시작에 영향을 줍니다.

위 예제에서 일부 흥미로운 호출 스택은 다음과 같습니다.

  1. System.IO 클래스 사용 IO: 이러한 프레임의 포괄적 비용이 추적에서 너무 높지 않게 나타날 수 있지만 파일 IO 속도가 컴퓨터마다 다르기 때문에 이것이 문제의 원인이 될 수 있습니다.

    system io frames

  2. 다른 비동기 작업에서 대기 중인 호출 차단: 여기에서 포괄적 비용은 비동기 작업이 완료될 때 기본 스레드가 차단된 시간을 나타냅니다.

    blocking call frames

추적에서 영향을 확인하는 데 유용한 다른 보기 중 하나는 이미지 로드 스택입니다. 적용된 동일한 필터를 스레드 시간 스택 보기에 적용하고 자동 로드된 패키지로 실행된 코드로 인해 로드된 모든 어셈블리를 찾을 수 있습니다.

어셈블리가 추가될 때마다 속도가 느린 컴퓨터에서 시작 시간을 더 크게 느리게 만들 수 있는 추가 디스크 I/O가 발생하므로, 패키지 초기화 루틴 내에서 로드되는 어셈블리 수를 최소화하는 것이 중요합니다.

요약

Visual Studio 시작은 지속적으로 피드백이 발생하는 분야 중 하나입니다. 앞에서 언급한 바와 같이 저희 목표는 설치된 구성 요소 및 확장에 관계없이 사용자의 시작 환경을 일관적으로 만드는 것입니다. 저희는 이러한 목표 달성을 돕기 위해 확장 소유자와의 협력을 추구하고 있습니다. 위에 설명된 지침은 확장이 시작에 미치는 영향을 이해하고 사용자 생산성에 미치는 영향을 최소화하기 위해 자동 로드 또는 비동기 로드에 대한 필요성을 방지하는 데 도움이 될 것입니다.