Уже написаны тонны статей на данную тему, но тем не менее писатели дистрибутивов и майнтенеры пакетов часто пропускают мимо такие вещи как совпадение путей и каталогов поиска используемых библиотек и ресурсов: картинок, звуков, иконок, шрифтов, и пр.
На примере собранной статистики с утилиты bootchart или подобных можно увидеть количество запускаемых утилит и сервисов при загрузке системы: www.bootchart.org/samples.html
Каждый запускаемы бинарник, обычно динамически, слинкован минимум с glibc, и другими ему нужными библиотеками. По правильной схеме, приложение открывает библиотеку через вызов dlopen(),
handle = dlopen("libm.so", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "%sn", 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)
Автор: pavlinux