Pull to refresh

Самые быстрые настройки для PHP-скриптов

Reading time 7 min
Views 35K
Наверное, все, кто сталкивался с разработкой более или менее серьезных приложений, знают, что выбор формата хранения настроек скрипта или приложения — достаточно ответственное дело. Конфиги должны быть легко читаемыми, легко модифицируемыми, легко переносимыми, и так далее — список можно продолжать и продолжать.

Так как серверные PHP-скрипты выполняются, бывает, много раз в секунду, скорость загрузки конфигов — достаточно важный параметр. Хотя ему, порой, уделяется не очень много внимания. Давайте сравним различные варианты хранения настроек для PHP-скриптов с точки зрения скорости их работы. Ну и коснемся вкратце их удобства.

Итак, подопытные:
  • INI-файлы
  • PHP-скрипты
  • XML-файлы
  • Текстовые файлы
  • Файлы с сериализованными данными
  • Вне конкурса — PHP-скрипты с define'ами
  • JSON-файлы NEW!
Чтобы никого не обидеть, перечисление в алфавитном порядке. Вариант хранения настроек в базе данных, кстати, не рассматривался. Уж слишком невыгодным он выглядит с точки зрения скорости доступа к настройкам.

Условия:
  • Как можно быстрее загрузить настройки из файла
  • Вернуть массив настроек в виде «ключ» => «значение»
  • Конфигурационный файл содержит 10, 100 или 1000 конфигурационных параметров, представляющих собой короткие строки
  • Конфигурация читается 1000 раз подряд, замеряется время работы в секундах
Понятно, что со вторым из условий тестирования можно поспорить. Мне этот вариант показался оптимальным для хранения настроек в памяти во время работы скрипта, но в некоторых случаях он таковым не является. И, кстати, этому условию не удовлетворяют PHP-скрипты с define'ами, из-за чего они и были помечены «вне конкурса».

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

Правда, необходимо сделать небольшое уточнение по поводу программного обеспечения сервера. Использовался реальный веб-сервер, в момент низкой загрузки. Соответственно, конфигурация сервера «боевая»: 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. Одно замечание: приведенный код возвращает не массив, а stdClass object.

Выводы


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

Если Вы серьезный человек — то избегайте прямого чтения текстовых файлов, особенно с большими объемами. Вместо этого Вам вполне подойдут 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. Добавлены небольшие ремарки по поводу железа и программного обеспечения. Без них, согласен с авторами комментариев, было как-то не так.
Tags:
Hubs:
+74
Comments 192
Comments Comments 192

Articles