Udostępnij za pośrednictwem


Mierzenie wpływu rozszerzenia na uruchamianie

Skup się na wydajności rozszerzeń w programie Visual Studio 2017

W oparciu o opinie klientów jedną z obszarów zainteresowania wydania programu Visual Studio 2017 była wydajność uruchamiania i ładowania rozwiązań. Jako zespół platformy programu Visual Studio pracujemy nad zwiększeniem wydajności uruchamiania i ładowania rozwiązań. Ogólnie rzecz biorąc, nasze pomiary sugerują, że zainstalowane rozszerzenia mogą również mieć znaczący wpływ na te scenariusze.

Aby ułatwić użytkownikom zrozumienie tego wpływu, dodaliśmy nową funkcję w programie Visual Studio, aby powiadomić użytkowników o powolnych rozszerzeniach. Czasami program Visual Studio wykrywa nowe rozszerzenie, które spowalnia ładowanie rozwiązania lub uruchamianie. Po wykryciu spowolnienia użytkownicy zobaczą powiadomienie w środowisku IDE wskazujące je na nowe okno dialogowe "Zarządzanie wydajnością programu Visual Studio". Do tego okna dialogowego zawsze można uzyskać dostęp za pomocą menu Pomoc, aby przeglądać wcześniej wykryte rozszerzenia.

manage Visual Studio performance

Ten dokument ma na celu ułatwienie deweloperom rozszerzeń, opisując sposób obliczania wpływu rozszerzenia. W tym dokumencie opisano również sposób lokalnego analizowania wpływu rozszerzenia. Lokalne analizowanie wpływu rozszerzenia określi, czy rozszerzenie może być wyświetlane jako rozszerzenie wpływające na wydajność.

Uwaga

Ten dokument koncentruje się na wpływie rozszerzeń na uruchamianie i ładowanie rozwiązań. Rozszerzenia wpływają również na wydajność programu Visual Studio, gdy interfejs użytkownika przestanie odpowiadać. Aby uzyskać więcej informacji na ten temat, zobacz How to: Diagnose UI delays caused by extensions (Instrukcje: diagnozowanie opóźnień interfejsu użytkownika spowodowanych przez rozszerzenia).

Jak rozszerzenia mogą mieć wpływ na uruchamianie

Jednym z najczęstszych sposobów wpływu rozszerzeń na wydajność uruchamiania jest wybranie automatycznego ładowania w jednym ze znanych kontekstów interfejsu użytkownika uruchamiania, takich jak NoSolutionExists lub ShellInitialized. Te konteksty interfejsu użytkownika są aktywowane podczas uruchamiania. Wszystkie pakiety, które zawierają ProvideAutoLoad atrybut w swojej definicji z tymi kontekstami, zostaną załadowane i zainicjowane w tym czasie.

Gdy mierzymy wpływ rozszerzenia, skupiamy się przede wszystkim na czasie spędzonym przez te rozszerzenia, które decydują się na automatyczne ładowanie w powyższych kontekstach. Mierzone czasy obejmują, ale nie są ograniczone do:

  • Ładowanie zestawów rozszerzeń dla pakietów synchronicznych
  • Czas spędzony w konstruktorze klasy pakietu dla pakietów synchronicznych
  • Czas spędzony w metodzie Initialize (lub SetSite) pakietu synchronicznego
  • W przypadku pakietów asynchronicznych powyższe operacje są uruchamiane w wątku w tle. W związku z tym operacje są wykluczone z monitorowania.
  • Czas spędzony w dowolnej asynchronicznej pracy zaplanowanej podczas inicjowania pakietu do uruchomienia w wątku głównym
  • Czas spędzony w programach obsługi zdarzeń, w szczególności zainicjowana aktywacja kontekstu lub zmiana stanu zombie powłoki
  • Począwszy od programu Visual Studio 2017 Update 3, rozpoczniemy również monitorowanie czasu poświęcanego na bezczynne wywołania przed zainicjowaniem powłoki. Długie operacje w bezczynnych programach obsługi powodują również brak odpowiedzi środowiska IDE i przyczyniają się do postrzeganego czasu uruchamiania przez użytkownika.

Dodaliśmy wiele funkcji począwszy od programu Visual Studio 2015. Te funkcje ułatwiają usuwanie konieczności automatycznego ładowania pakietów. Funkcje odroczają również potrzebę ładowania pakietów do bardziej szczegółowych przypadków. Te przypadki obejmują przykłady, w których użytkownicy będą bardziej pewni używania rozszerzenia lub zmniejszyć wpływ rozszerzenia podczas ładowania automatycznego.

Więcej informacji o tych funkcjach można znaleźć w następujących dokumentach:

Konteksty interfejsu użytkownika oparte na regułach: bogatszy aparat oparty na regułach oparty na kontekstach interfejsu użytkownika umożliwia tworzenie kontekstów niestandardowych na podstawie typów projektów, smaków i atrybutów. Konteksty niestandardowe mogą służyć do ładowania pakietu w bardziej szczegółowych scenariuszach. Te konkretne scenariusze obejmują obecność projektu z określoną możliwością zamiast uruchamiania. Konteksty niestandardowe umożliwiają również powiązanie widoczności poleceń z kontekstem niestandardowym na podstawie składników projektu lub innych dostępnych terminów. Ta funkcja eliminuje konieczność załadowania pakietu w celu zarejestrowania procedury obsługi zapytań o stanie polecenia.

Obsługa pakietów asynchronicznych: nowa klasa bazowa AsyncPackage w programie Visual Studio 2015 umożliwia ładowanie pakietów programu Visual Studio w tle asynchronicznie, jeśli żądanie załadowania pakietu zostało żądane przez atrybut automatycznego ładowania lub asynchroniczne zapytanie usługi. To ładowanie w tle umożliwia środowisko IDE zachowanie reakcji. Środowisko IDE odpowiada nawet wtedy, gdy rozszerzenie jest inicjowane w tle, a krytyczne scenariusze, takie jak uruchamianie i ładowanie rozwiązań, nie będzie miało to wpływu.

Usługi asynchroniczne: dzięki obsłudze pakietów asynchronicznych dodaliśmy również obsługę asynchronicznego wykonywania zapytań dotyczących usług i możliwości rejestrowania usług asynchronicznych. Co ważniejsze pracujemy nad konwertowaniem podstawowych usług Programu Visual Studio w celu obsługi asynchronicznego zapytania tak, aby większość pracy w zapytaniu asynchronicznym występowała w wątkach w tle. SComponentModel (host MEF programu Visual Studio) jest jedną z głównych usług, które obsługują teraz zapytanie asynchroniczne, aby umożliwić rozszerzenia do całkowitego obsługi ładowania asynchronicznego.

Zmniejszenie wpływu rozszerzeń ładowanych automatycznie

Jeśli pakiet nadal musi być ładowany automatycznie podczas uruchamiania, należy zminimalizować pracę wykonaną podczas inicjowania pakietu. Minimalizacja pracy inicjowania pakietu zmniejsza szanse na uruchomienie rozszerzenia, które ma wpływ na uruchomienie.

Niektóre przykłady, które mogą spowodować, że inicjowanie pakietu będzie kosztowne, to:

Użycie synchronicznego ładowania pakietów zamiast asynchronicznego ładowania pakietów

Ponieważ domyślnie pakiety synchroniczne są ładowane do głównego wątku, zachęcamy właścicieli rozszerzeń, którzy mają automatycznie załadowane pakiety, aby zamiast tego używać asynchronicznej klasy bazowej pakietu, jak wspomniano wcześniej. Zmiana automatycznie załadowanego pakietu w celu obsługi ładowania asynchronicznego ułatwi również rozwiązanie innych problemów poniżej.

Synchroniczne żądania we/wy pliku/sieci

W głównym wątku należy unikać każdego synchronicznego żądania we/wy pliku lub sieci. Ich wpływ będzie zależeć od stanu maszyny i może blokować długi czas w niektórych przypadkach.

Użycie asynchronicznego ładowania pakietów i asynchronicznych interfejsów API we/wy powinno upewnić się, że inicjowanie pakietu nie blokuje głównego wątku w takich przypadkach. Użytkownicy mogą również nadal wchodzić w interakcje z programem Visual Studio, gdy żądania we/wy są wykonywane w tle.

Wczesne inicjowanie usług, składników

Jednym z typowych wzorców inicjowania pakietu jest inicjowanie usług używanych przez pakiet lub udostępnianych przez ten pakiet w pakiecie constructor lub initialize metodzie. Chociaż zapewnia to, że usługi są gotowe do użycia, może również dodać niepotrzebne koszty ładowania pakietów, jeśli te usługi nie są używane natychmiast. Zamiast tego takie usługi powinny być inicjowane na żądanie, aby zminimalizować pracę wykonaną w inicjowaniu pakietu.

W przypadku usług globalnych dostarczanych przez pakiet można użyć AddService metod, które przyjmują funkcję, aby leniwie zainicjować usługę tylko wtedy, gdy jest ona żądana przez składnik. W przypadku usług używanych w pakiecie można użyć języka Lazy<T> lub AsyncLazy<T> , aby upewnić się, że usługi są inicjowane/odpytywane podczas pierwszego użycia.

Mierzenie wpływu automatycznych rozszerzeń przy użyciu dziennika aktywności

Począwszy od programu Visual Studio 2017 Update 3, dziennik aktywności programu Visual Studio będzie teraz zawierać wpisy dotyczące wpływu pakietów na wydajność podczas uruchamiania i ładowania rozwiązań. Aby wyświetlić te miary, musisz otworzyć program Visual Studio z przełącznikiem /log i otworzyć plik ActivityLog.xml .

W dzienniku aktywności wpisy będą znajdować się w źródle "Zarządzanie wydajnością programu Visual Studio" i będą wyglądać podobnie do następującego przykładu:

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

W tym przykładzie pokazano, że pakiet z identyfikatorem GUID "3cd7f5bf-6662-4ff0-ade8-97b5ff12f39c" spędził 2008 ms podczas uruchamiania programu Visual Studio. Należy pamiętać, że program Visual Studio uwzględnia koszt najwyższego poziomu jako liczbę podstawową podczas obliczania wpływu pakietu, ponieważ byłyby to oszczędności, które użytkownicy zobaczą po wyłączeniu rozszerzenia dla tego pakietu.

Mierzenie wpływu automatycznych rozszerzeń przy użyciu narzędzia PerfView

Chociaż analiza kodu może pomóc zidentyfikować ścieżki kodu, które mogą spowolnić inicjowanie pakietu, można również korzystać z śledzenia za pomocą aplikacji, takich jak Program PerfView , aby zrozumieć wpływ ładowania pakietu w uruchamianiu programu Visual Studio.

PerfView to narzędzie do śledzenia w całym systemie. To narzędzie pomoże zrozumieć gorące ścieżki w aplikacji z powodu użycia procesora CPU lub blokowania wywołań systemowych. Poniżej przedstawiono szybki przykład analizowania przykładowego rozszerzenia przy użyciu narzędzia PerfView.

Przykładowy kod:

Ten przykład jest oparty na poniższym przykładowym kodzie, który ma na celu pokazanie przypadków występowania niektórych typowych przyczyn opóźnienia:

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

Rejestrowanie śledzenia za pomocą narzędzia PerfView:

Po skonfigurowaniu środowiska programu Visual Studio z zainstalowanym rozszerzeniem możesz zarejestrować ślad uruchamiania, otwierając program PerfView i otwierając okno dialogowe Zbieraj z menu Zbieraj.

perfview collect menu

Domyślne opcje zapewniają stosy wywołań dla użycia procesora CPU, ale ponieważ interesuje nas również czas blokowania, należy również włączyć stosy czasu wątku. Gdy ustawienia będą gotowe, możesz kliknąć pozycję Uruchom kolekcję , a następnie otworzyć program Visual Studio po rozpoczęciu nagrywania.

Przed zatrzymaniem zbierania chcesz upewnić się, że program Visual Studio jest w pełni zainicjowany, główne okno jest całkowicie widoczne i jeśli rozszerzenie ma jakiekolwiek elementy interfejsu użytkownika, które są automatycznie wyświetlane, są również widoczne. Po całkowitym załadowaniu programu Visual Studio i zainicjowaniu rozszerzenia można zatrzymać nagrywanie w celu przeanalizowania śledzenia.

Analizowanie śledzenia za pomocą narzędzia PerfView:

Po zakończeniu rejestrowania narzędzie PerfView automatycznie otworzy opcje śledzenia i rozwiń.

Na potrzeby tego przykładu interesuje nas głównie widok Stosy czasu wątku , który można znaleźć w obszarze Grupa zaawansowana. Ten widok pokaże całkowity czas spędzony na wątku przez metodę, w tym czas procesora CPU i zablokowany czas, taki jak operacje we/wy dysku lub oczekiwanie na dojścia.

thread time stacks

Podczas otwierania widoku Stosy czasu wątku należy wybrać proces devenv , aby rozpocząć analizę.

Narzędzie PerfView zawiera szczegółowe wskazówki dotyczące sposobu odczytywania stosów czasu wątku w ramach własnego menu Pomoc w celu uzyskania bardziej szczegółowej analizy. Na potrzeby tego przykładu chcemy filtrować ten widok dalej, dołączając tylko stosy z nazwą modułu pakietów i wątkiem uruchamiania.

  1. Ustaw wartość GroupPats na pusty tekst, aby usunąć wszystkie dodane domyślnie grupy.
  2. Ustaw wartość IncPats , aby uwzględnić część nazwy zestawu i wątek uruchamiania oprócz istniejącego filtru procesu. W tym przypadku powinno to być devenv; Wątek uruchamiania; MakeVsSlowExtension.

Teraz widok pokaże tylko koszt skojarzony z zestawami powiązanymi z rozszerzeniem. W tym widoku każdy czas wymieniony w kolumnie Inc (koszt inkluzywny) wątku uruchamiania jest związany z naszym filtrowanym rozszerzeniem i będzie miało wpływ na uruchomienie.

W powyższym przykładzie przedstawiono kilka interesujących stosów wywołań:

  1. Operacje we/wy przy użyciu System.IO klasy: Chociaż koszt inkluzywny tych ramek może nie być zbyt kosztowny w śledzeniu, jest to potencjalna przyczyna problemu, ponieważ szybkość operacji we/wy pliku będzie się różnić od maszyny do komputera.

    system io frames

  2. Blokowanie wywołań oczekujących na inną pracę asynchroniczną: w tym przypadku czas inkluzywny będzie reprezentować czas, w jakim główny wątek jest blokowany po zakończeniu pracy asynchronicznej.

    blocking call frames

Jednym z pozostałych widoków śledzenia, które będą przydatne do określenia wpływu, będą stosy ładowania obrazów. Możesz zastosować te same filtry co zastosowane do widoku Stosy czasu wątku i sprawdzić wszystkie zestawy załadowane ze względu na kod wykonywany przez pakiet załadowany automatycznie.

Ważne jest zminimalizowanie liczby załadowanych zestawów wewnątrz procedury inicjowania pakietu, ponieważ każdy dodatkowy zestaw będzie obejmować dodatkowe we/wy dysku, co może znacznie spowolnić uruchamianie na wolniejszych maszynach.

Podsumowanie

Uruchamianie programu Visual Studio było jednym z obszarów, w których stale uzyskujemy opinie. Naszym celem, jak ustalono wcześniej, jest to, że wszyscy użytkownicy mają spójne środowisko uruchamiania niezależnie od zainstalowanych składników i rozszerzeń. Chcemy współpracować z właścicielami rozszerzeń, aby pomóc im osiągnąć ten cel. Powyższe wskazówki powinny być pomocne w zrozumieniu wpływu rozszerzeń na uruchamianie i unikaniu konieczności automatycznego ładowania lub ładowania go asynchronicznie, aby zminimalizować wpływ na produktywność użytkowników.