Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode

Сравнение производительности C++ php-расширения с нативным кодом

Давно меня интересовал вопрос: насколько увеличивается производительность при переписывании нативного кода в php-расширение.

И вот я решил провести сравнение.

В качестве платформы для написания расширения была выбрана библиотека PHP-CPP.

Пару слов о самой библиотеке PHP-CPP.
Довольно неплохая библиотека. Работать с ней приятно и удобно. Честно говоря, никогда еще не было так легко писать расширения для php.
Из минусов могу отметить недостаток документации. Чтобы разобраться с некоторыми вещами, недостаточно даже примеров, которыми автор снабжает код — приходится смотреть исходники. В частности, я так и не разобрался, как перенести статический метод класса из C++ в статический же метод в php.
Основана она на C++11, т.е. на старых дистрибутивах может потребоваться обновить gcc.

В качестве подопытного был выбран модуль постраничного разбиения.
Исходники на GitHub: https://github.com/valmat/myscrnav
Я написал два идентичных класса: один на php, другой на C++ (в виде расширения к php).

Пару слов как пользоваться.
Пример использования есть в исходниках: https://github.com/valmat/myscrnav/blob/master/screennav_test.php

Создаем pagination-объект из расширения:

$scr = new myScrNav($pageNom, $Count, '/url/to/page/');

Или из php-класса:

require 'php/class.screennav.php';
$scr = new ScreenNav($pageNom, $Count, '/url/to/page/');

Задаём необходимые параметры (если необходимо, можно не задавать):

$scr->setInterval(10);       // Сколько объектов на странице
$scr->setPrefix('?qwe&part='); // URL prefix
$scr->setPostfix('&prm=132');  // URL postfix
$scr->setSpace('<space>...</space>'); // Разделитель блоков табов
$scr->setCssName('newClassName');     // Имя класса css блока управления постраничным выводом
$scr->setMidTab(15);          // см. info.png
$scr->setMaxTab(5);           // см. info.png
$scr->showCount(true);        // Показывать ли общее количество элементов

info.png

Кроме того, доступны следующие методы для получения вычисленных данных:

getStartPos();   // Номер начального элемента на текущей странице (для выборки из БД)
getLimitPos();   // Длина списка элементов на странице на текущей странице (для выборки из БД)
getPageCnt();    // Количество страниц при разбивке на части
getStartPos();   // Номер (вычисленный) текущей страницы
show();          // Возвращает собственно сам элемент управления постраничной разбивкой (html)

То есть можно управлять постраничной разбивкой и делать запросы к БД на основе этой разбивки. Внешний вид, разумеется, полностью настраивается через css.

Теперь сами результаты сравнения.
PHP тестируется с включённым опкешером (apc). Без него смысла тестировать не вижу, ибо тестировать нужно так, как используется на рабочей системе.

1
Во-первых, сравним просто функции.

На php будет:

function ScreenNav_pageNo($var) {
    return (isset($_GET[$var])) ? ((int)$_GET[$var] - 1) : 0;
}

На C++:

Php::Value GETpageNom(Php::Parameters &params) {
    if (params.size() == 0) return 0;
    string var = (new Php::Value(params[0]))->stringValue();
    string get = Php::globals["_GET"][var];
    long int rez = (new Php::Value(get))->numericValue();
    return rez ? (rez - 1) : 0;
}

php

  • memory usage: 0.56Kb
  • memory peak_usage: 1.1Kb
  • Вычисленное: time: 50·10⁻⁶ sec
  • ab -n 10000 … : Time per request: 0.39 [ms] (mean)

C++

  • memory usage: 1.26Kb
  • memory peak_usage: 1.8Kb
  • Вычисленное: time: 60·10⁻⁶ sec
  • ab -n 10000 … : Time per request: 0.4 [ms] (mean)

Как видно, на таком простом примере расширение не выигрывает, а даже проигрывает нативному коду.


2
Теперь сравним классы.

php

  • time: 215·10⁻⁶ sec
  • memory usage: 16.5Kb
  • memory peak_usage: 22.7Kb
  • ab -n 10000 … : Time per request: 0.533 [ms] (mean)

C++

  • time: 170·10⁻⁶ sec
  • memory usage: 2.3Kb
  • memory peak_usage: 4.6Kb
  • ab -n 10000 … : Time per request: 0.49 [ms] (mean)

Выводы

Разница во времени порядка 10⁻⁵ sec — не то, ради чего нужно переписывать с php на C++.
С другой стороны, само то, что она проявилась на такой незначительной задаче — уже результат.
Приведённый пример позволяет понять порядок выигрыша и оценить целесообразность переписывания нативного кода в C++ расширение.

Обращает на себя внимание разница в потреблении памяти. Причём, если расширение выгрузить, то потребление памяти нативным кодом не уменьшается.