Что-то давненько мы здесь не занимались глупостями.
Сегодня мы будем проводить тесты со всеми возможными нарушениями их чистоты и нагло манипулировать статистикой.
Вопрос: как правильно подключать в своём проекте классы, чтобы при этом сэкономить время на их подключение.
Варианты:
- Старый добрый
autoload()
, как у всех нормальных людей. - Взять и собрать все классы в один файл, как многие учат.
- Подключать нужные классы явно через
require_once()
. - И новомодная штучка: Phar-архив.
- Phar-архив ещё можно делать не простой, а со сжатием (GZ или BZ2).
- Ну и phar’ом также два варианта: autoload() или прямое подключение вложенных файлов.
- И поверх этого можно акселератором каким-нибудь пошаманить.
Инструменты
Инструменты для тестов здесь: на гитхабе.
Запуск ./create.php
создаёт каталог test
, а в нём следующие вещи.
Каталог classes
Структура наших тестируемых классов. Всё по стандарту PSR-0.
Создаётся 5 пространств имён со случайными именами. В каждом создаётся 5 случайных классов со случайным набором методов.
И в каждом из глобальный пространств создаётся ещё 5 вложенных и в них ещё по 5 классов.
Итого 150 классов. Стандартный такой средненький фреймворк.
Файл full.php
Весь «фреймворк» одним файлом.
Файл получился размером 85 K. А общий вес каталога (за счёт большого количества маленьких файлов) 725 К (файловая система ext4).
Несколько спичек уже сэкономили.
Файл exec.php
Это исполняемый скрипт, в котором вызываются нужные классы.
Чтобы добиться какого-то подобия с обычными сценариями: в одном сценарии задействованы не все классы фреймворка, а только 50 (треть). Но к каждому классу может быть несколько обращений.
Работа с каждым классом проста: вызывается статический метод, который выполняет простейшее арифметическое действие. Таким образом время потраченное на работу с классами не должно намного превзойти время потраченное на их подключение (которое мы и тестируем).
Phar-архивы
Из каталога classes
создаётся три phar-архива: phar-none.phar
, phar-gz.phar
, phar-bz.phar
(без компрессии, gz, bz).
Размер phar-node.phar
: 99 К. Больше чем full.php
за счёт дополнительной информации.
Сжатые файлы: 51 К (gz) и 61 К (bz). Нормальные то архиваторы сжимают и получше.
(Нужно расширение Phar и должна быть сброшена настройка phar.readonly
в php.ini)
Файлы req.php
и phar-req.php
В req.php подключаются все используемые в exec.php файлы (50 штук).
В phar-req.php тоже самое, только подключаются они не из каталога, а из phar-архива.
Тесты
После создания тестового каталога запускаем из консоли тесты: ./run.php [test]
.
Аргумент [test]
может быть следующим:
autoload: подключение файлов из каталога с помощью автолоада.
Автолоад простой: заменяет NS1\NS2\Class
на DIR/NS1/NS2/Class.php
, проверяет наличие файла и подключает его.
full: подключение одного большого файла.
req: подключение нужных файлов через require_once
phar-auto: подключение из phar через автолоад.
phar-req: подключение нужных файлов из phar через require_once.
phar-gz и phar-bz: подключение через автолоад из сжатых архивов.
Так как в цикле прогонять такие тесты бессмысленно, да и вообще чистотой тестов тут уже и не пахнет, просто вызываем несколько раз из консоли (результаты всё равно всегда оказываются похожими).
Результаты
У меня получилось такое (P. Dual Core 2,8Гц, ubuntu, php 5.5.1):
Тест Время (мс)|Прирост памяти (Kb) autoload 6,7 275 full 8,5 776 req 6,1 275 phar-auto 7,6 315 phar-req 7,4 315 phar-gz 10 323 phar-bz 11,7 323
Псевдоаналитика
Автолоад vs сборка в один файл
Мало того, что сборка в один файл выела в 3 раза больше памяти (подключаем все классы, когда нужна только треть), так она ещё и по времени проиграла автолоаду с которым призвана бороться.
Дальнейшие тесты показали, что в времени сборка приближается к автолоаду при подключении около 50% от общее числа классов, а потом начинает обходить его.
Автолоад vs requre
require ожидаемо обошло autoload.
Логично, ведь при автолоаде происходит много различных телодвижений, которые заканчиваются всё тем же require.
Однако, разница всего лишь 10%.
И это при том, что в тесте require_once для каждого класса вызывается только один раз (в реальном проекте, если перед каждым классом вначале подключать нужные ему, один и тот же класс будет запрашиваться многократно и require_once каждый раз будет проверять подключён ли уже файл или нет).
Кроме того, наши тестовые файлы маленькие. На большом файле, его подключение и разбор заняло бы гораздо больше.
Это к теме оптимизации поиска классов: можно убрать из автолоада file_exists(), можно сканировать каталоги и собирать карту классов в массив и использовать его, ещё можно много чего сделать. Но все эти оптимизации будут только в пределах этих 10%. Простое явное require по скорости никак не обойти.
Phar
Phar вполне себе бодренький.
Чуть помедленнее и чуть больше памяти хочет.
Но по потреблению памяти до варианта со сборкой в один файл ему далеко.
Хотя тут тоже один файл.
Сжатие: gzip несколько опередил bz2 и здесь (выше у него получился меньший размер файла).
Однако, при сжатии значительно понижается скорость доступа.
Да и степень сжатия, как видели выше, как-то не очень (более того, при сжатии нормальным архиватором сжатого phar’а результат получается больше, чем сжатие несжатого).
Так что использовать сжатые phar’ы я бы не рекомендовал.
Хотя, возможно, какой-нибудь опкэшер решил бы эту ситуацию.
Zend Opcache
Ну и желательно всё это протестировать со всеми опкэшерами, акселераторами и тому подобным.
Но ставить их все лениво, так что проверил только на opcache.
При запуске тестов из консоли следует не забыть включить в php.ini опцию opcache.enable_cli
.
Включение opcahce привело к двукратному падению потребления памяти.
Насчёт скорости точных чисел нету, так как результаты начали сильно колебаться, а наши методы не позволяют усреднить их по большому количеству тестов.
Однако, колеблются результаты примерно в тех же пропорциях. Максимально: соответствует времени без opcahce, минимально: в два-три раза меньше.
При вызове из консоли могут иногда значительно превосходить по времени вызовы через браузер.
Выводы
1. Нечего париться из-за чепухи. Оптимизация всё равно ничтожная по сравнению с обычным временем выполнения сценария.
2. autoload() — лучшее и самое удобное решение. Тем более все стандарты типа PSR теперь завязаны на него.
3. Производить сборку нескольких файлов в один, построение карт классов и т.п. можно только на продакшене при деплое.
Когда всё более существенное уже оптимизировано.
При разработке же: геморроя много, а толку мало.
4. Сборка нескольких файлов в один даёт результат, только если каждый раз используется хотя бы половина вложенных файлов.
5. phar — хорошо (для поставляемых библиотек, которые не нужно изменять).
6. Сжатый phar — не так хорошо.
7. Opcahce вполне себе рулит.
Отказ от обязательств
Автор не считает результаты тестов особо точными, а выводы предельно правильными.
И не советует использовать их без проверки в своей деятельности и как аргументы в дискуссии.
забавненько и ожидаемо :-)
phpdude, 7.08.2013, 15:33
даже чет не задумывался
IvanSCM, 9.08.2013, 1:36
нормально
artoodetoo, 17.08.2013, 9:31
Интересненько . Phar не на столько плох как я думал
Павел Ланкмилер, 1.04.2015, 16:55