Optymalizacja programów open-source – raport

Bartosz Janiak


Na wstępie chciałbym wyjaśnić, skąd tak mała (przynajmniej moim zdaniem) ilość materiału, którą udało mi się tutaj zgromadzić.

W pierwszych dniach sierpnia wziąłem się do pracy nad optymalizacją, którą omawiałem w trakcie prezentacji w czerwcu, tj. przyśpieszeniem czasu uruchomienia środowiska xfce (moduł xfce4-session), poprzez prefetching potrzebnych później bibliotek w trakcie ładowania sesji oraz uruchomienie wszystkich procesów środowiska równocześnie. Patch dotyczący pierwszej optymalizacji był gotowy już w czerwcu, ale nie dawał oczekiwanych rezultatów (nie wszystkie testy wykazywały przyśpieszenie ładowania, a nawet jeśli ono wystąpiło – było niezadowalająco niskie). Z drugą optymalizacją był inny problem – dzięki niej czas ładowania środowiska zmniejszył się o ~1 sekundę (p. prezentacja), ale sam patch w dość losowy sposób zmniejszał funkcjonalność środowiska - średnio co 2-3 ładowanie nie uruchamiał się poprawnie moduł xfdesktop.

W przeciągu następnego tygodnia udało mi się:

Szukałem pomocy na liście dyskusyjnej xfce4-dev , również bez skutku. Możliwe, że udałoby mi się dojść do jakiegoś rozwiązania, gdyby nie pojawił się inny problem – 10 sierpnia dość poważnie zachorowałem i na kilka dni kompletnie uniemożliwiło mi to dostęp do komputera. Gdy mój stan nieco się polepszył, musiałem z kolei przejść serię różnych badań, które – wraz z oczekiwaniem w kolejkach i dojazdami – nie zostawiły mi zbyt dużo czasu na programowanie. Do pracy mogłem wrócić dopiero 22 sierpnia.

Wtedy zdecydowałem, że kontynuowanie prób z xfce4-session jest zbyt ryzykowne – nie miałem żadnego pomysłu, jak rozwiązać tę kwestię, a zbliżał się już czas oddawania gotowych optymalizacji. Stąd wziąłem się za mniejszy projekt, który – jak się spodziewałem – zdążę ukończyć przed 31 sierpnia.

xfce4-battery-plugin

xfce4-battery-plugin to typowa graficzna wtyczka służąca do odczytu stanu baterii i zasilania. Doskonale integruje się z panelem xfce, ma jednak zasadniczą wadę: bardzo słabą responsywność. Wyświetlenie tooltipa z pozostałym czasem pracy na baterii trwa kilka sekund, a zmiana konfiguracji jest bardzo irytująca – program na wszelkie kliknięcia w interfejsie reaguje ze sporym opóźnieniem.

Pobieżna analiza kodu, dodanie paru znaczników do debugowania i strace'owanie programu szybko pokazały, gdzie jest problem:

# strace -e trace=access,open -f -tt -r -p 6717
(...)     
     0.057583 access("DEBUG: libacpi.c:364", R_OK) = -1 ENOENT (No such file or directory)
     0.000136 open("/proc/acpi/battery/BAT0/info", O_RDONLY) = 6
     1.299184 access("DEBUG: libacpi.c:387", R_OK) = -1 ENOENT (No such file or directory)
(...)     
     0.103768 access("DEBUG: libacpi.c:364", R_OK) = -1 ENOENT (No such file or directory)
     0.000092 open("/proc/acpi/battery/BAT0/info", O_RDONLY) = 6
     0.940560 access("DEBUG: libacpi.c:387", R_OK) = -1 ENOENT (No such file or directory)

xfce4-battery-plugin pobiera stan baterii z plików zawartych w systemie plików procfs. Zawartość tych plików (konkretnie: /proc/acpi/battery/BATn/{state,info} oraz /proc/acpi/ac_adapter/AC/state ) jest generowana „na życzenie” przez jądro, to jednak chwilę trwa:

# time cat /proc/acpi/ac_adapter/AC/state > /dev/null
real 0m1.134s
user 0m0.000s
sys 0m0.004s

Odczyt tych plików w xfce4-battery-plugin jest dokonywany „brutalnie” (tj. fopen-fread-fclose ) w funkcjach read_acpi_info , read_acpi_state oraz read_acad_state . Nic dziwnego, że blokuje się wtedy interfejs. Postanowiłem więc przepisać fragmenty odpowiedzialne za odczyt tych plików przy użyciu funkcji, które przeniosą blokującą operację odczytu do innego wątku. Oczywistym wyborem była biblioteka glib (program i tak musi się z nią linkować ze względu na zależność od gtk) i funkcje obsługi kanałów I/O.

xfce4-battery-plugin-0.5.0-giochannel.patch

Ten patch nie rozwiązuje jednak problemu: interfejs nadal się blokuje! Najwyraźniej przeniesienie odczytu do odrębnego wątku nie wystarczy. Kilka informacji znalezionych na ten temat w internecie przekonuje mnie do tego, żeby odczyt wykonywać w odrębnym procesie. fork() byłby jednak niepotrzebnym marnotrawstwem zasobów; postanowiłem więc wypróbować udostępniane przez jądro linuxa (ale i kompatybilne ze standardem POSIX) funkcje z rodziny aio :

xfce4-battery-plugin-0.5.0-aio.patch

Niestety, ten patch również nie przyniósł oczekiwanych przeze mnie rezultatów. Zirytowany i przekonany, że robię jakiś prosty błąd, postanowiłem podejrzeć, jak ten problem jest rozwiązany w innych monitorach baterii na linuxie. Okazało się, że:

Wniosek był dla mnie prosty: problem znajduje się na poziomie jądra, a nie samego programu sprawdzającego stan baterii. Przeanalizowałem odpowiedni kod jądra ( drivers/acpi/battery.c ) i dokumentację procfs'a. Okazuje się, że przy takim API, jakie oferuje procfs, nie da się(!) napisać nieblokującego interfejsu do jakiegokolwiek urządzenia – wszelka interakcja z interfejsem ACPI (która zwykle jest niezauważalna, ale w szczególnym przypadku baterii trwa ok. sekundy) musi odbyć się już po rozpoczęciu odczytu odpowiedniego pliku w /proc przez zewnętrzny program.

Powyższe spostrzeżenia wstrzymały postęp moich prac do momentu, gdy przypomniałem sobie o innym wirtualnym systemie plików, który oferuje linux – sysfs (muszę przyznać, że wcześniej nie byłem szczególnie świadomy jego zastosowań i stąd nie przyszło mi to głowy od razu). Sysfs okazuje się doskonale rozwiązywać problem z „blokowaniem” już na poziomie architektury – to sterownik urządzenia generuje sygnał, który powoduje zmianę jakiejś jego własności dostępnej przez sysfs. Stąd dostęp do tych własności jest natychmiastowy.

Po przeczytaniu kilku artykułów na temat sysfs'a zacząłem się przymierzać do przepisania battery.c na sysfs, niestety – po kilku minutach google'owania natrafiłem na ten patch:

http://marc.info/?l=linux-acpi&m=118727459117847&w=2

Jest to na tyle nowy i niesprawdzony kod, że nie znajduje się jeszcze nawet w repozytorium linux-acpi, nie wspominając o innych rozwojowych wersjach kernela. Postanowiłem go jednak wykorzystać, aby móc dodać obsługę sysfs do xfce4-battery-plugin. Ostatecznie sysfs'owy interfejs dostępu do baterii, w takiej czy innej formie, pojawi się w stabilnym jądrze, więc warto dodać do niego obsługę w monitorze baterii.

Trochę czasu zajęło mi skompletowanie odpowiednich patch'y (powyższy oczywiście nie aplikuje się bezpośrednio na stabilne jądro). Do obsługi baterii i zasilania przez sysfs potrzebne są:

Przy konfiguracji należy włączyć flagę POWER_SUPPLY (i oczywiście obsługę baterii przez ACPI).

Mając tak przygotowane jądro dopisałem do xfce4-battery-plugin obsługę nowego interfejsu:

xfce4-battery-plugin-0.5.0-sysfs.patch

Nie wszystkie własności są jeszcze obsługiwane, ale do ustalenia poziomu naładowania baterii i tego, czy laptop jest podłączony do zasilania – to w zupełności wystarcza. Nie muszę chyba wspominać o tym, że żadne kłopoty z responsywnością interfejsu już nie występują :)