Что такое php extensions

Пишем PHP extension

А давайте сегодня взглянем на PHP немного с другой точки зрения, и напишем к нему расширение. Так как на эту тему уже были публикации на Хабре (здесь и здесь), то не будем углубляться в причины того, для чего это может оказаться полезным и для чего может быть использовано на практике. Эта статья расскажет, как собирать простые расширения под Windows с использованием Visual C++ и под Debian с использованием GCC. Также я постараюсь немного осветить работу с PHP-массивами внутри расширений и провести сравнение производительности алгоритма, написанного на native PHP и использующего код, написанный на C.

Компиляция под Win32

Итак, начнем с Windows. Как известно, разработчики PHP используют Visual C++ 9 или Visual Studio 2008 для компиляции своего детища под Windows. Поэтому мы будем использовать Visual Studio 2008, бесплатная Express версия тоже подойдет, как впрочем, наверное, и более поздние и ранние версии студии.

  • Скомпилированные бинарники PHP 5.3, которые можно взять здесь,
  • Исходники PHP 5.3, которые можно скачать с сайта или вытянуть из общедоступного SVN,
  • Желание поэкспериментировать и немного терпения.
php-5.3.6 php-5.3.6\main php-5.3.6\TSRM php-5.3.6\Zend
PHP_WIN32 ZEND_WIN32 ZTS=1 ZEND_DEBUG=0
#ifndef STDAFX #define STDAFX #define PHP_COMPILER_ID "VC9" // эту опцию мы указываем для совместимости с PHP, скомпилированным Visual C++ 9.0 #include "zend_config.w32.h" #include "php.h" #endif

Если вы попытаетесь скомпилировать проект на данном этапе, вы получите ошибку, говорящую о том, что отсутствует main\config.w32.h. Его можно получить либо запустив скрипт main\configure.bat, либо можно выдернуть его из исходников, например версии PHP 5.2. При этом не забываем отредактировать в этом файле все пути и раскомментировать директиву «#define HAVE_SOCKLEN_T». Теперь проект должен скомпилироваться без ошибок.

Читайте также:  Get all input elements in javascript

Теперь давайте напишем hello world, добавим в наш cpp файл следующее:

PHP_FUNCTION(test); const zend_function_entry test_functions[] = < PHP_FE(test, NULL) >; zend_module_entry test_module_entry = < STANDARD_MODULE_HEADER, // #if ZEND_MODULE_API_NO >= 20010901 "test", // название модуля test_functions, // указываем экспортируемые функции NULL, // PHP_MINIT(test), Module Initialization NULL, // PHP_MSHUTDOWN(test), Module Shutdown NULL, // PHP_RINIT(test), Request Initialization NULL, // PHP_RSHUTDOWN(test), Request Shutdown NULL, // PHP_MINFO(test), Module Info (для phpinfo()) "0.1", // версия нашего модуля STANDARD_MODULE_PROPERTIES >; ZEND_GET_MODULE(test) PHP_FUNCTION(test) < RETURN_STRING("hello habr", 1); // возвращаем PHP-строку, второй параметр указывает, нужно ли копировать строку в памяти или нет >

На что мы должны получить ответ «hello habr».

Компиляция под *nix

  • Иметь установленный PHP на машине,
  • Иметь установленный PHP-dev. Для этого нужно выполнить всего одну команду:

Первый нужен для магической компиляции расширения, а во втором будет его исходный код. В config.m4 напишем следующее:

PHP_ARG_ENABLE(test, Enable test support) if test "$PHP_TEST" = "yes"; then AC_DEFINE(HAVE_TEST, 1, [You have test extension]) PHP_NEW_EXTENSION(test, test.c, $ext_shared) fi
# phpize // команда сгенерирует необходимые файлы для следующего шага # ./configure // сгенерируется makefile # make // компилируем # make install // устанавливаем .so в директорию с PHP расширениями

Обработка аргументов и возвращаемые значения

Для начала посмотрим, как можно принимать аргументы:

char* text; int text_length; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &text, &text_lenght) == FAILURE)

Третий параметр указывает ожидаемый тип (здесь можно просмотреть все варианты), в данном случае это char* или int. Также по ссылке можно найти варианты комбинирования типов и указания количества аргументов. Все следующие параметры являются переменными, в которые будут записаны переданные значения. При передаче строки передается сама строка и ее длина.
Если количество аргументов, переданных в вашу функцию, не совпадает, будет выброшен E_WARNING, при этом вы можете возвратить какое-либо значение, например, сообщение об ошибке.

Возвращать можно как простые типы, так и сложные. Давайте познакомимся с формированием возвращаемого массива. Для указания того, что будет возвращен массив, его нужно проинициализировать:

Для добавления значений в массив необходимо использовать функции, зависящие от того, какой индекс и значение добавляется в массив. Например:

add_next_index_long(result, 42); // $result[] = 42; add_assoc_bool(result, "foo", 1); // $result['foo'] = true; add_next_index_null(result); // $result[] = NULL; 

Полный список функций можно найти здесь

Если кого-то заинтересует, я могу в следующей статье рассмотреть пример работы с объектами (классический пример расширения на объектах — mysqli). Тут есть очень хорошая статья на эту тему.

Производительность

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

У меня получилась такая реализация, сильно не пинайте за код, я все-таки больше пишу на PHP, чем на C:

PHP_FUNCTION(calculate_chars) < char* text; int text_length; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &text, &text_length) == FAILURE) < return; >array_init(return_array); int table[256] = < 0 >; for (int i = 0; i < text_length; i++) < table[((unsigned char*)text)[i]]++; >char str[2]; str[1] = '\0'; for (int i = 0; i < 256; i++) < if (table[i]) < str[0] = (char)i; add_assoc_long(return_array, str, table[i]); >> >
user> php -r "print_r( calculate_chars('example') );" Array ( [a] => 1 [e] => 2 [l] => 1 [m] => 1 [p] => 1 [x] => 1 >

А теперь давайте сравним скорость выполнения этого кода и аналогичного на native PHP:

$map = array(); for ($i = 0; $i < $length; $i++) < $char = $text[$i]; if (isset($map[$char])) < $map[$char]++; >else < $map[$char] = 1; >>

Сравнивать я буду время выполнения обоих решений с помощью функции microtime. Возьмем строку в 100 символов, строку в 5000 символов, и строку в 69000 символов (я взял книгу A Message from the Sea, написанную Чарльзом Диккенсом, надеюсь, что он мне это простит), и для каждого варианта прогоним оба решения по несколько тысяч раз. Результаты приведены в таблице ниже. Тестирование проводилось на моем не самом сильном домашнем ноутбуке и VDS с Debian на борту, и да, я отчетливо понимаю, что результаты могут зависеть от конфигурации, от версии операционной системы, PHP, атмосферного давления и направления ветра, но я хотел показать лишь примерные цифры.
Полный код тестового скрипта можно скачать здесь. Исходники и бинарники самих расширений можно скачать здесь (win) и здесь (nix).

Кол-во итераций PHP code / Win32 PHP code / Debian PHP extension / Win32 PHP extension / Debian Win32 выигрыш Debian выигрыш
1. Строка 100 символов 1000000 84.7566 сек 72.5617 сек 8.4750 сек 4.4175 сек в 10 раз в 16.43 раз
2. Строка 5000 символов 10000 39.1012 сек 31.7541 сек 0.5001 сек 0.134 сек в 78.19 раз в 236.98 раз
3. Строка 69000 символов 1000 52.3378 сек 44.0647 сек 0.4875 сек 0.0763 сек в 107.36 раз в 577.51 раз

Выводы

Если судить о производительности модуля по сравнению с интерпретируемым кодом, то мы видим, что ощутимые результаты можно получить на больших объемах данных и на малых количествах итераций. То есть, для часто использующихся, однако, не очень ресурсоемких алгоритмов не имеет смысла вынесение их в компилируемый код. Но для алгоритмов, работающих с большими объемами данных, это может иметь практический смысл. Также, опираясь на мои измерения, можно заметить, что результаты работы PHP-кода сравнимы на разных системах (напомню, что это были две разные машины), а вот результаты работы расширения очень сильно отличаются. Из этого лично я делаю вывод, что существуют какие-то особенности компиляции, которые мне не известны. Впрочем, я сильно сомневаюсь, что кто-то использует Windows-сервера для PHP-проектов. Хотя я также очень сомневаюсь, что кто-то прямо сейчас побежит переписывать что-то на С, эта статья все-таки больше just for fun, чем руководство к действию. Просто я хотел показать, что написать PHP extension очень просто, и иногда может быть очень полезно.

UPD1. Сравнение с count_chars
В комментах задали интересный вопрос: что если сравнить с производительностью функции count_chars?
Я увеличил количество итераций в сто раз, и прогнал тот же самый тест, но уже с использованием этой функции. Можно увидеть, что на Debian результаты почти сравнялись, а под Windows наблюдается интересная ситуация: чем больше объем данных, тем больше мой модуль сливает в производительности. Напомню, что идея теста была не в том, чтобы написать велосипед, а в том, чтобы взять алгоритм для работы с большими объемами данных.

Кол-во итераций count_chars / Win32 count_chars / Debian extension / Win32 extension / Debian Win32 выигрыш Debian выигрыш
1. Строка 100 символов 10000000 67.5245 сек 47.8104 сек 81.8185 сек 43.8091 сек в 0.83 раз в 1.09 раз
2. Строка 5000 символов 1000000 22.4693 сек 12.8959 сек 47.2514 сек 12.9577 сек в 0.48 раз в 0.99 раз
3. Строка 69000 символов 100000 15.0681 сек 7.661 сек 46.9598 сек 7.7387 сек в 0.32 раз в 0.99 раз

Материалы

  • PHP at the Core: A Hacker’s Guide to the Zend Engine, php.net
  • Compiling shared PECL extensions with phpize, php.net
  • Creating a PHP Extension for Windows using Microsoft Visual C++ 2008, talkphp.com
  • Extension Writing Part I: Introduction to PHP and Zend, devzone.zend.com
  • Extension Writing Part II: Parameters, Arrays, and ZVALs, devzone.zend.com
  • Wrapping C++ Classes in a PHP Extension, devzone.zend.com

Источник

Два типа расширений PHP. Zend extension VS PHP module

image

PHP module – оно же обычное расширение PHP
К этому типу относится подавляющее число расширений в PHP. Все то, что подключается в php.ini с помощью инструкции extension=some_library.so — это они и есть.

Zend extension
Расширений такого типа крайне мало, однако они ничуть не менее востребованы.

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

С точки зрения конечного пользователя.

Отличаются только способом подключения.
Обычные расширения подключаются через php.ini с помощью инструкции:
extension=some_extension.so
Расширения zend с помощью:
zend_extension=some_extension.so .

Если хочется подключить через аргумент командной строки, то, для обычных:
php -d extension=/path/extension.so
А для расширений zend:
php -z /path/zend_extension.so

Однако под капотом они очень разные.

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

С точки зрения решаемых задач

Стандартные расширения, в подавляющем числе случаев, используются для расширения функциональных возможностей языка, таких как добавления новых классов, функций, констант и т.д. Крайне редко используются для решения других задач. Например, PECL расширение Vulcan Logic Disassembler(vld) позволяет посмотреть сгенерированный opcode для скрипта.

Расширения zend используются в случаях, когда нужно максимально глубоко залезть внутрь виртуальной машины. Например для отладки или профилирования скрипта, либо для изменения логики работы PHP.

С точки зрения разработчика, который раньше не писал расширений для PHP и вдруг сподобился

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

В случае с Zend extension ничего этого нет. Хороших статей практически нет. Плохих тоже. Будьте готовы к длительному и вдумчивому изучению исходных кодов как самого PHP, так и немногих существующих расширений данного типа.

С точки зрения жизненного цикла

К сожалению, тут не обойтись без кода на С, поскольку жизненный цикл расширения целиком и полностью является отражением определяющей его структуры. (Структуры привожу в сокращенном виде. Только то, что необходимо в рамках статьи)

Стандартное расширение задается структурой _zend_module_entry (описывается в zend_module.h )

struct _zend_module_entry < /* skipped */ int (*module_startup_func)(INIT_FUNC_ARGS); /* MINIT() */ int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS); /* MSHUTDOWN() */ int (*request_startup_func)(INIT_FUNC_ARGS); /* RINIT() */ int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS); /* RSHUTDOWN() */ void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS); /* PHPINFO() */ /* skipped */ void (*globals_ctor)(void *global); /* GINIT() */ void (*globals_dtor)(void *global); /* GSHUTDOWN */ int (*post_deactivate_func)(void); /* PRSHUTDOWN() */ /* skipped */ >;

Расширение Zend задается структурой _zend_extension (описывается в zend_extensions.h )

А вот теперь уже можно показывать картинку с жизненным циклом.

image

Бонус. Гибридные расширения

Да. Такая возможность есть.

Зачем оно может понадобиться?

  1. Вам нужен полный контроль, предоставляемый расширением zend и, помимо этого, хочется зарегистрировать новые функции.
  2. Вам, зачем-то, понадобилось использовать вообще все возможные хуки.
  3. Вам необходимо управлять порядком загрузки своего расширения. К примеру надо загрузиться не раньше загрузки OPCache .

Источник

Оцените статью