Sytuacja kobiet w IT w 2024 roku
23.06.20205 min
Piotr Lewandowski
Dynatrace

Piotr LewandowskiSoftware DeveloperDynatrace

Angular + Ivy = poprawa wydajności budowania?

Sprawdź, jak pod kątem wydajności sprawdza się nowy kompilator Ivy, wydany wraz z Angularem 9.

Angular + Ivy = poprawa wydajności budowania?

Nowy kompilator “Ivy”, wydany wraz z Angular 9 jest przełomowy. Nie dość, że wygenerowany kod jest prostszy (właściwie, to nawet czytelny dla człowieka), to mniejszy, szybszy w runtime i szybszy podczas budowania - takie rzeczy słyszeliśmy na konferencjach. Czy faktycznie się udało? Sprawdzam to na realnej aplikacji.

To jak działają wnętrzności nowego kompilatora są poza zakresem tego artykuły, polecam szczegółowy artykuł na indepth.dev oraz oficjalne blogposty dla Angular 9 i Angular 9.1.

Dane - testowana aplikacja

Rezultat badania może się znacznie różnić w zależności od wielkości lub konfiguracji naszej aplikacji, twórcy wielokrotnie to podkreślają. Poniższe dane mają sens w określonym środowisku.

Wyniki które przygotowałem odnoszą się do aplikacji którą rozwijamy od dwóch lat:

  • 130 tysięcy linii kodu (TypeScript + HTML)
  • 800 komponentów
  • ponad 140 modułów lazy-loaded


Testy wykonywałem na Angular 9.0.6 z włączonym i wyłączonym kompilatorem Ivy. Porównuję bundle dla starych przeglądarek (ECMAScript 5) oraz dla nowszych (ECMAScript 2015+).

W momencie publikacji, używamy już Angulara 9.1, dodam notkę kiedy to ma znaczenie.

Wielkość kodu

Na podstawie oficjalnego bloga, wartości oczekiwane to:

  • Małe aplikacje: 30% mniej kod
  • Średnie aplikacje: 2% mniej kodu
  • Duże aplikacje: 25-45% mniej kodu


Sprawdźmy to!

Pełen build produkcyjny 

Używam tej metryki jako sumę usprawnień dostarczonych przez kompilator, im więcej komponentów tym potencjalnie lepszy rezultat.

Widocznie wchodzimy w zakres średnich aplikacji.

Dlaczego wartość gzipped jest poprawiona tylko 0.5%?

Żeby lepiej zrozumieć to co się dzieje pod maską, nie możemy się ograniczać do mierzenia wagi sumy wszystkich plików. Rozbijmy statystykę na pojedynczy plik main.js oraz sumę wszystkich innych plików. Oznaczam je jako “chunks”, to te pliki które są ładowane na żądanie.

Wszędzie poza main.js poprawa jest dużo większa, i to o około 20%!

Dlaczego main.js jest większy?

I dlaczego pliki kompilowane jako "lazy loaded" są jednak mniejsze? Zajrzyjmy jeszcze głębiej. Spójrz, main.js składa się z:

  • Bibliotek Angulara
  • Niezależnych bibliotek utilowych
  • Komponentów i serwisów które nie mogły być skompilowane jako "lazy loaded"


Podstawowe biblioteki JavaScript jak RxJs, lodash nie mają nic wspólnego z Ivy - to nie powinno się zmienić. Powinniśmy zwrócić uwagę na biblioteki powiązane z Angularem - @angular/core ngrx lub biblioteki komponentów jak @angular/material.

W Angular 9, biblioteki nie są kompatybilne z Ivy i muszą być re-kompilowane narzędziem ngcc (Angular Compatibility Compiler). Spekuluje, że ten proces powoduje minimalnie większą wagę komponentów w bibliotekach.

Co potwierdza poniższy wykres (wartości w Kilobajtach):

Drugim powodem większej wagi który zauważyłem, to z włączonym Ivy - webpack generuje dużo mniej modułów lazy-loaded. Wcześniej (143 pliki) generowane było dla praktycznie każdego modułu w routerze. Teraz (37 plików) - moduły są generowany tylko dla modułów najwyższego poziomu.

Poza zwiększonymi paczkami bibliotek, odnalazłem więcej kodu w main.js, który wcześniej był w jednym z modułów lazy-loaded . Być może mamy buga w konfiguracji? W oficjalnej dokumentacji jest sporo przykładów, które powoduje niedoskonały podział modułów.

Chwilowo nie wygląda to spektakularnie. Miej jednak na uwadzę, że to tymczasowa sytuacja byśmy mogli się łatwiej zmigrować! Angular 10 lub Angular 11 będzie działał bez ngcc. Wtedy te wartości będą mniejsze, podobnie jak wykresy “lazy-loaded chunks”.

Czas kompilacji

Angular oferuje dwa tryby kompilacji: ES5 dla starszych przeglądarek i “differential loading”, która produkuje ES5 i ES2015. Sprawdzam czasy kompilacji dla obydwu w kontekście budowania paczki produkcyjnej oraz kompilacji lokalnej wersji developerskiej.

Build produkcyjny

Po włączeniu kompilatora Ivy, budowanie obu procesów przyspieszyło znacząco!

  • 4 minuty zamiast 6 dla "differential loading"
  • 3 minuty 40 sekund, zamiast 4 minuty 45 sekund dla standardowego builda ES5.


Wasz serwer CI na pewno odczuje różnicę.

Szybkość ng serve

Podczas startowania serwera na potrzeby lokalnego developmentu nie istnieje differential loading. Kompilujemy ES2015 albo ES5, nigdy razem. W tym benchmarku sprawdzamy inicjalny start.

Po włączeniu Ivy:

  • ES2015: 1 min 25s zamiast 1min 55s
  • ES5: 1 min 40s zamiast 2 minut


To 20-30s szybciej! Przy okazji można zauważyć, że porzucając wsparcie dla starszych przeglądarek (przełączając się na ES2015), dodatkowo przyspieszymy czasy kompilacji.

Czas rekompilacji

Czas rekompilacji mocno zależy od miejsca w którym robimy zmiany. Pamiętasz podział na main.js i lazy-loaded chunks? 

Angular re-kompiluje zmiany w obrębie jednego pliku - im mniejszy moduł, tym szybsza zmiana. Zmiana która doprowadzi do re-kompilacji main.js będzie trwała najdłużej.

Angular już był zoptymalizowany pod tym kątem całkiem dobrze, ale hej! Ivy wyciska z tego jeszcze kilka sekund.

  • Mały moduł, lazy-loaded: 5-7 sekund zamiast 10-12s
  • Zmiana HTML w jednym z core komponentów: 12-13s zamiast 20s


? 5 sekund! ?Takie wartości w trybie live-reload to czysta przyjemność kodowania. 

Dodam, że takie wyniki to zasługa nie tylko Ivy. W zespole optymalizowaliśmy niedawno czas rekompilacji, który trwał 30-40s!

TAK!

Te wyniki zdecydowanie potwierdzają, warto zacząć migrację do Angular 9 z Ivy.

Jednak! Jest jeden detal, który obniży powyższe wyniki. Pamiętasz ngcc, który umożliwia migrację do Ivy, zachowując wsteczną kompatybilność, ale zarazem pogarszał lekko wyniki kompilacji? W przypadku czasu kompilacji ono również powoduje dodatkowy narzut.

Dla nas, jest to koszt dodatkowych 40-50 sekund dla Angular 9.0 i 20-30 sekund dla Angular 9.1. 

Na szczęście, ngcc jest uruchamiane na żądanie. Lokalnie narzut będzie widoczny tylko podczas pierwszego builda po instalacji nowych zależności.

Czas wykonywania testów

Dostajemy nową implementację klasy TestBed, która od teraz potrafi buforować definicje komponentów, dzięki czemu nie trzeba ich przekompilowywać pomiędzy testami. Jest to istotna zmiana, wcześniej z każdym testem każdy komponent musiał być kompilowany od nowa.

W repozytorium Angular Material, testy przyspieszyły o 25-505! Jak było u nas? Soczyste 0%. :)

Cóż, jest to coś czemu będziemy się musieli mocno przyjrzeć. Przede wszystkim w projekcie nie korzystamy z domyślnego Karma/jasmine, tylko z jest. Być może specyfika konfiguracji innego frameworka testowego powoduje, że w naszym przypadku nie ma różnicy.

Niemniej, jestem niezmiernie szczęśliwy, bo uruchamianie testów w Karma i Jasmine skutecznie zniechęcało mnie do TDD z powodu swojej powolności.

Wnioski

Możesz odnieść wrażenie, że niektóre z wyników niespecjalnie zachęcają do aktualizacji. Otóż nie, nic z tych rzeczy! 

To prawda, że ngcc dodaje narzut który pogarsza trochę wyniki wielkości bundla i szybkości kompilacji na CI. Pamiętaj, że jest to na czas ułatwionej migracji dla całego ekosystemu.

Niezależnie od kompilatora, upewnij się, że robisz analizę bundla od czasu do czasu, a nawet zrób sobie skrypt który Ci to ułatwi albo zrobi za ciebie. Angular team zaleca source-map-explorer.

Niedawno nauczyłem się, że nawet małe podbicie wersji jednej z zależności, albo niefortunne ułożenie struktury importów może znacząco pogorszyć wyniki budowania. To problemy skomplikowanego środowiska front-endowego w którym działamy. Ivy tych problemów nie rozwiąże.

Brzmi interesująco? To jest właśnie to, co robimy w Dynatrace. Analizujemy i dopieszczamy każdy szczegół aplikacji którą budujemy.

<p>Loading...</p>