В большинстве фреймворков и более крупных PHP-приложений используется контейнер внедрения зависимостей с целью обеспечения большей поддерживаемой базы кода. Однако это может повлиять на производительность. Поскольку время загрузки имеет большое значение, важно как можно быстрее поддерживать сайты. Сегодня я собираюсь сравнить несколько контейнеров внедрения зависимостей PHP, чтобы увидеть их относительную производительность.
Для тех, кто не знаком с этой концепцией, Dependency Injection Container — это часть программного обеспечения, которая автоматически создает дерево объектов. Например, рассмотрим объект User, для которого требуется экземпляр базы данных.
$user = new User(new Database());
Контейнер внедрения зависимостей может использоваться для автоматического построения дерева объектов без необходимости предоставления параметров вручную:
$user = $container->get('User');
Каждый раз, когда это вызывается, объект пользователя будет создан с объектом базы данных, «внедренным».
Есть несколько хорошо известных (и не очень известных) контейнеров, доступных для PHP:
- PHP-DI , популярный DI-контейнер
- Symfony \ DependencyInjection , контейнер внедрения зависимостей , предоставляемый платформой Symfony
- Zend \ Di Контейнер внедрения зависимостей, предоставляемый Zend Framework
- Orno \ Di , менее известный контейнер с ограниченными возможностями, но разработанный с учетом производительности
- Dice , еще один менее известный контейнер с акцентом на облегчение. Полное раскрытие, я являюсь автором этого контейнера, но я не буду ничем иным, как абсолютно объективным в этом анализе.
- Aura.Di , довольно популярный контейнер с минимальными возможностями
Слово о Pimple: хотя Pimple объявляется как контейнер внедрения зависимостей, получение объекта из контейнера всегда возвращает один и тот же экземпляр, что делает Pimple локатором службы, а не контейнером внедрения зависимостей, и поэтому не может быть протестировано.
Хотя все контейнеры поддерживают различные функции, этот тест будет охватывать основные функции, необходимые для контейнера для инъекций зависимости. То есть создание объектов и внедрение зависимостей там, где они необходимы.
Какие аспекты внедрения зависимости будут измерены?
- Время исполнения
- Использование памяти
- Количество файлов в комплекте. Хотя это мало влияет на производительность, это хороший показатель легкости и портативности библиотеки. Если вам необходимо отправить сотни файлов вместе с вашим проектом из-за выбора DI, это может сильно повлиять на общий вес вашего собственного приложения.
Тестовая среда
Все тесты выполнялись на одной машине с Arch Linux (ядро 3.15), PHP 5.5.13 и последними версиями каждого контейнера по состоянию на 03.07.2014.
Все представленные числа времени выполнения составляют в среднем 10 прогонов после сброса любого, который на 20% медленнее, чем самый быстрый.
Тест 1 — Создание экземпляра объекта
Этот тест использует каждый контейнер для создания простого объекта 10000 раз
Без контейнера внедрения зависимостей это будет записано как:
for ($i = 0; $i < 10000; $i++) {
$a = new A;
}
Тестовый код (на github): Aura , Dice , Orno \ Di , PHP-DI , Symfony \ DependencyInjection , Zend \ Di
Как видите, здесь есть два чистых лагеря. Aura, Dice и Orno работают примерно в десять раз быстрее, чем PHP-DI, Symfony и Zend \ DI.
Как и во время выполнения, есть две отдельные группы с Symfony, которые находятся где-то посередине.
Это говорит о том, насколько легок каждый контейнер, и объясняет различия в использовании памяти. Следует отметить, что многие файлы, используемые Zend \ Di, являются общими файлами фреймворка, поэтому, если вы используете Zend Framework, то использование Zend \ Di не повлечет за собой таких же накладных расходов памяти, поскольку файлы, вероятно, будут повторно использованы в других местах вашего приложения. ,
Точно так же PHP-DI сильно зависит от библиотек Doctrine. Если вы используете Doctrine в своем проекте, то нагрузка на память PHP-DI уменьшается.
Однако приятно видеть, что Symfony \ DependencyInjection, несмотря на то, что является частью стека фреймворка, полностью автономен и работает без каких-либо зависимостей от других проектов Symfony.
Aura, Dice и Orno не имеют никаких внешних зависимостей, и это помогает уменьшить их количество файлов.
Тест 2 — Игнорирование автозагрузки
Поскольку загрузка файлов может влиять на производительность, а Zend и PHP-DI загружали значительное количество файлов, один и тот же тест проводился без учета времени автозагрузчика, сначала создав один экземпляр класса, убедившись, что все требуемые классы были загружены автоматически перед измерением времени.
Это также могло вызвать любое внутреннее кэширование, выполняемое контейнером, но для каждого контейнера применялась одинаковая обработка, чтобы сохранить его справедливым
Эквивалентный код PHP:
for ($i = 0; $i < 10000; $i++) {
$a = new A;
}
Тестовый код (на github): Aura , Dice , Orno \ Di , PHP-DI , Symfony \ DependencyInjection , Zend \ Di
Как и ожидалось, использование памяти остается неизменным, а производительность немного лучше, поскольку время автозагрузчика не измеряется. Тем не менее, это показывает, что PHP-DI, даже загрузка 42 файлов, оказывает незначительное влияние на общее время выполнения, и относительная производительность остается неизменной, загрузка десятков файлов не является причиной того, что PHP-DI и Zend \ DI имеют относительно низкую производительность ,
Даже после игнорирования накладных расходов на загрузку файлов, здесь все еще есть два разных шага. Aura, Dice и Orno очень похожи по производительности и использованию памяти, тогда как PHP-DI, Zend и Symfony только конкурируют друг с другом.
Все дальнейшие тесты будут игнорировать время автозагрузки, чтобы гарантировать, что измеряется производительность контейнера.
Тест 3 — Глубокий объектный граф
Этот тест выполняется тем, что контейнеры создают этот набор объектов 10000 раз:
for ($i = 0; $i < 10000; $i++) {
$j = new J(new I(new H(new G(new F(new E(new D(new C(new B(new A())))))))));
}
Тестовый код (на github): Aura , Dice , Orno \ Di , PHP-DI , Symfony \ DependencyInjection , Zend \ Di
Примечание. Как вы можете видеть из тестового кода, Symfony, PHP-DI и Aura требуют значительно больше кода конфигурации, чем другие контейнеры для выполнения этого теста. Время конфигурации не было включено в тест.
Опять же, разница между топ-3 очень мала: Dice на 20% быстрее, чем Aura, и на 70% быстрее, чем Orno. Все три значительно быстрее, чем Zend, PHP-DI и Symfony. Разница между тремя верхними контейнерами настолько незначительна в реальном выражении, что вы никогда не заметите разницу в скорости вне искусственного теста, подобного этому.
Zend, PHP-DI и, в меньшей степени, Symfony работают медленно. Зенду требуется 37 секунд, чтобы выполнить задачу, которую выполняет Кость менее чем за 1 секунду; конечно нетривиальная разница. И снова Symfony занимает лидирующие позиции среди известных контейнеров.
Количество памяти и файлов соответствует тому, что мы видели в других тестах.
Тест 4 — Извлечение Сервиса из контейнера
Контейнеры DI также должны хранить и извлекать сервисы, которые будут повторно использоваться в приложении. Этот тест повторно выбирает один экземпляр из контейнера.
Эквивалент чистого PHP:
$a = new A;
for ($i = 0; $i < 10000; $i++) {
$a1 = $a;
}
Тестовый код (на github): Aura , Dice , Orno \ Di , PHP-DI , Symfony \ DependencyInjection , Zend \ Di
Это неожиданно на основании предыдущих результатов. Все контейнеры, за исключением Zend и Symfony, примерно равны всего лишь 0,01, разделяя лучшие 4 результата. Symfony не сильно отстает, но Zend более чем в десять раз медленнее остальных.
Использование памяти и количество файлов становятся предсказуемыми с тем же разделением между контейнерами, которое мы видели во время выполнения.
Тест 5 — Внедрить услугу
Последний тест — увидеть, как быстро можно построить объект и внедрить сервис. Это принимает формат:
$a = new A;
for ($i = 0; $i < 10000; $i++) {
$b = new B($a);
}
Тестовый код (на github): Aura , Dice , Orno \ Di , PHP-DI , Symfony \ DependencyInjection , Zend \ Di
Интересно, что в этом тесте Аура заняла небольшое место. Однако это не совсем похожий тест, поскольку Symfony и Aura требуют нескольких строк явной конфигурации, в то время как другие контейнеры автоматически разрешают зависимость. Время, затраченное на настройку контейнера, не было частью теста.
Удивительно, но PHP-DI является самым медленным в этой задаче: Zend впервые занял свое место впереди PHP-DI и Symfony.
Вывод
Только по производительности Dice, Aura и Orno являются сильными конкурентами, Dice — самый быстрый в целом, а Aura — самый быстрый в финальном тесте. Разница между двумя различными группами очевидна, но интересно сравнить характеристики каждого контейнера. Количество функций и производительность не совсем соответствуют, как вы ожидаете. И PHP-DI, и Dice содержат уникальные функции, но PHP-DI сильно ударил по производительности. Аура, хотя и быстрая, требует большого количества ручных настроек и, как и следовало ожидать, имеет очень минимальные возможности, тогда как Dice и Orno имеют очень похожую производительность, но требуют гораздо меньше кода для настройки.
Symfony во всех тестах находится на среднем уровне, хотя его настройка, как и в случае с Aura, является гораздо более сложной задачей, поскольку ни один из них не поддерживает подсказки типа. Если вы ищете контейнер из известного проекта, тогда Symfony должен быть предпочтительным контейнером, если важна производительность.
Тем не менее, если вам нужна чистая производительность, Dice и Aura — явные победители, а Orno очень близко от них. Тем не менее, стоит взглянуть на синтаксис конфигурации и особенности каждого из них, чтобы увидеть, с кем вы предпочитаете работать, поскольку разница в производительности между Dice, Aura и Orno незначительна для любого реального приложения.
Весь код для тестов доступен на github . Обратите внимание: репозиторий github содержит копии протестированных библиотек, а не использует composer для включения их в проект, чтобы убедиться, что вы можете запустить код с точными версиями, которые я тестировал, и получить те же результаты.