Ten post został napisany przez Ahmeda Elwasefija, który opowiada w nim, jak został wolontariuszem w ramach Google Summer of Code, oraz opisuje problemy z dostępnością, które zidentyfikował i rozwiązał.
Gdy zbliżał się mój ostatni rok studiów inżynierskich na kierunku informatyka na niemieckim uniwersytecie w Kairze, postanowiłem sprawdzić, czy mogę w jakimś stopniu przyczynić się do rozwoju oprogramowania open source. Zacząłem przeglądać listę problemów w Chromium przeznaczonych dla początkujących i szczególnie zainteresowały mnie ułatwienia dostępu. W wyniku poszukiwań trafiłem na Aarona Leventhala, którego wiedza i chęć pomocy zainspirowały mnie do współpracy przy projekcie. Ta współpraca stała się moją przygodą w ramach Google Summer of Code, w której pracowałam z zespołem Ułatwień dostępu w Chromium.
Po zakończeniu Google Summer of Code kontynuowałem rozwiązywanie nierozwiązanego problemu ze skanowaniem, ponieważ chciałem poprawić wydajność. Dzięki dwóm dotaczeniom z programu OpenCollective Google mogłem kontynuować pracę nad tym projektem, a także podjąć dodatkowe zadania polegające na uproszczeniu kodu w celu zwiększenia jego wydajności.
W tym poście na blogu opisuję moją przygodę z Chromium w ciągu ostatniego półtora roku, ze szczególnym uwzględnieniem wprowadzonych przez nas ulepszeń technicznych, zwłaszcza w zakresie wydajności.
Jak kod ułatwień dostępu wpływa na wydajność w Chrome
Kod dostępności w Chrome pomaga technologiom wspomagającym, takim jak czytniki ekranu, w dostępie do internetu. Jednak po jej włączeniu może to wpłynąć na czas wczytywania, wydajność i żywotność baterii. Dlatego, jeśli nie jest potrzebny, pozostaje nieaktywny, aby nie spowalniać działania. Około 5–10% użytkowników ma włączony kod ułatwień dostępu, często z powodu narzędzi takich jak menedżery haseł i programy antywirusowe, które korzystają z interfejsów API ułatwień dostępu na platformie. Te narzędzia korzystają z tych interfejsów API, aby wchodzić w interakcje z zawartością stron i je modyfikować, np. znajdować pola haseł w menedżerach haseł i w formularzach.
Całkowite pogorszenie się podstawowych danych nie jest jeszcze znane, ale niedawny eksperyment o nazwie Auto Disable Accessibility (automatyczne wyłączanie funkcji ułatwień dostępu, gdy nie są używane) pokazuje, że jest ono dość duże. Problem występuje z powodu ogromnej ilości obliczeń i komunikacji w 2 głównych obszarach kodu źródłowego Chrome dotyczącego ułatwień dostępu: w renderzerze i przeglądarce. Przetwarzacz zbiera informacje o treściach i zmianach w treściach, obliczając właściwości ułatwień dostępu dla drzewa węzłów. Następnie wszystkie zmienione węzły są serializowane i wysyłane przez kanał do głównego wątku interfejsu procesu przeglądarki. Ten wątek odbiera i deserializuje te informacje w identycznym drzewie węzłów, a następnie konwertuje je na odpowiednią formę dla zewnętrznych technologii wspomagających, takich jak czytniki ekranu.
Ulepszenia ułatwień dostępu w Chromium
Następujące projekty zostały ukończone podczas Summer of Code, a potem po nim, finansowane przez program Google OpenCollective.
Poprawa pamięci podręcznej
W Chrome jest specjalna struktura danych o nazwie drzewo ułatwień dostępu, która odzwierciedla drzewo DOM. Służy do ułatwiania dostępu do treści internetowych technologiom wspomagającym. Czasami, gdy urządzenie potrzebuje informacji z tego drzewa, może nie być gotowe do ich pobrania, więc przeglądarka musi zaplanować te żądania na później.
Wcześniej to planowanie było obsługiwane za pomocą metody o nazwie zamknięcia, która polegała na umieszczaniu wywołań zwrotnych w kole. Takie podejście wymaga dodatkowej pracy ze względu na sposób przetwarzania zamknięć.
Aby to poprawić, przełączyliśmy się na system korzystający z enumeracji. Każdemu zadaniu przypisana jest określona wartość enumeracji, a gdy drzewo ułatwień jest gotowe, wywoływana jest odpowiednia metoda dla tego zadania. Ta zmiana ułatwiła zrozumienie kodu i poprawiła jego wydajność o ponad 20%.
Znajdowanie i rozwiązywanie problemów z wydajnością przewijania
Następnie sprawdziłem, jak poprawia się wydajność po wyłączeniu serializacji ramki ograniczającej. Pudełka ograniczające to pozycje i rozmiary elementów na stronie internetowej, w tym szczegóły takie jak szerokość, wysokość i położenie względem elementu nadrzędnego.
Aby to sprawdzić, tymczasowo usunęliśmy kod, który obsługuje ramki ograniczające, i przeprowadziliśmy testy wydajności, aby sprawdzić wpływ tej zmiany. Jeden test, focus-links.html, wykazał ogromną poprawę o około 1618%. To odkrycie stało się podstawą dalszych prac.
Badanie wyników testu powolnego
Zaczęłam sprawdzać, dlaczego ten konkretny test był wolny z ramkami ograniczającymi. Test polegał na tym, że skupiał się na kilku linkach jeden po drugim. Dlatego głównym problemem musi być skupianie się na elementach lub przewijanie, które nastąpiło po wykonaniu działania. Aby to sprawdzić, dodałem do wywołania {preventScroll: true}
w teście wydajności wywołanie focus()
, aby zatrzymać przewijanie.
Gdy przewijanie było wyłączone, czas testu zmniejszył się do 1,2 milisekundy, gdy aktywne były obliczenia dotyczące ramki. Okazało się, że problemem jest przewijanie.
Utworzyliśmy nowy test o nazwie scroll-in-page.html, aby odtworzyć test focus-links, ale zamiast używania fokusa przewija elementy za pomocą scrollIntoView()
. Testowałem przewijanie płynne i natychmiastowe, z obliczeniami ramki ograniczającej i bez nich.
Wyniki pokazały, że przy błyskawicznym przewijaniu i ramkach ograniczających proces zajął około 66 ms. Płynne przewijanie było jeszcze wolniejsze, bo trwało około 124 ms. Wyłączenie ramek ograniczających nie zajęło nam wcale dużo czasu, ponieważ nie wywołało żadnych zdarzeń.
Znaliśmy już ten przypadek, ale dlaczego tak się działo?
Wiemy już, że przewijanie jest przyczyną znacznego spowolnienia serializacji ułatwień dostępu, ale musieliśmy jeszcze ustalić, dlaczego tak się dzieje. W tym celu użyto dwóch narzędzi: perf i pprof, aby podzielić na części działania wykonywane przez proces przeglądarki. Te narzędzia są często używane w języku C++ do profilowania. Na wykresach poniżej widać fragment interesującego fragmentu.
Po zbadaniu sprawy stwierdziliśmy, że problem nie dotyczy kodu deserializacji, ale częstotliwości wywoływania tego kodu. Aby to zrozumieć, musimy przyjrzeć się temu, jak aktualizacje ułatwień dostępu działają w Chromium. Aktualizacje nie są wysyłane pojedynczo. Zamiast tego istnieje centralna lokalizacja o nazwie AXObjectCache
, w której przechowywane są wszystkie właściwości. Gdy nastąpi zmiana węzła, różne metody informują pamięć podręczną o tym, aby oznaczyć ten węzeł jako zmodyfikowany na potrzeby późniejszej serializacji. Następnie wszystkie właściwości nieczystych notatek, w tym niezmienione właściwości, są serializowane i wysyłane do przeglądarki. Chociaż takie rozwiązanie upraszcza kod i zmniejsza złożoność, ponieważ wprowadza tylko jedną ścieżkę aktualizacji, staje się powolne, gdy występują częste zdarzenia „oznaczone jako brudne”, np. podczas przewijania. Zmieniają się tylko wartości scrollX
i scrollY
, ale serializujemy je razem z pozostałymi właściwościami. Częstotliwość aktualizacji osiągnęła ponad 20 razy na sekundę.
Serializacja prostokąta ograniczającego rozwiązuje ten problem dzięki szybszej ścieżce serializacji, która wysyła tylko szczegóły prostokąta ograniczającego, umożliwiając szybkie aktualizowanie bez wpływu na inne właściwości. Ta metoda skutecznie obsługuje zmiany ramki ograniczającej.
Naprawa problemu z przewijaniem
Rozwiązanie było jasne: należy uwzględnić bieżące przesunięcia przewijania z serializacją ograniczonego prostokąta. Dzięki temu aktualizacje przewijania są przetwarzane przez szybką ścieżkę, co poprawia wydajność bez zbędnych opóźnień. Dzięki pakowaniu przesunięć w kierunku pionu i poziomu z danymi o ramkach ograniczających optymalizujemy proces, aby zapewnić płynniejsze i bardziej wydajne aktualizacje, co poprawi wrażenia użytkowników korzystających z funkcji ułatwień dostępu. Po wdrożeniu poprawki w testach przewijania uzyskano poprawę o do 825%.
Upraszczanie kodu
W tym czasie skupiłem się na jakości kodu w ramach projektu o nazwie OnionSoup, który upraszcza kod przez zmniejszenie lub usunięcie niepotrzebnego rozprzestrzeniania kodu na warstwach.
Pierwszy projekt miał na celu usprawnienie serializacji danych ułatwień z renderowania do przeglądarki. Wcześniej dane musiały przejść przez dodatkową warstwę, zanim dotarły do miejsca przeznaczenia, co zwiększało niepotrzebnie złożoność. Uprościliśmy ten proces, umożliwiając wysyłanie danych bezpośrednio, bez pośrednika.
Dodatkowo wykryliśmy i usunęliśmy niektóre nieaktualne zdarzenia, które powodowały niepotrzebne obciążenie systemu, np. zdarzenie, które było wywoływane po zakończeniu tworzenia układu. Zastąpiliśmy je bardziej wydajnym rozwiązaniem.
Wprowadziliśmy też inne drobne ulepszenia. Niestety nie udało się zaobserwować poprawy wydajności w przypadku tych wersji, ale z przyjemnością informujemy, że kod jest teraz znacznie bardziej przejrzysty i sam się dokumentuje. Pomoże to znacznie ułatwić przyszłe ulepszanie skuteczności. Prawdziwe zmiany możesz sprawdzić na moim profilu w gerrit.
Podsumowanie
Współpraca z zespołem ds. ułatwień dostępu w Chromium była bardzo owocna. Dzięki rozwiązywaniu różnych problemów, od optymalizacji wydajności przewijania do upraszczania kodu źródłowego, lepiej poznałam programowanie w ramach tak dużego projektu, a także poznałam ważne narzędzia do profilowania. Ponadto dowiedziałem się, jak ważne jest ułatwianie dostępu do stron internetowych, aby były one dostępne dla wszystkich. Dzięki tym ulepszeniom użytkownicy korzystający z technologii wspomagających mogą korzystać z przeglądarki w bardziej płynny sposób, a dodatkowo zwiększa się ogólna wydajność i skuteczność przeglądarki.
Wyniki były imponujące. Na przykład przejście na używanie list numerycznych do planowania zadań poprawiło wydajność o ponad 20%. Dodatkowo nasza poprawka dotycząca przewijania spowodowała zmniejszenie o nawet 825% w testach przewijania. Upraszczanie kodu nie tylko sprawiło, że kod jest bardziej przejrzysty i łatwy w utrzymaniu, ale też otworzyło drogę do przyszłych ulepszeń.
Chcielibyśmy podziękować Stefanowi Zagerowi, Chrisowi Harrelsonowi i Masonowi Freedowi za wsparcie i wskazówki przez cały rok, a szczególnie Aaronowi Leventhalowi, bez którego nie udałoby się to. Dziękuję też Tab Atkins-Bittner i zespołowi GSoC za pomoc.
Jeśli chcesz brać udział w ważnym projekcie i doskonalić swoje umiejętności, zachęcam do zaangażowania się w zespół Chromium. To świetny sposób na naukę, a programy takie jak Google Summer of Code stanowią doskonały punkt wyjścia.