W wersji 8.0 środowiska wykonawczego Androida (ART) wprowadzono znaczne ulepszenia. Poniżej znajdziesz listę ulepszeń, których mogą oczekiwać producenci urządzeń w ramach ART.
Równoległy skompresowany odbiorca pamięci
Jak ogłosiliśmy na konferencji Google I/O, ART zawiera nowy równoległy kompaktowy zbieracz śmieci (GC) w Androidzie 8.0. Ten kolekcjoner zagęszcza stos za każdym razem, gdy działa GC, oraz podczas działania aplikacji, z jedną krótką przerwą na przetworzenie korzeni wątków. Oto jej zalety:
- GC zawsze zagęszcza stos: rozmiary stosu są średnio o 32% mniejsze niż w Androidzie 7.0.
- Kompilacja umożliwia przydzielanie obiektów wskazujących na lokalny wskaźnik przesunięcia wątku: przydziały są o 70% szybsze niż w Androidzie 7.0.
- Oferuje o 85% krótsze czasy pauzy w benchmarku H2 w porównaniu z GC Androida 7.0.
- Czasy pauzy nie są już skalowane wraz z rozmiarem stosu; aplikacje powinny mieć możliwość używania dużych stosów bez obaw o zacinanie.
- Szczegóły implementacji GC – czytanie:
- Bariery odczytu to niewielka ilość pracy wykonywanej w przypadku każdego odczytu pola obiektu.
- Są one optymalizowane w kompilatorze, ale mogą spowolnić działanie w niektórych przypadkach.
Optymalizacja zapętlenia
W wersji Androida 8.0 ART wykorzystuje wiele różnych optymalizacji pętli:
- Eliminacja kontroli zakresów:
- Statyczne: zakresy są sprawdzone pod kątem mieści się w granicach w czasie kompilacji
- Dynamiczna: testy w czasie wykonywania kodu, które zapewniają, że pętle pozostają w ramach (w przeciwnym razie deoptymalizacja)
- Eliminacja zmiennych indukcyjnych
- Usuń nieużywane indukcje
- Zastąp indukcję, która jest używana tylko po pętli, wyrażeniami w formie zamkniętej
- eliminacja martwego kodu w ciele pętli, usunięcie całych pętli, które stały się martwe;
- Redukcja siły
- Przekształcenia pętli: odwracanie, przełączanie, dzielenie, rozwijanie, unimodular itp.
- SIMDizacja (inaczej wektoryzacja)
Optymalizator pętli znajduje się w ramach własnego etapu 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 przepisują plik CFG w bardziej rozbudowany sposób niż zwykle, ponieważ większość narzędzi CFG (patrz nodes.h) koncentruje się na tworzeniu pliku CFG, a nie jego przepisywaniu.
Analiza hierarchii zajęć
ART w Androidzie 8.0 korzysta z analizy hierarchii klas (CHA), czyli optymalizacji kompilatora, która przekształca wywołania wirtualne w wywołania bezpośrednie na podstawie informacji wygenerowanych przez analizę hierarchii klas. Wywołania wirtualne są kosztowne, ponieważ są implementowane za pomocą wyszukiwania w tabeli V i wymagają kilku zależnych wczytań. Nie można też wstawiać połączeń wirtualnych.
Oto podsumowanie powiązanych ulepszeń:
- Dynamiczna aktualizacja stanu metody o pojedynczej implementacji – pod koniec procesu łączenia klas, gdy tabela v została wypełniona, ART przeprowadza porównanie poszczególnych wpisów z tabelą v superklasy.
- Optymalizacja kompilatora – kompilator wykorzysta informacje o jednym wdrożeniu metody. Jeśli metoda A.foo ma ustawioną flagę implementacji pojedynczej, kompilator zdewirtualizuje wywołanie wirtualne na bezpośrednie, a następnie spróbuje wstawić to bezpośrednie wywołanie.
- Unieważnienie skompilowanego kodu – również na koniec czasu łączenia klasy, gdy zaktualizowane zostaną informacje o jednej implementacji. Jeśli metoda A.foo, która wcześniej miała jedną implementację, ale ten stan został unieważniony, cały skompilowany kod, który zależy od założenia, że metoda A.foo ma jedną implementację, musi zostać unieważniony.
- Deoptymalizacja – w przypadku kodu skompilowanego na żywo, który znajduje się na stosie, zostanie zainicjowana deoptymalizacja, aby wymusić przełączenie nieprawidłowego skompilowanego kodu w tryb interpretera i tym samym zapewnić poprawność. Zostanie użyty nowy mechanizm deoptymalizacji, który jest hybrydą deoptymalizacji synchronicznej i asynchronicznej.
pamięci podręcznej w plikach .oat,
ART używa teraz pamięci podręcznej w ramach procesu i optymalizuje strony wywołania, dla których istnieje wystarczająca ilość danych. Funkcja wbudowanych pamięci podręcznych rejestruje dodatkowe informacje o czasie wykonywania w profilach i wykorzystuje je do dodawania optymalizacji dynamicznych do kompilacji z wyprzedzeniem.
Dexlayout
Dexlayout to biblioteka wprowadzona w Androidzie 8.0, która analizuje pliki dex i zmienia ich kolejność zgodnie z profilem. Dexlayout wykorzystuje informacje z profilowania środowiska wykonawczego do zmiany kolejności sekcji pliku dex podczas konserwacji w trybie bezczynności na urządzeniu. Dzięki grupowaniu części pliku dex, do których często sięga się razem, programy mogą mieć lepsze wzorce dostępu do pamięci, co zapewnia lepszą lokalizację, oszczędza pamięć RAM i skraca czas uruchamiania.
Informacje o profilu są obecnie dostępne tylko po uruchomieniu aplikacji, dexlayout jest zintegrowany z kompilacją dex2oat na urządzeniu podczas konserwacji w trybie bezczynności.
Usuwanie pamięci podręcznej Dex
Do Androida 7.0 obiekt DexCache miał 4 duże tablice proporcjonalne do liczby określonych elementów w pliku DexFile, a mianowicie:
- strings (jedno odwołanie na DexFile::StringId),
- typów (po jednym odwołaniu na DexFile::TypeId),
- metod (jeden wskaźnik na DexFile::MethodId),
- pola (jeden natywny wskaźnik na DexFile::FieldId).
Te tablice były używane do szybkiego pobierania obiektów, które zostały wcześniej rozwiązane. W Androidzie 8.0 usunięto wszystkie tablice z wyjątkiem tablicy methods.
Wydajność tłumacza
Wydajność interpretera znacznie wzrosła w wersji 7.0 Androida dzięki wprowadzeniu „mterp” – interpretera z mechanizmem pobierania/dekodowania/interpretowania kodu źródłowego napisanego w języku asemblera. Mterp jest wzorowany na szybkim interpreterze Dalvik i obsługuje procesory arm, arm64, x86, x86_64, mips i mips64. W przypadku kodu obliczeniowego mterp w Art jest mniej więcej porównywalny z szybkim interpreterem Dalvik. W niektórych sytuacjach może się jednak zdarzyć, że będzie ona znacznie, a nawet bardzo znacznie wolniejsza:
- Wywołanie wydajności.
- manipulacja ciągami tekstowymi i inne intensywne korzystanie z metod uznawanych za specyficzne dla Dalvik.
- Większe wykorzystanie pamięci podręcznej.
Android 8.0 rozwiązuje te problemy.
Więcej informacji o wstawianiu
Od Androida 6.0 usługa ART może wstawiać w kod źródłowy dowolne wywołanie w tych samych plikach dex, ale mogła wstawiać tylko metody liściaste z różnych plików dex. Ograniczenie to zostało wprowadzone z 2 przyczyn:
- Wstawianie kodu z innego pliku dex wymaga użycia pamięci podręcznej tego pliku, a nie pamięci podręcznej pliku wywołującego, w przeciwieństwie do wstawiania kodu z tego samego pliku dex, który może ponownie użyć pamięci podręcznej wywołującego. Bufor dex jest potrzebny w skompilowanym kodzie do obsługi kilku instrukcji, takich jak wywołania statyczne, wczytywanie ciągu znaków czy wczytywanie klasy.
- Mapy stosu kodują tylko indeks metody w bieżącym pliku dex.
Aby wyeliminować te ograniczenia, Android 8.0:
- Usuwanie z skompilowanego kodu dostępu do pamięci podręcznej dex (patrz też sekcja „Dezaktywacja pamięci podręcznej dex”)
- Rozszerza kodowanie mapy stosu.
Ulepszenia synchronizacji
Zespół ART dopracował ścieżki kodu MonitorEnter/MonitorExit i zmniejszył zależność od tradycyjnych barier pamięci w ARMv8, zastępując je nowszymi instrukcjami (acquire/release) tam, gdzie to możliwe.
Szybsze metody natywne
Szybsze wywołania natywne interfejsu JNI (Java Native Interface) są dostępne za pomocą adnotacji @FastNative
i @CriticalNative
. Te wbudowane optymalizacje ART w czasie wykonywania przyspieszają przejścia JNI i zastępują wycofane oznaczenia !bang JNI. Adnotacje nie mają wpływu na metody niestandardowe i są dostępne tylko dla kodu platformy w języku Java na platformie bootclasspath
(bez aktualizacji w Google Play).
Adnotacja @FastNative
obsługuje metody niestatyczne. Użyj tego elementu, jeśli metoda uzyskuje dostęp do elementu jobject
jako parametru lub wartości zwracanej.
Adnotacja @CriticalNative
zapewnia jeszcze szybszy sposób uruchamiania metod natywnych z tymi ograniczeniami:
-
Metody muszą być statyczne – nie można używać obiektów w parametrach, wartościach zwracanych ani w implikowanym
this
. - Do metody natywnej przekazywane są tylko typy proste.
-
Metoda natywna nie używa parametrów
JNIEnv
ijclass
w definicji funkcji. -
Metoda musi być zarejestrowana w
RegisterNatives
, a nie polegać na łączeniu JNI dynamicznie.
@FastNative
może zwiększyć wydajność metody natywnej nawet 3-krotnie, a @CriticalNative
– nawet 5-krotnie. Na przykład przejście JNI zmierzone na urządzeniu Nexus 6P:
wywołanie interfejsu JNI (Java Native Interface), | Czas wykonywania (w nanosekundach) |
---|---|
Zwykła metoda JNI | 115 |
!bang JNI | 60 |
@FastNative |
35 |
@CriticalNative |
25 |