Подключаем классы разным макаром (PHP)

Что-то давненько мы здесь не занимались глупостями.

Сегодня мы будем проводить тесты со всеми возможными нарушениями их чистоты и нагло манипулировать статистикой.

Вопрос: как правильно подключать в своём проекте классы, чтобы при этом сэкономить время на их подключение.

Варианты:

  • Старый добрый 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 вполне себе рулит.

Отказ от обязательств

Автор не считает результаты тестов особо точными, а выводы предельно правильными.
И не советует использовать их без проверки в своей деятельности и как аргументы в дискуссии.

4 комментария »

  • забавненько и ожидаемо :-)

    phpdude, 7.08.2013, 15:33

  • даже чет не задумывался

    IvanSCM, 9.08.2013, 1:36

  • нормально

    artoodetoo, 17.08.2013, 9:31

  • Интересненько . Phar не на столько плох как я думал

    Павел Ланкмилер, 1.04.2015, 16:55

Leave a comment