Ускорение парсинга INI-файлов для Zend Framework веб-приложений (в 4-5 раз)

// Март 28th, 2012 // Yaf, Zend Framework

В этой статье я расскажу, как можно увеличить скорость обработки запросов в вашем проекте на ZF с помощью уменьшения времени, необходимого на парсинг INI-файлов конфигурации.

Не секрет, что при создании большого приложения на Zend Framework большинство разработчиков используют для хранения настроек конфигурационные файлы. Некоторые любят XML-формат, другие модный YAML, я же возможно из-за своего админского прошлого предпочитаю INI-формат.

В Zend_Framework для парсинга INI файлов используется компонент Zend_Config_Ini. Но парсинг сам по себе вещь довольно накладная.

Скрин от фреймфорка LiteMVC иллюстрирующий обращения к парсеру при каждом запросе

Как его ускорить? Можно использовать кэшер, и кэшировать результат парсинга. Но иногда такой возможности нет, т.к. в самом конфиге хранятся настройки системы кэширования. Какой есть ещё вариант?

Yaf Framework

Этот китайский проект очень заинтересовал меня в последнее время. Создатели Yaf  (Yet Another Framework) поставили своей целью написать ZF на C. Устанавливается он просто:

pecl install yaf

Потом вам надо будет включить расширение, добавив строчку:

extension=yaf.so

в ваш php.ini (или отдельный yaf.ini файл). И перезапустить интерпретатор php(fpm или mod_php вместе с apache). Этот фреймворк содержит следующие классы:

  • Yaf_Application
  • Yaf_Dispatcher
  • Yaf_Bootstrap_Abstract
  • Yaf_Plugin_Abstract
  • Yaf_Router
  • Yaf_Route_Static
  • Yaf_Route_Simple
  • Yaf_Route_Supervar
  • Yaf_Route_Rewrite
  • Yaf_Route_Regex
  • Yaf_Route_Map
  • Yaf_Config_Ini
  • Yaf_Config_Simple
  • Yaf_Controller_Abstract
  • Yaf_Action_Abstract
  • Yaf_Request_Http
  • Yaf_Request_Simple
  • Yaf_Exception
  • Yaf_Exception
  • Yaf_View_Simple
  • Yaf_Response_Abstract
  • Yaf_Response_Simple
  • Yaf_Response_Http

Как вы можете видеть, классы аналогичны ZF классам. В этой статье я рассмотрю использование Yaf_Config и его бенчмарки по сравнению с Zend_Config.

Zend_Config_Ini vs Yaf_Config_Ini

Для проведения тестовых испытаний возмём вот такой конфиг:
/cfg/application.config.ini

; Общие настройки для всех типов окружений

[bootstrap]
module_paths[] = APPLICATION_PATH'/module'
module_paths[] = ROOT_PATH'/vendor'
modules[] = 'Application'

module_listener_options.config_cache_enabled = false
module_listener_options.cache_dir = APPLICATION_PATH . '/data/cache'
module_listener_options.application_environment = APPLICATION_ENV

; Настройки для авторизации из социальных сетей
social.facebook.app_id = "456247645747"
social.facebook.api_key = "trfyrthfghfghfgh"
social.facebook.app_secret = "fghfghsytyty"
social.vkontakte.app_id = "4564646"
social.vkontakte.secret_key = "hfghdhgfd"
social.main_url = "site.ru"
social.twitter.app_id = "456456456"
social.twitter.api_key = "fghfghfghrtyy"
social.twitter.app_secret = "retertergfdhgfhfhgfhfg"

resources.frontController.throwExceptions = false; Для перехватчика исключений и ErrorController'а = false
; Настройки проекта в окружении development
[development : bootstrap]
server.data_root = '/home/www/site.ru/data'

; Настройки подключения к базам данных для Doctrine
resources.doctrine.connections.default.dsn = "mysqli://user:pass@localhost/db"
resources.doctrine.connections.default.profiler = true
resources.cachemanager.backend.memcached.servers.default.host = 127.0.0.1

; Настройка php
phpSettings.display_startup_errors = 1
phpSettings.display_errors = 1
phpSettings.error_reporting = E_ALL & ~E_NOTICE
phpSettings.html_errors = 1

; Настройки проекта в окружении stage
[production: bootstrap]
server.data_root = '/home/www/site.ru/data'

; Настройки подключения к базам данных для Doctrine
resources.doctrine.connections.default.dsn = "mysqli://user:pass@localhost/db"
resources.doctrine.connections.default.profiler = false
resources.cachemanager.backend.memcached.servers.default.host = 127.0.0.1

; Настройка php
phpSettings.display_startup_errors = 0
phpSettings.display_errors = 0
phpSettings.error_reporting = 0

В ./lib/Zend сохраним копию ZendFramework, Yaf у нас уже установлен. Поэтому напишем простенький тест:

zf.php

<?php
define('ROOT_PATH', '/home/andrey/sandbox/yaf.new/html/');
error_reporting(E_ALL);
ini_set("display_errors", 1);
ini_set("html_errors", 1);

set_include_path(get_include_path() .
PATH_SEPARATOR . ROOT_PATH . DIRECTORY_SEPARATOR . "lib"
);
require_once ('Zend/Config/Ini.php');
require_once ('Zend/Registry.php');

// Загружаем ZF
$localConfigPath = '/cfg/local.config.ini';
$config = new Zend_Config_Ini(ROOT_PATH . '/cfg/application.config.ini');
$appConfig = $config->toArray();
Zend_Registry::set('appconfig', $appConfig);
print 'Ok';

yaf.php

<?php
define('ROOT_PATH', '/home/andrey/sandbox/yaf.ru/html/');
error_reporting(E_ALL);
ini_set("display_errors", 1);
ini_set("html_errors", 1);

$config = new Yaf_Config_Ini(ROOT_PATH . '/cfg/application.config.ini');
$appConfig = $config->toArray();
Yaf_Registry::set('appConfig', 'appConfig');
print('ok');

Результаты тестирования

Ну что-же, теперь время сухих бенчмарков.

zf.php

andrey@z11:~/sandbox/yaf.new/html$ clear; ab -c 10 -n 1000 http://yaf.new/zf.php

Server Software: nginx/0.7.67

Server Hostname: yaf.ru
Server Port: 80

Document Path: /engine.php
Document Length: 2 bytes

Concurrency Level: 10
Time taken for tests: 1.187 seconds
Complete requests: 1000
Failed requests: 0
Write errors: 0
Total transferred: 160000 bytes
HTML transferred: 2000 bytes
Requests per second: 842.64 [#/sec] (mean)
Time per request: 11.867 [ms] (mean)
Time per request: 1.187 [ms] (mean, across all concurrent requests)
Transfer rate: 131.66 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.1 0 0
Processing: 4 12 7.3 9 50
Waiting: 4 12 7.3 9 50
Total: 4 12 7.3 9 50

Percentage of the requests served within a certain time (ms)
50% 9
66% 12
75% 14
80% 16
90% 21
95% 27
98% 34
99% 40
100% 50 (longest request)

yaf.php

andrey@z11:~/sandbox/yaf.new/html$ clear; ab -c 10 -n 1000 http://yaf.new/yaf.php

Server Software: nginx/0.7.67

Server Hostname: yaf.new
Server Port: 80

Document Path: /index.php
Document Length: 2 bytes

Concurrency Level: 10
Time taken for tests: 0.245 seconds
Complete requests: 1000
Failed requests: 0
Write errors: 0
Total transferred: 160000 bytes
HTML transferred: 2000 bytes
Requests per second: 4076.21 [#/sec] (mean)
Time per request: 2.453 [ms] (mean)
Time per request: 0.245 [ms] (mean, across all concurrent requests)
Transfer rate: 636.91 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.1 0 0
Processing: 1 2 1.1 2 11
Waiting: 1 2 1.1 2 11
Total: 1 2 1.1 2 11

Percentage of the requests served within a certain time (ms)
50% 2
66% 2
75% 3
80% 3
90% 4
95% 5
98% 6
99% 7
100% 11 (longest request)

Выводы

Скорость парсинга ZF 1.10.3: 842.64 запросов в секунду.
Скорость парсинга Yaf: 4076.21 запросов в секунду.
Ускорение: почти в 5 раз (4.83)!

К сожалению в Yaf не так много компонентов как в ZF, с поддержкой и сообществом там совсем туго, однако некоторые простые и часто-используемые компоненты (например Zend_Config_Ini и Zend_View) вполне можно заменить на их аналоги из Yaf, или сделать обертку.

 

Ссылки

http://litemvc.org/2010/06/04/optimising-ini-parsing/

Share

Спасибо!


Если вам помогла статья, или вы хотите поддержать мои исследования и блог - вот лучший способ сделать это:


4 Responses to “Ускорение парсинга INI-файлов для Zend Framework веб-приложений (в 4-5 раз)”

  1. Andrew:

    Я бы не сказал, что Yaf — это Zend на C, потому что есть различия в API и достаточно серьёзные. Лучше относится к Yaf как к отдельному фрейморвку и разрабатывать проект с учётом его особенностей.

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

    • google.com Андрей Токарчук:

      Ну по крайней мере, ближайший аналог Yaf это как раз таки ZF. Естественно в YAF нет ооочень многого, но думаю что за скелет его вполне можно взять, а потом уже на php добавлять «мяса». По поводу разбора конфигов — да, это скорее синтетический тест. Но думаю в некоторых задачах такое решение может пригодиться.

  2. Denis:

    Зачем вообще хранить конфиги в ini, если можно их хранить в виде подключаемого php файла, содержащего в себе define и $_GLOBALS[‘config’], при чём с APC их данные уже закэшированны в памяти.

Комментировать