Środowisko wykonawcze Androida (ART) zostało znacznie ulepszone w Androidzie 8.0. Poniższa lista zawiera podsumowanie ulepszeń, których producenci urządzeń mogą oczekiwać w ART.
Równoczesny kompresujący moduł odśmiecania
Jak ogłosiliśmy na konferencji Google I/O, w Androidzie 8.0 ART ma nowy, równoległy, kompaktowy moduł odśmiecania pamięci (GC). Ten moduł zbierający kompresuje stertę za każdym razem, gdy uruchamia się odśmiecanie pamięci, i podczas działania aplikacji, z tylko jedną krótką przerwą na przetwarzanie wątków głównych. Oto jego zalety:
- GC zawsze kompresuje stertę: średnio o 32% mniejsze rozmiary sterty w porównaniu z Androidem 7.0.
- Kompaktowanie umożliwia przydzielanie obiektów wskaźnika lokalnego wątku: przydzielanie jest o 70% szybsze niż w Androidzie 7.0.
- W przypadku testu porównawczego H2 oferuje o 85% krótsze czasy wstrzymania niż moduł GC w Androidzie 7.0.
- Czasy wstrzymania nie są już skalowane w zależności od rozmiaru sterty. Aplikacje powinny móc używać dużych stert bez obaw o zacinanie się.
- Szczegóły implementacji GC – bariery odczytu:
- Bariery odczytu to niewielka ilość pracy wykonywanej przy odczycie każdego pola obiektu.
- Są one optymalizowane w kompilatorze, ale mogą spowalniać niektóre przypadki użycia.
Optymalizacje pętli
W ART w Androidzie 8.0 stosuje się wiele różnych optymalizacji pętli:
- Eliminacja sprawdzania zakresu
- Statyczne: zakresy są w czasie kompilacji uznawane za mieszczące się w granicach.
- Dynamiczne: testy w czasie działania zapewniają, że pętle pozostają w zakresie (w przeciwnym razie następuje deoptymalizacja).
- Eliminacja zmiennych indukcyjnych
- Usuwanie martwej indukcji
- Zastąp indukcję, która jest używana tylko po pętli, wyrażeniami w formie zamkniętej.
- Eliminacja martwego kodu w treści pętli, usuwanie całych pętli, które stają się martwe
- Zmniejszenie siły
- Przekształcenia pętli: odwracanie, zamiana, dzielenie, rozwijanie, unimodularne itp.
- SIMDizacja (nazywana też wektoryzacją)
Optymalizator pętli znajduje się w osobnym etapie optymalizacji w kompilatorze ART. Większość optymalizacji pętli jest podobna do optymalizacji i uproszczeń w innych miejscach. Problemy pojawiają się w przypadku niektórych optymalizacji, które w bardziej złożony niż zwykle sposób przepisują CFG, ponieważ większość narzędzi CFG (patrz nodes.h) koncentruje się na tworzeniu CFG, a nie na jego przepisywaniu.
Analiza hierarchii klas
ART w Androidzie 8.0 korzysta z analizy hierarchii klas (CHA), czyli optymalizacji kompilatora, która dewirtualizuje wywołania wirtualne do wywołań bezpośrednich na podstawie informacji wygenerowanych przez analizę hierarchii klas. Wywołania wirtualne są kosztowne, ponieważ są realizowane na podstawie wyszukiwania w tabeli funkcji wirtualnych i wymagają kilku zależnych operacji wczytywania. Poza tym wywołań wirtualnych nie można wstawiać w kodzie.
Oto podsumowanie powiązanych ulepszeń:
- Dynamiczne aktualizowanie stanu metody pojedynczej implementacji – po zakończeniu czasu łączenia klas, gdy tabela wirtualna jest wypełniona, ART przeprowadza porównanie pozycji po pozycji z tabelą wirtualną klasy nadrzędnej.
- Optymalizacja kompilatora – kompilator wykorzysta informacje o pojedynczej implementacji metody. Jeśli metoda A.foo ma ustawioną flagę pojedynczej implementacji, kompilator zdeirtualizuje wywołanie wirtualne do wywołania bezpośredniego, a następnie spróbuje wstawić wywołanie bezpośrednie w kodzie.
- Unieważnienie skompilowanego kodu – również pod koniec czasu łączenia klas, gdy informacje o pojedynczej implementacji są aktualizowane, jeśli metoda A.foo, która wcześniej miała pojedynczą implementację, ale ten stan jest teraz unieważniony, cały skompilowany kod, który zależy od założenia, że metoda A.foo ma pojedynczą implementację, musi zostać unieważniony.
- Deoptymalizacja – w przypadku skompilowanego na żywo kodu, który znajduje się na stosie, zostanie zainicjowana deoptymalizacja, aby wymusić przejście unieważnionego skompilowanego kodu w tryb interpretera i zagwarantować poprawność. Zostanie użyty nowy mechanizm deoptymalizacji, który jest hybrydą deoptymalizacji synchronicznej i asynchronicznej.
Pamięć podręczna w plikach .oat
ART korzysta teraz z pamięci podręcznych wbudowanych w kod i optymalizuje miejsca wywołań, dla których jest wystarczająca ilość danych. Funkcja pamięci podręcznych wbudowanych rejestruje dodatkowe informacje o czasie działania w profilach i używa ich do dodawania dynamicznych optymalizacji do kompilacji z wyprzedzeniem.
Dexlayout
Dexlayout to biblioteka wprowadzona w Androidzie 8.0, która służy do analizowania plików dex i zmieniania ich kolejności zgodnie z profilem. Dexlayout ma na celu wykorzystanie informacji z profilowania w czasie działania do zmiany kolejności sekcji pliku dex podczas kompilacji w ramach konserwacji w czasie bezczynności na urządzeniu. Grupowanie części pliku DEX, do których często uzyskuje się dostęp razem, może poprawić wzorce dostępu do pamięci programów dzięki lepszej lokalizacji, co pozwala zaoszczędzić pamięć RAM i skrócić czas uruchamiania.
Informacje o profilu są obecnie dostępne tylko po uruchomieniu aplikacji, dlatego dexlayout jest zintegrowany z kompilacją na urządzeniu dex2oat podczas bezczynnej konserwacji.
Usuwanie pamięci podręcznej Dex
Do Androida 7.0 obiekt DexCache zawierał 4 duże tablice proporcjonalne do liczby określonych elementów w pliku DexFile:
- ciągi znaków (jedno odwołanie na DexFile::StringId),
- typy (1 odwołanie na DexFile::TypeId),
- metody (jeden wskaźnik natywny na DexFile::MethodId),
- pola (jeden wskaźnik natywny na DexFile::FieldId).
Te tablice służyły do szybkiego pobierania obiektów, które wcześniej zostały przez nas rozpoznane. W Androidzie 8.0 usunięto wszystkie tablice z wyjątkiem tablicy metod.
Wydajność tłumacza
W Androidzie 7.0 znacznie poprawiliśmy wydajność interpretera, wprowadzając „mterp” – interpreter z podstawowym mechanizmem pobierania, dekodowania i interpretowania napisanym w języku asemblera. Mterp jest wzorowany na szybkim interpreterze Dalvik i obsługuje architektury arm, arm64, x86, x86_64, mips i mips64. W przypadku kodu obliczeniowego mterp w ART jest w przybliżeniu porównywalny z szybkim interpreterem Dalvika. W niektórych sytuacjach może być jednak znacznie, a nawet drastycznie wolniejsza:
- wywołać wydajność,
- manipulowanie ciągami znaków i inne operacje intensywnie korzystające z metod rozpoznawanych jako funkcje wewnętrzne w Dalviku.
- większe wykorzystanie pamięci stosu,
Android 8.0 rozwiązuje te problemy.
Więcej wstawiania
Od Androida 6.0 ART może wstawiać dowolne wywołanie w ramach tych samych plików dex, ale z różnych plików dex może wstawiać tylko metody liściowe. Ograniczenie to wynikało z 2 przyczyn:
- Wstawianie z innego pliku DEX wymaga użycia pamięci podręcznej DEX tego pliku, w przeciwieństwie do wstawiania z tego samego pliku DEX, które może po prostu ponownie użyć pamięci podręcznej DEX wywołującego. Pamięć podręczna DEX jest potrzebna w skompilowanym kodzie w przypadku kilku instrukcji, takich jak wywołania statyczne, wczytywanie ciągów znaków czy wczytywanie klas.
- Mapy stosu kodują tylko indeks metody w bieżącym pliku DEX.
Aby rozwiązać te problemy, Android 8.0:
- Usuwa dostęp do pamięci podręcznej DEX z skompilowanego kodu (patrz też sekcja „Usuwanie pamięci podręcznej DEX”).
- Rozszerza kodowanie mapy stosu.
Ulepszenia synchronizacji
Zespół ART dostroił ścieżki kodu MonitorEnter/MonitorExit i zmniejszył zależność od tradycyjnych barier pamięci na architekturze ARMv8, zastępując je w miarę możliwości nowszymi instrukcjami (acquire/release).
Szybsze metody natywne
Szybsze wywołania natywne interfejsu Java Native Interface (JNI) są dostępne dzięki adnotacjom @FastNative
i @CriticalNative
. Te wbudowane optymalizacje środowiska wykonawczego ART przyspieszają przejścia JNI i zastępują obecnie wycofaną notację !bang JNI. Adnotacje nie mają wpływu na metody inne niż natywne i są dostępne tylko w kodzie języka Java na platformie bootclasspath
(bez aktualizacji Sklepu Play).
Adnotacja @FastNative
obsługuje metody niestatyczne. Użyj tego tagu, jeśli metoda uzyskuje dostęp do jobject
jako parametru lub wartości zwracanej.
Adnotacja @CriticalNative
umożliwia jeszcze szybsze uruchamianie metod natywnych, ale z tymi ograniczeniami:
-
Metody muszą być statyczne – nie mogą mieć obiektów jako parametrów, wartości zwracanych ani niejawnego parametru
this
. - Do metody natywnej przekazywane są tylko typy proste.
-
Metoda natywna nie używa w definicji funkcji parametrów
JNIEnv
ijclass
. -
Metoda musi być zarejestrowana w
RegisterNatives
zamiast korzystać z dynamicznego łączenia JNI.
@FastNative
może zwiększyć skuteczność metody natywnej nawet 3-krotnie, a @CriticalNative
nawet 5-krotnie. Na przykład przejście JNI zmierzone na urządzeniu Nexus 6P:
Wywołanie Java Native Interface (JNI) | Czas wykonywania (w nanosekundach) |
---|---|
Zwykły JNI | 115 |
!bang JNI | 60 |
@FastNative |
35 |
@CriticalNative |
25 |