Przejdź do treści głównej

Wydajność React Native w Marketplace

· 5 minut czytania
Inżynier oprogramowania w Facebooku
Nieoficjalne Tłumaczenie Beta

Ta strona została przetłumaczona przez PageTurner AI (beta). Nie jest oficjalnie zatwierdzona przez projekt. Znalazłeś błąd? Zgłoś problem →

React Native jest używany w wielu miejscach w różnych aplikacjach z rodziny Facebooka, w tym w zakładce najwyższego poziomu w głównej aplikacji. W tym poście skupiamy się na bardzo widocznym produkcie – Marketplace. Jest dostępny w kilkunastu krajach i umożliwia użytkownikom odkrywanie produktów i usług oferowanych przez innych użytkowników.

W pierwszej połowie 2017 roku, dzięki wspólnym wysiłkom zespołów Relay, Marketplace, Mobile JS Platform i React Native, skróciliśmy czas do interakcji (TTI) w Marketplace o połowę na urządzeniach z Androidem klasy rocznikowej 2010-11. Facebook historycznie uznawał te urządzenia za urządzenia z Androidem niskiej klasy, które mają najwolniejsze czasy TTI na dowolnej platformie.

Typowy proces uruchamiania React Native wygląda następująco:

Uwaga: przedstawione proporcje nie są reprezentatywne i będą się różnić w zależności od konfiguracji i sposobu użycia React Native.

Najpierw inicjujemy rdzeń React Native (tzw. „Bridge”), a następnie uruchamiamy produktowy kod JavaScript, który określa, jakie natywne widoki React Native wyrenderuje w czasie przetwarzania natywnego.

Alternatywne podejście

Jednym z naszych wcześniejszych błędów było pozwolenie, by Systrace i CTScan napędzały nasze działania optymalizacyjne. Narzędzia te pomogły nam znaleźć wiele nisko wiszących owoców w 2016 roku, ale odkryliśmy, że zarówno Systrace, jak i CTScan nie odzwierciedlają scenariuszy produkcyjnych i nie mogą symulować warunków rzeczywistych. Proporcje czasu spędzanego w poszczególnych segmentach są często nieprawidłowe, a czasem wręcz bardzo odbiegają od rzeczywistości. W skrajnych przypadkach operacje, które powinny zająć kilka milisekund, faktycznie trwały setki lub tysiące milisekund. Mimo to CTScan pozostaje przydatny – wykrywa jedną trzecią regresji przed trafieniem do produkcji.

Na Androidzie niedoskonałości tych narzędzi przypisujemy faktowi, że 1) React Native to framework wielowątkowy, 2) Marketplace współdzieli przestrzeń z wieloma złożonymi widokami (jak Newsfeed czy inne zakładki najwyższego poziomu) oraz 3) czasy obliczeń bardzo się różnią. Dlatego w tym okresie pozwoliliśmy, by pomiary produkcyjne i szczegółowe analizy napędzały niemal wszystkie nasze decyzje i priorytety.

W głąb instrumentacji produkcyjnej

Instrumentacja produkcji może wydawać się prosta, ale okazała się dość złożonym procesem. Wymagała wielu cykli iteracyjnych (po 2-3 tygodnie każdy) z powodu opóźnień: od zatwierdzenia commita w gałęzi głównej, przez wypuszczenie aplikacji w Sklepie Play, po zebranie wystarczającej liczby próbek produkcyjnych dla weryfikacji. Każda iteracja obejmowała sprawdzenie dokładności naszych analiz, ich odpowiedniego poziomu szczegółowości oraz poprawnego sumowania się do całkowitego czasu. Nie mogliśmy polegać na wersjach alpha i beta, ponieważ nie reprezentują ogólnej populacji. W efekcie mozolnie zbudowaliśmy bardzo dokładny ślad produkcyjny oparty na agregacji milionów próbek.

Jednym z powodów skrupulatnego sprawdzania, czy każda milisekunda w analizach sumuje się do metryk nadrzędnych, było wczesne wykrycie luk w instrumentacji. Okazało się, że nasze wstępne analizy nie uwzględniały przestojów spowodowanych przełączaniem wątków. Same przełączania nie są kosztowne, ale przełączanie na zajęte wątki już wykonujące pracę jest bardzo kosztowne. Odtworzyliśmy te blokady lokalnie poprzez dodanie wywołań Thread.sleep() w kluczowych momentach i rozwiązaliśmy problem poprzez:

  1. usunięcie zależności od AsyncTask,

  2. rezygnację z wymuszonej inicjalizacji ReactContext i NativeModules w wątku UI oraz

  3. usunięcie zależności od pomiaru ReactRootView podczas inicjalizacji.

Razem, usunięcie tych problemów z blokadą wątków skróciło czas uruchamiania o ponad 25%.

Metryki z produkcji podważyły też niektóre z naszych wcześniejszych założeń. Przykładowo, wcześniej wstępnie ładowaliśmy wiele modułów JavaScript na ścieżce startowej, zakładając, że zgrupowanie ich w jednym pakiecie zmniejszy koszt ich inicjalizacji. Jednak koszt wstępnego ładowania i grupowania tych modułów znacznie przewyższał korzyści. Poprzez rekonfigurację naszych czarnych list inline require i usunięcie modułów JavaScript ze ścieżki startowej, udało nam się uniknąć ładowania niepotrzebnych modułów, takich jak Relay Classic (gdy potrzebny był tylko Relay Modern). Dziś nasza sekcja RUN_JS_BUNDLE jest ponad 75% szybsza.

Odnaleźliśmy też korzyści badając natywne moduły specyficzne dla produktu. Przykładowo, poprzez leniwą inicjalizację zależności modułu natywnego, zmniejszyliśmy jego koszt o 98%. Usuwając rywalizację uruchamiania Marketplace z innymi produktami, skróciliśmy czas startu o równoważny przedział.

Najlepsze jest to, że wiele z tych ulepszeń ma szerokie zastosowanie na wszystkich ekranach budowanych z React Native.

Podsumowanie

Ludzie zakładają, że problemy z wydajnością startu React Native wynikają z powolnego JavaScriptu lub nadmiernie długiego czasu sieciowego. Choć przyspieszenie elementów takich jak JavaScript skróciłoby TTI w znaczącym stopniu, każdy z tych czynników stanowi znacznie mniejszy procent TTI niż wcześniej sądzono.

Dotychczasowa lekcja brzmi: mierz, mierz i jeszcze raz mierz! Niektóre korzyści płyną z przenoszenia kosztów czasu wykonania na etap budowania, jak w przypadku Relay Modern i Lazy NativeModules. Inne wynikają z unikania pracy poprzez inteligentniejsze równoległe przetwarzanie kodu lub usuwanie martwego kodu. Kolejne płyną z dużych zmian architektonicznych w React Native, jak usuwanie blokad wątków. Nie istnieje jedno wielkie rozwiązanie problemów wydajności, a długoterminowe sukcesy będą wynikać z przyrostowej instrumentacji i ulepszeń. Nie pozwól, by decyzjami kierowały uprzedzenia poznawcze. Zamiast tego, skrupulatnie zbieraj i interpretuj dane produkcyjne, aby kierować przyszłymi pracami.

Plany na przyszłość

Długoterminowo chcemy, aby TTI Marketplace było porównywalne z podobnymi produktami zbudowanymi natywnie, a ogólnie – by wydajność React Native dorównywała natywnej. Co więcej, chociaż w tym półroczu drastycznie zmniejszyliśmy koszt startu mostka React Native o około 80%, planujemy zbliżyć ten koszt do zera poprzez projekty takie jak Prepack i więcej przetwarzania na etapie budowania.