Ускорение запуска приложений в Linux

от автора

Уже написаны тонны статей на данную тему, но тем не менее писатели дистрибутивов и майнтенеры пакетов часто пропускают мимо такие вещи как совпадение путей и каталогов поиска используемых библиотек и ресурсов: картинок, звуков, иконок, шрифтов, и пр.

На примере собранной статистики с утилиты bootchart или подобных можно увидеть количество запускаемых утилит и сервисов при загрузке системы: www.bootchart.org/samples.html

Каждый запускаемы бинарник, обычно динамически, слинкован минимум с glibc, и другими ему нужными библиотеками. По правильной схеме, приложение открывает библиотеку через вызов dlopen(),

handle = dlopen("libm.so", RTLD_LAZY);           if (!handle) {                fprintf(stderr, "%s\n", dlerror());                exit(EXIT_FAILURE); } 


Дальше подключаются функции из libdl.so и поиск нужно библиотеки идет с учетом строк в файле /etc/ld.so.conf, /etc/ld.so.preload, переменных LD_LIBRARY_PATH и LD_PRELOAD и в кэше /etc/ld.so.cache, и некоторых других, зависящих от системы и glibc, условий. Напомню, ld.so.cache формируется утилитой ldconfig, с учетом путей указанных в /etc/ld.so.conf. Порядок обхода путей поиска совпадает с последовательностью, указанной в этом файле.

Так вот, сборщики пакетов конечно соблюдают эти правила, но на своих рабочих компьютерах вынуждены использовать разные библиотеки, для разных архитектур, дистрибутивов и просто для тестирования. По этой причине в рабочих дистрибутивах мы получим что-то вроде:

# strace -e open ps  open("/etc/ld.so.preload", O_RDONLY|O_CLOEXEC) = 3 open("/usr/lib64/tls/x86_64/libprocps.so.0", O_RDONLY|O_CLOEXEC) = -1  ENOENT (No such file or directory) open("/usr/lib64/tls/libprocps.so.0", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) open("/usr/lib64/libprocps.so.0", O_RDONLY|O_CLOEXEC) = 0 open("/usr/lib64/tls/x86_64/librt.so.1", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) open("/usr/lib64/tls/librt.so.1", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) open("/usr/lib64/librt.so.1", O_RDONLY|O_CLOEXEC) = 0 

… итд.

Как видите, библиотеки libprocps и librt нашлись только с третьего раза, выделил жирным. (… почему glibc не использует комбинацию stat+open, не ясно, но не об этом …).

Уже наверно догадались, что для поиска подобных ошибок, мы будем использовать утилиту strace. Не вдаваясь в подробности, strace — отслеживает системные вызовы вызываемой исследуемой программой. У неё есть два, нас интересующих параметра -e c аргументом, имени интересующего нас системного вызова, и флаг -f — отслеживающий системные вызовы у дочерних процессов.
(-ff и -o, но оних позже).

1. Библиотеки

Сама оптимизация, если это можно так назвать, заключается в подсовывании нужных каталогов нашим бинарникам. Плодить копии одних и тех же библиотек мы не будем, а просто создадим ссылки с нужными на каталоги или на файлы с нужными на библиотеками. Из примера выше, мы видим, что приложение в первую очередь ищет в каталоге /usr/lib64/tls/x86_64, которого у нас нет. Нужно его создать, но только на один уровень меньше, т.е /usr/lib64/tls, а в нем ссылку x86_64 на каталог /usr/lib64:

# mkdir /usr/lib64/tls/  # ln -s /usr/lib64/tls/x86_64 /usr/lib64 

посмотрим результат

# ls -l /usr/lib64/tls/x86_64   /usr/lib64/tls/x86_64 -> /usr/lib64 

Запустим ещё раз проверку

$ strace -e open ps  open("/etc/ld.so.preload", O_RDONLY|O_CLOEXEC) = 3 open("/usr/lib64/libprocps.so.0", O_RDONLY|O_CLOEXEC) = 0 open("/usr/lib64/librt.so.1", O_RDONLY|O_CLOEXEC) = 0 

Прекрасно, минус четыре системных вызова и это только с двух библиотек и одного бинарника.

2. Локали.

Аналогичная ситуация с файлами локализаций и локальными настройками LC_CTYPE, LC_MESSAGES и остальные. Эта бяда тянется ещё с лохматых 90-х, по крайней мере в SuSE. Данные файлы и настройки должны лежать в каталоге $LOCALES/$USER_LOCALE (имена условные).
LOCALES — это общая свалка для всех поддерживаемых локалей. В реальности это переменная I18NPATH (у кого в дистрибутиве она установлена? 🙂 (используемые каталоги локалей можно посмотреть командой localedef —help )
USER_LOCALE — это пользовательская настройка, которую можно узнать командой locale.

$ locale LANG=ru_RU.UTF-8 LC_CTYPE=ru_RU.UTF-8 LC_MESSAGES=ru_RU.UTF-8 … 

Приложение, если поддерживает локализацию вызывает нужные функции из glibc, которые ищут эти файлы в забавном, но логичном, инкрементальном порядке.

$ strace -e open ps  open("/usr/lib/locale/ru_RU.UTF-8/LC_MESSAGES", O_RDONLY|O_CLOEXEC) = -1 open("/usr/lib/locale/ru_RU/LC_MESSAGES", O_RDONLY|O_CLOEXEC) =  -1  open("/usr/lib/locale/ru/LC_MESSAGES", O_RDONLY|O_CLOEXEC) = 3 

То есть, сначала в /usr/lib/locale/ru_RU.UTF-8, потом в /usr/lib/locale/ru_RU, затем в /usr/lib/locale/ru
Исправляем по предыдущей схеме — создаём ссылку /usr/lib/locale/ru_RU.UTF-8 на каталог /usr/lib/locale/ru

# ln -s /usr/lib/locale/ru_RU.UTF-8 /usr/lib/locale/ru; 

и скажем glibc, что мы используем ru_RU.UTF-8, а не ru.
(кто не знает как исправить в случае глюков, лучше не делайте)

# localedef -f UTF-8 -i ru_RU ru_RU.UTF-8; 

и проверим:

$ strace -e open ps  open("/usr/lib/locale/ru_RU.UTF-8/LC_MEASUREMENT", O_RDONLY|O_CLOEXEC) = 3 open("/usr/lib/locale/ru_RU.UTF-8/LC_TELEPHONE", O_RDONLY|O_CLOEXEC) = 3 open("/usr/lib/locale/ru_RU.UTF-8/LC_ADDRESS", O_RDONLY|O_CLOEXEC) = 3 open("/usr/lib/locale/ru_RU.UTF-8/LC_NAME", O_RDONLY|O_CLOEXEC) = 3 open("/usr/lib/locale/ru_RU.UTF-8/LC_PAPER", O_RDONLY|O_CLOEXEC) = 3 open("/usr/lib/locale/ru_RU.UTF-8/LC_MESSAGES", O_RDONLY|O_CLOEXEC) = 3 … 

замечательно, минус по два open на каждый параметр!

3. Пользовательские и графические приложения.

Начнём с классики — xterm. Работает в полном соответствии с POSIX, LSB, X/Open, IEEE, ISO, ГОСТ, DIN 🙂 Лезет в shared library, в локали, локали Xorg и далее специфичные для Xorg/Xfree86 конфиги. Опять же без вникания в суть, Xorg дублирует конфиги, точнее их имена: Но в отличии от glibc, Xorg иногда использует функцию stat()

$ strace -e open,stat xterm  # (через запятую - список нужны сискалов)  stat("/home/pavel/XTerm", {st_mode=S_IFREG|0600, st_size=333, ...}) = 0  open("/home/pavel/XTerm", O_RDONLY)     = 4 open("/usr/share/X11/app-defaults/XTerm", O_RDONLY) = 4 

Первый файл это мои настройки, второй — общесистемный.

Тут медаль о двух сторонах, независимо от наличия или отсутствия моего файла, мы получим два системных вызова, но один из них менее ресурсоёмкий — stat(). Получается, если у Вас нет каких-то специфичных настроек для xterm, то лучше выкинуть такие файлы из домашней папки. Это могу быть .Xdefaults-$(hostname), .Xdefaults, .terminfo (файлы со словами auth, лучше не трогать :))
Похожая, но отличная ситуация с курсорами мыши. Если приложение, а это почти все графические, используют библиотеку libXcursor, значит оно будет искать курсоры и иконки сначала в $HOME/.icons/ и затем в /usr/share/icons

$ strace -e open,stat xterm   open("/home/pavel/.icons/DMZ/cursors/top_left_arrow", O_RDONLY) = -1 ENOENT (No such file or directory) open("/home/pavel/.icons/DMZ/index.theme", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/share/icons/DMZ/cursors/top_left_arrow", O_RDONLY) = 5 open("/home/pavel/.icons/DMZ/cursors/sb_v_double_arrow", O_RDONLY) = -1 ENOENT (No such file or directory) open("/home/pavel/.icons/DMZ/index.theme", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/share/icons/DMZ/cursors/sb_v_double_arrow", O_RDONLY) = 5 

Просто делаем ссылку с /usr/share/icons на $HOME/.icons и проверяем:

$ ln -s /usr/share/icons $HOME/.icons $ strace -e open,stat xterm  open("/home/pavel/.icons/DMZ/cursors/top_left_arrow", O_RDONLY) = 5 open("/home/pavel/.icons/DMZ/cursors/sb_v_double_arrow", O_RDONLY) = 5 

Великолепно, минус 4 опена()!

Далее добавим к strace флаг -f, для отслеживания дочерних процессов.

$ strace -f -e open,stat xterm 

Вывод очень громоздкий, оставляю вам для изучения в качестве домашнего задания. Скажу лишь, что для многонитиевых приложений лучше делать вывод в файл или даже в отдельные файлы для каждой «дочки».

$ mkdir /tmp/Xterm (Отдельный каталог для чистоты феншуя)   $ cd /tmp/Xterm $ strace -o Xterm.trace -ff xterm  $ ls   Xterm.trace.6001  Xterm.trace.6009  Xterm.trace.6017 Xterm.trace.6025  Xterm.trace.6034   Xterm.trace.6042  Xterm.trace.6050  Xterm.trace.6002  Xterm.trace.6010  Xterm.trace.6018   Xterm.trace.6026  Xterm.trace.6035  Xterm.trace.6043  Xterm.trace.6051  Xterm.trace.6003   Xterm.trace.6011  Xterm.trace.6019  Xterm.trace.6027  Xterm.trace.6036  Xterm.trace.6044   Xterm.trace.6052  Xterm.trace.6004  Xterm.trace.6012  Xterm.trace.6020  Xterm.trace.6028   ... 

изучайте 🙂

все сразу

$ egrep "open|stat" ./Xterm.trace.* | grep --color " = -" 

или по одному

$ egrep "open|stat" ./Xterm.trace.6022 | grep --color " = -" 

Даже сейчас, нашел, что кто-то упорно ищет libreadline.so.6

./Xterm.trace.28465:open("/usr/lib64/tls/x86_64/libreadline.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) ./Xterm.trace.28465:open("/usr/lib64/tls/libreadline.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) ./Xterm.trace.28465:open("/usr/lib64/x86_64/libreadline.so.6", O_RDONLY|O_CLOEXEC) = -1 EACCES (Permission denied) ./Xterm.trace.28465:open("/usr/lib64/libreadline.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) 

В данном случае косяк в том, что программеры указали явно (специально или через парсер) версию библиотеки. Вместо libreadline.so, захаркодили libreadline.so.6. Исправляем.

$ cd /usr/lib64/tls/x86_64/ # ln -s libreadline.so libreadline.so.6  $ cd /tmp/Xterm && rm ./*; $ strace -o Xterm.trace -ff xterm $ egrep "open|stat" ./Xterm.trace.* | grep --color "libreadline"  ./Xterm.trace.21278:open("/usr/lib64/tls/x86_64/libreadline.so.6", O_RDONLY|O_CLOEXEC) = 3 

Ура!

Заключение

Для оптимизации KDE, GNOME, Xfce приложений не обязательно настраивать сами менеджеры gdm/kdm/xfwm4, достаточно любого приложения из набора к вашему десктопу, схема работы практически идентичная.

Займитесь в первую очередь всеми бинарниками запускаемыми при загрузке.

Таким способом время загрузки компьютера можно легко уменьшить до 10-15 сек. Время загрузки — это от загрузчика до открытия страницы в фейсбуке, а не появление обоины на экране, как думает Microsoft и Поттеринг.

Потом можно взяться за большие программы Google Chrome, Firefox, Gimp, LibreOffice, etc.

Возможные косяки

Как и в любом деле — важно знать меру, создание ссылок на каталоги может привести к «зацикленным каталогам» и вызов, скажем find ./, может устроить локальный DoS системе. (вроде в ядре уже исправили, но тем не менее ). Года два назад, на SuSE были проблемы при обновлении glibc. Дополнительно рекомендую почитать про TLS (Thread Local Storage)

ссылка на оригинал статьи http://habrahabr.ru/post/192264/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *