воскресенье, 12 января 2014 г.

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

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

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

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

В качестве подопытного был выбран модуль постраничного разбиения.
Исходники на Гитхабе: 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 prefix
    $scr->setSpace('...'); // Разделитель блоков табов
    $scr->setCssName('newClassName'); // Имя класса css блока управления постраничным выводом
    $scr->setMidTab(15); // см. info.png
    $scr->setMaxTab(5); // см. info.png
    $scr->showCount(true); // Показывать ли общее количество элементов

Кроме того доступны следующие методы для получения вычисленных данных:
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-6 sec
ab -n 10000 ... : Time per request:       0.39 [ms] (mean)
C++
memory usage: 1.26Kb
memory peak_usage: 1.8Kb
Вычесленное: time: 60•10-6 sec
ab -n 10000 ... : Time per request:       0.4 [ms] (mean)

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

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

php
time: 215•10-6 sec
memory usage: 16.5Kb
memory peak_usage: 22.7Kb
 ab -n 10000 ... : Time per request:       0.533 [ms] (mean)
C++
time: 170•10-6 sec
memory usage: 2.3Kb
memory peak_usage: 4.6Kb
 ab -n 10000 ... : Time per request:       0.49 [ms] (mean)

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