Наверное, все, кто сталкивался с разработкой более или менее серьезных приложений, знают, что выбор формата хранения настроек скрипта или приложения — достаточно ответственное дело. Конфиги должны быть легко читаемыми, легко модифицируемыми, легко переносимыми, и так далее — список можно продолжать и продолжать.Так как серверные PHP-скрипты выполняются, бывает, много раз в секунду, скорость загрузки конфигов — достаточно важный параметр. Хотя ему, порой, уделяется не очень много внимания. Давайте сравним различные варианты хранения настроек для PHP-скриптов с точки зрения скорости их работы. Ну и коснемся вкратце их удобства.
Итак, подопытные:
- INI-файлы
- PHP-скрипты
- XML-файлы
- Текстовые файлы
- Файлы с сериализованными данными
- Вне конкурса — PHP-скрипты с define'ами
- JSON-файлы NEW!
Условия:
- Как можно быстрее загрузить настройки из файла
- Вернуть массив настроек в виде «ключ» => «значение»
- Конфигурационный файл содержит 10, 100 или 1000 конфигурационных параметров, представляющих собой короткие строки
- Конфигурация читается 1000 раз подряд, замеряется время работы в секундах
Конфигурацию оборудования не привожу. Понятно, что скорость скриптов зависит от сервера, но в данном случае сравниваются скрипты, а не серверы.
Правда, необходимо сделать небольшое уточнение по поводу программного обеспечения сервера. Использовался реальный веб-сервер, в момент низкой загрузки. Соответственно, конфигурация сервера «боевая»: Linux Debian Lenny, много памяти и RAID1-массив жестких дисков. PHP серии 5.2.x (не самый последний, врочем) с eAccelerator'ом. На время тестов отключался Zend Optimizer, чтобы тесты были боле�� «чистыми», что минимально повлияло на результаты. Тесты без eAccelerator тоже проводились, но, как ни странно, сильно на распределение сил это не повлияло. Причина, на мой взгляд, кроется в том, что eAccelerator настроен на дисковое кэширование опкодов PHP и на сравнение времени модификации файлов, что «съедает» определенное количество времени — хотя и приносит определенные бонусы.
INI-файлы
Результаты: 0.015, 0.086, 0.784
Пример:
x1 = 1
x2 = 2
x3 = 3
Скрипт:
function config($file) {
return parse_ini_file($file);
}
Конфигурационный файл с классическим, всем знакомым синтаксисом. Достаточно быстрый и удобный способ.
PHP-скрипты
Результаты: 0.029, 0.111, 0.902
Пример:
<?
return array (
'x1' => '1',
'x2' => '2',
'x3' => '3',
);
?>
Скрипт:
function config($file) {
return include($file);
}
Сначала маленькая оговорка. Во многих проектах конфигурационный файл не делает return, а просто определяет элементы глобального массива настроек. Это, с одной стороны, не совсем подходило под условия теста, а с другой стороны не совсем идеологически корректно в рамках борьбы против глобальных переменных. Поэтому для сравнения был использован предложенный вариант.
Обратите внимание на то, что этот вариант стабильно проигрывает INI-файлам, хоть и не очень значительно. Что ж, это компенсируется тем, что в настройках можно использовать PHP-выражения, что позволяет сделать конфиги максимально гибкими.
XML-файлы
Результаты: 0.062, 0.385, 3.911
Пример:
<root>
<x1>1</x1>
<x2>2</x2>
<x3>3</x3>
</root>
Скрипт:
function config($file) {
$r = array();
$dom = new DOMDocument;
$dom->load($file);
foreach ($dom->firstChild->childNodes as $node) {
if ($node->nodeType == XML_ELEMENT_NODE) {
$r[$node->nodeName] = $node->firstChild->nodeValue;
}
}
return $r;
}
Недостаток очевидный: очень маленькая скорость работы, в несколько раз медленнее, чем другие варианты. Чтобы проверить, не слишком ли медленная PHP-часть этого кода, я попробовал сделать return сразу после загрузки XML-документа (то есть, фактически, конфигурационные параметры не возвращались). Это ускорило процесс всего приблизительно в два раза. Что подтвердило общий вывод.
Результаты: NEW! 0.047, 0.276, 2.791
Скрипт: NEW!
function config($file) {
$r = array();
foreach(simplexml_load_file($file) as $k => $v) {
$r[$key] = strval($v);
}
return $r;
}
С помощью SimpleXML получается, коне��но, быстрее. Но не настолько, чтобы претендовать на лидерство.
Текстовые файлы
Результаты: 0.034, 0.250, 2.369
Пример:
x1 1
x2 2
x3 3
Скрипт:
function config($file) {
$r = array();
if ($F = fopen($file, "r")) {
while (($line = fgets($F)) !== false) {
list($k, $v) = explode("\t", $line, 2);
$r[trim($k)] = trim($v);
}
fclose($F);
}
return $r;
}
Не знаю, право, зачем этот способ существует. Возможно, его придумали до того, как изобрели parse_ini_file, или до того, как узнали об этой функции. Не сильно быстрый, не сильно удобный способ.
Результат: NEW! 0.036, 0.250, 2.213
Скрипт: NEW!
function config($file) {
$r = array();
foreach (explode("\n", file_get_contents($file)) as $line) {
list($k, $v) = explode("\t", $line, 2);
$r[trim($k)] = trim($v);
}
return $r;
}
Такой вариант реализации несколько медленнее для небольших файлов и быстрее для больших файлов. Но, в общем, не влияет на расстановку сил.
Файлы с сериализованными данными
Результаты: 0.011, 0.041, 0.309
Пример:
a:3:{s:2:"x1";s:1:"1";s:2:"x2";s:1:"2";s:2:"x3";s:1:"3";}
Скрипт:
function config($file) {
return unserialize(file_get_contents($file));
}
Наименее удобочитаемый конфигурационный файл — но при этом самый быстрый результат.
PHP-скрипты с define'ами
Результаты: 0.045, 0.252, 2.404
Пример:
<?
define("x1", "1");
define("x2", "2");
define("x3", "3");
?>
Пример скрипта не привожу, потому что, как уже говорилось выше, результат в нужном виде вернуть достаточно сложно. Кроме этого, полученные результаты носят условный характер, поскольку второй раз define не переопределяет значение константы.
JSON-файлы NEW!
Результаты: 0.015, 0.057, 0.495
Пример:
{"x1":"1","x2":"2","x3":"3"}
Скрипт:
function config($file) {
return json_decode(file_get_contents($file), true);
}
JSON ворвался в нашу жизнь. Его реализация в PHP позволила даже обогнать одного из лидеров, INI-файлы, но немного уступает встроенной сериализации PHP.
Выводы
Итак, самый быстрый способ чтения конфигов — это чтение сериализованных данных. Работает быстрее остальных, не тормозит на больших объемах данных. Правда, при этом конфигурационные файлы править вручную достаточно сложно.
Если Вы серьезный человек — то избегайте прямого чтения текстовых файлов, особенно с большими объемами. Вместо этого Вам вполне подойдут JSON-файлы или INI-файлы, тем более, что скрипты станут работать быстрее.
Если нужны гибкие настройки, с возможностью применения условий и переменных — то пишите конфигурационный файл на PHP. Работать будет медленнее предыдущих способов, но гибкость настроек в других способах недостижима.
Настройки в формате XML — самые медленные. Прежде, чем их использовать, подумайте хорошенько.
Искренне надеюсь, что define'ы никто не использует, поэтому оставляем их обсуждение вне выводов.
Итог
Итак, что же применить в реальном приложении? Само собой напрашивается применение комбинированного способа хранения настроек. Например, настройки хранятся в виде PHP-скрипта, результаты выполнения котор��го кэшируются в виде сериализованного массива. Использование такого подхода вместо чтения конфигурационного файла на PHP позволило получить следующие результаты:
Результаты: 0.018, 0.046, 0.317
Оптимально с точки зрения гибкости и скорости, на мой взгляд.
А вот и сам скрипт:
function config($file) {
$file_dat = "$file.dat";
if (!file_exists($file_dat) || filemtime($file_dat) <= filemtime($file)) {
$r = include($file);
if ($F = fopen($file_dat, "w")) {
fwrite($F, serialize($r));
fclose($F);
}
} else {
$r = unserialize(file_get_contents($file_dat));
}
return $r;
}
Еще раз повторю, что подобные ухищрения могут быть полезны только в приложениях, которые запускаются часто и, соответственно, часто читают конфигурационные файлы.
P.S. PHP-код в статье не самый хороший. Я писал его, преследуя две цели: краткость и скорость работы. Поэтому отсутствуют комментарии, длинные имена переменных и различные проверки. Кроме того, большая часть кода работает под PHP 4 и 5 без проблем (кроме, конечно, XML). Надеюсь, это не вызовет излишнего накала страстей.
P.P.S. В сравнение добавлен код JSON.
P.P.P.S. Добавлены небольшие ремарки по поводу железа и программного обеспечения. Без них, согласен с авторами комментариев, было как-то не так.