Разрабатываем модуль PHP на C++. Часть 1: экскурс в мир Zend Engine 2

    Доброго времени суток, уважаемое Харбасообщество!

    Под катом изложена некоторая информация касательно написания расширений для PHP c использованием C++, почерпнутая мной из различных источников (по большей части англоязычных) и ковыряния исходников Zend Engine 2 во время разработки одного модуля для собственных нужд. Так как объем ее достаточно велик, далее я старался быть краток.

    Итак, в этой части:

    А вот до C++ в этой части мы так и не доберемся… =)

    Маленький дисклеймер: содержимое статьи не есть истина в первой инстанции, не основывается на официальной документации (а есть ли она?) и является моим субъективным взглядом на ZE 2. Тем не менее, в свое время я был бы рад найти нечто подобное на просторах Рунета, дабы сэкономить время на начальных этапах разработки.



    Внутренний мир Zend Engine 2


    Основные типы данных

    Zend Engine 2 написан на C. Это сильно повлияло на его внутреннюю экосистему. В отсутствие класс-объектной парадигмы внутри ZE 2 расплодились глобальные переменные, свободные функции и подобия пользовательских типов данных — структуры. На все случаи жизни существуют свои комбинации простых и составных типов данных и процедур, предназначенных для их обработки.

    Наиболее часто встречается структура zval (zend-значение?). Структура является представлением PHP-переменной по обратную сторону от userspace (под userspace здесь и далее будем понимать код, написанный на PHP, выполнением которого и занимается ZE, следовательно, обратная сторона userspace — это код на C).
    PHP является языком со слабой динамической типизацией и автоматическим управлением памятью, переменные в этом языке могут менять свой тип на протяжении своего жизненного цикла и не требуют явного удаления программистом после того, как необходимость в них отпадает (сборщик мусора самостоятельно позаботится об этом). За эти прихоти отчасти отдуваться приходится и zval`у. На данный момент (PHP 5.3.3) эта структура определена следующим образом (zend.h):

    typedef struct _zval_struct zval;
    
    struct _zval_struct {
    	/* Variable information */
    	zvalue_value value;		/* value */
    	zend_uint refcount__gc;
    	zend_uchar type;	/* active type */
    	zend_uchar is_ref__gc;
    };
    

    Что же мы здесь видим? Как это не удивительно, zend-значение (zval) не является непосредственно значением переменной. Значение переменной хранится в поле value, тип которого zvalue_value (ниже). Тип значения, хранящегося в value, определяется полем type и может быть одним из следующих (zend.h):

    	#define IS_NULL           0
    	#define IS_LONG           1
    	#define IS_DOUBLE         2
    	#define IS_BOOL           3
    	#define IS_ARRAY          4
    	#define IS_OBJECT         5
    	#define IS_STRING         6
    	#define IS_RESOURCE       7
    	#define IS_CONSTANT       8
    	#define IS_CONSTANT_ARRAY 9
    

    Ага, вот они — те самые 8 типов данных PHP, которые люди так часто не могут перечислить на собеседованиях! Плюс два отдельно стоящих значения IS_CONSTANT и IS_CONSTANT_ARRAY.

    Структура также содержит информацию о количестве ссылок на данную переменную (refcount__gc) и флаг, определяющий, является ли эта переменная ссылкой (is_ref__gc). Эта информация нужна для организации эффективной работы с памятью внутри ZE. Например следующая ситуация в userspace:

    <?php
    $foo = 5;      // 1
    $bar = $foo;   // 2
    

    приведет к созданию объекта zval val, имеющего тип IS_LONG (все целочисленные в userspace соответствуют C long по другую сторону от userspace), установке у него is_ref_gc в 0 и refcount_gc в 1 (первая строка) и регистрации символа «foo» (об этом в следующих сериях) в текущей таблице символов процесса (на самом деле просто ассоциативный массив имен переменных и их значений) с val в качестве значения. На второй же строке новый экземпляр zval создан не будет. У только что помещенного в таблицу символов val`а будет увеличен на 1 счетчик ссылок, и символ «bar» будет зарегистрирован в таблице символов с этим же самым val`ом. За счет этого будет сокращено количество необходимых выделений памяти для создания новых переменных.
    Когда же интерпретатор встретит код:

    ...
    $bar = '42';
    

    он извлечет из текущей таблицы символов процесса zval, соответствующий символу «bar», и, в первую очередь, проверит количество ссылок на это значение. Если оно больше 1 и zval не является ссылкой (is_ref_gc == 0), то интерпретатор создаст копию текущего значения $bar, выполнит действия с ней (в нашем случае приравняет строковому значению '42') и поместит в таблицу символов «bar» с уже новым значением. В случае же, если refcoun_gc == 1 или is_ref_gc == 1 действия будут выполнены непосредственно над значением, полученным из таблицы символов. Таким образом в следующей (достаточно искусственной, но имеющей право на жизнь) ситуации:

    <?php
    $foo = 100500;  //1
    $bar = $foo;   //2
    echo $bar;
    unset($bar);
    $foo = '42';  //3
    

    интерпретатор обойдется всего одним zval`ом, но двумя символами, соответствующими ему. Это возможно, потому что на строке с комментарием 1 будет создан zval val у которого количество ссылок будет равно 1. На строке c комментарием 2 будет зарегистрирован новый символ «bar» со значением val, у которого при этом refcoun_gc уже равен 2, но на строке с комментарием 3 новый zval создан не будет, так как после вызова unset($bar) количество ссылок на val снова сократится до 1.
    Как не сложно догадаться is_ref_gc становится равным 1, когда в userspace встречаются конструкции вида $b = &$a.
    Описанный выше подход можно назвать «отделение при модификации» (separate on write).

    Теперь посмотрим на тип zvalue_value (zend.h):

    typedef union _zvalue_value {
    	long lval;					/* long value */
    	double dval;				/* double value */
    	struct {
    		char *val;
    		int len;
    	} str;
    	HashTable *ht;				/* hash table value */
    	zend_object_value obj;
    } zvalue_value;
    

    Не трудно заметить, что это объединение. Это значит, что данные, лежащие в памяти по адресу переменной типа zvalue_value могут быть удобным для программиста способом проинтерпретированы как на этапе разработки, так и на этапе выполнения как любой из типов данных, входящих в состав объединения. Именно эта особенность zvalue_value позволяет userspace-переменным PHP так легко менять свой тип в течение жизни (напомню, что текущий тип переменной zval можно выяснить, обратившись к ее полю type).
    Однако, вы можете сказать, что видите здесь всего 5 уникальных полей, а типов данных 8. Отображение типов PHP на объединение zvalue_value следующее:
    • long lval — целочисленные (integer), логический тип (boolean) и ресурсы (resource)
    • double dval — тут однозначно (double)
    • struct… str — строки PHP (string)
    • HashTable *ht — массивы (array)
    • zend_object_value obj — объекты PHP (object) — как SPL, так и пользовательские.
    • null — значение не имеет никакого значения (zval.type == IS_NULL).

    Что же полезного можно вынести из рассмотрения структуры zvalue_value?
    1. Тип resource представлен только лишь целочисленным идентификатором, и поэтому в userspace выглядит темной лошадкой. По другую сторону от userspace такому идентификатору может быть сопоставлен дескриптор открытого файла, TCP сокет, объект соединения с БД и т.п. Сопоставление происходит через специальное хранилище ресурсов (на стороне C, естественно).
    2. Все вещественные в PHP имеют двойную точность и занимают 8 байт.
    3. Строки в принципе могут быть binary safe, т.е. использовать внутри себя нулевые символы. Достигается за счет того, что длина строки хранится вместе с указателем на память, где эта строка располагается. Операция strlen является быстрой в userspace. Нулевой символ на конце строки не обязателен. На самом же деле многие расширения используют именно нуль-терминированные строки и не брезгуют сишной strlen.
    4. Массивы представлены внутренним типом HashTable. HashTable — это очередная структура, но рассмотрение ее и принципов работы с ней выходит за рамки этой статьи.
    5. Объекты в PHP представляются структурой zend_object_value. О ней мы поговорим далее, так как именно с созданием собственного типа данных и связана разработка нашего расширения.
    6. Создание переменной в PHP потратит минимум 16 байт на 32-битной архитектуре, какого бы типа переменная не была (складываем размеры полей zval с учетом того, что размер объединения равен размеру максимального из полей в его составе).


    Вот мы и добрались до объектов. Структура zend_object_value предназначена для представления userspace-переменных, содержащими в себе объекты. А что из себя представляют объект в класс-объектной парадигме? Объект — это симбиоз данных и методов по их обработке. Теперь давайте посмотрим на структуру zend_object_value (zend_type.h):

    typedef unsigned int zend_object_handle;
    typedef struct _zend_object_handlers zend_object_handlers;
    
    typedef struct _zend_object_value {
    	zend_object_handle handle;
    	zend_object_handlers *handlers;
    } zend_object_value;
    

    Структура представляет собой объединение некоторого целочисленного идентификатора (handle) и еще одной структуры (zend_object_handlers *handlers), содержащей в себе указатели на функции, которые будут вызываться движком ZE 2 при наступлении тех или иных событий, связанных с объектом. К таким событиям относятся: приравнивание новой переменной значения переменной, содержащей в себе объект (zend_object_add_ref_t add_ref), выход за пределы области видимости, инициализация другим значением или вызов unset для переменной, содержащей в себе объект (zend_object_del_ref_t del_ref), клонирование объекта вызовом __clone ( zend_object_clone_obj_t clone_obj), обращение к свойству объекта (zend_object_read_property_t read_property), запись свойства объекта (zend_object_write_property_t write_property) и т.д. Сама структура zend_object_handlers выглядит следующим образом (zend_object_handlers.h):

    struct _zend_object_handlers {
        /* general object functions */
        zend_object_add_ref_t                   add_ref;
        zend_object_del_ref_t                   del_ref;
        zend_object_clone_obj_t                 clone_obj;
        /* individual object functions */
        zend_object_read_property_t             read_property;
        zend_object_write_property_t            write_property;
        zend_object_read_dimension_t            read_dimension;
        zend_object_write_dimension_t           write_dimension;
        zend_object_get_property_ptr_ptr_t      get_property_ptr_ptr;
        zend_object_get_t                       get;
        zend_object_set_t                       set;
        zend_object_has_property_t              has_property;
        zend_object_unset_property_t            unset_property;
        zend_object_has_dimension_t             has_dimension;
        zend_object_unset_dimension_t           unset_dimension;
        zend_object_get_properties_t            get_properties;
        zend_object_get_method_t                get_method;
        zend_object_call_method_t               call_method;
        zend_object_get_constructor_t           get_constructor;
        zend_object_get_class_entry_t           get_class_entry;
        zend_object_get_class_name_t            get_class_name;
        zend_object_compare_t                   compare_objects;
        zend_object_cast_t                      cast_object;
        zend_object_count_elements_t            count_elements;
        zend_object_get_debug_info_t            get_debug_info;
        zend_object_get_closure_t               get_closure;
    };
    

    а прочитать о ней подробно можно здесь.

    Отвлеклись, вернемся к zend_object_value. Итак, что в ней содержится помимо указателей на функции-обработчики событий? А ничего! Если некое подобие попытки определения поведения объекта мы увидели в _zend_object_handlers, то кроме какого-то странного идентификатора (handle) никаких данных, специфичных для конкретного экземпляра в ней не наблюдается. Но идентификатор сам по себе (сферический в вакууме) смысла не имеет. Значит должно быть какое-то хранилище однородных сущностей, где этот идентификатор будет отличать одну сущность от другой.
    Таким хранилищем в ZE является Zend Object Storage. Ключами в нем являются дескрипторы объектов (zend_object_value.handle), а значениями… Да, да, вы уже наверное догадались — еще один вид структур — zend_object (zend.h):

    typedef struct _zend_object {
    	zend_class_entry *ce;
    	HashTable *properties;
    	HashTable *guards; /* protects from __get/__set ... recursion */
    } zend_object;
    

    Здесь из разрозненных кусочков уже начинает складываться картина маслом. HashTable *properties — вот где можно держать данные, специфичные для конкретного экземпляра. Поле properties — это стандартный для ZE ассоциативный массив, ключами которого должны являться имена полей класса текущего объект, а значениями — текущие значения полей (свойств) этого объекта.

    Итак, на данный момент мы имеем следующие возможности для работы с объектами — умеем переопределять стандартное поведение объекта в тех или иных ситуация (за счет переопределения соответствующих функций в zend_object_handlers) и можем хранить данные в полях экземпляров, записывая их в HashTable *properties, связанную с текущим объектом. Чего-то не хватает… Ах да, а как же добавлять пользовательское поведение объекту (создавать новые методы)? Так как методы — это нечто общее для всех объектов одного класса, их логично было бы разместить в некоторой структуре, разделяемый доступ к которой был бы у всех объектов класса. Такой структурой является zend_class_entry (zend.h):

    struct _zend_class_entry {
    	char type;
    	char *name;
    	zend_uint name_length;
    	struct _zend_class_entry *parent;
    	int refcount;
    	zend_bool constants_updated;
    	zend_uint ce_flags;
    
    	HashTable function_table;
    	HashTable default_properties;
    	HashTable properties_info;
    	HashTable default_static_members;
    	HashTable *static_members;
    	HashTable constants_table;
    	const struct _zend_function_entry *builtin_functions;   //ПОЛЬЗОВАТЕЛЬСКИЕ МЕТОДЫ
    
    	union _zend_function *constructor;               //МЕТОДЫ, КОТОРЫМИ ОБЛАДАЮТ ОБЪЕКТЫ
    	union _zend_function *destructor;                 //ЛЮБОГО БЕЗ ИСКЛЮЧЕНИЯ КЛАССА
    	union _zend_function *clone;
    	union _zend_function *__get;
    	union _zend_function *__set;
    	union _zend_function *__unset;
    	union _zend_function *__isset;
    	union _zend_function *__call;
    	union _zend_function *__callstatic;
    	union _zend_function *__tostring;
    	union _zend_function *serialize_func;
    	union _zend_function *unserialize_func;
    
    	zend_class_iterator_funcs iterator_funcs;
    
    	/* handlers */
    	zend_object_value (*create_object)(zend_class_entry *class_type TSRMLS_DC);
    	zend_object_iterator *(*get_iterator)(zend_class_entry *ce, zval *object, int by_ref TSRMLS_DC);
    	int (*interface_gets_implemented)(zend_class_entry *iface, zend_class_entry *class_type TSRMLS_DC); /* a class implements this interface */
    	union _zend_function *(*get_static_method)(zend_class_entry *ce, char* method, int method_len TSRMLS_DC);
    
    	/* serializer callbacks */
    	int (*serialize)(zval *object, unsigned char **buffer, zend_uint *buf_len, zend_serialize_data *data TSRMLS_DC);
    	int (*unserialize)(zval **object, zend_class_entry *ce, const unsigned char *buf, zend_uint buf_len, zend_unserialize_data *data TSRMLS_DC);
    
    	zend_class_entry **interfaces;
    	zend_uint num_interfaces;
    
    	char *filename;
    	zend_uint line_start;
    	zend_uint line_end;
    	char *doc_comment;
    	zend_uint doc_comment_len;
    
    	struct _zend_module_entry *module;
    };
    

    Назначение структуры zend_class_entry — представлять общие аспекты всех объектов одного класса. zend_class_entry фактически и есть сам класс. Структура, как видите, не маленькая и рассматривать назначение каждого из ее полей не есть задача этой статьи. Остановим наше внимание на полях, которые я пометил комментариями.

    const struct _zend_function_entry *builtin_functions — указатель на массив структур _zend_function_entry. Не трудно догадаться, что это и есть методы нашего будущего класса. Так как он помечен модификатором const, изменять элементы этого массива (т.е. переопределять методы инстанцированного объекта класса) не возможно (в отличие от zend_object_value.handlers).

    Поля, начиная с constructor по unserialize_func, являются магическими методами PHP (не трудно догадаться, что unserialize_func — это __wakeup, а serialize_func — это __sleep, остальные же методы имеют схожую мнемонику).

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

    Последним, но не по значению, героем этого экскурса в увлекательный мир структур ZE, будет структура, которая занимается представлением самого модуля расширения — zend_module_entry (zend_modules.h):

    struct _zend_module_entry {
    	unsigned short size;
    	unsigned int zend_api;
    	unsigned char zend_debug;
    	unsigned char zts;
    	const struct _zend_ini_entry *ini_entry;
    	const struct _zend_module_dep *deps;
    	const char *name;
    	const struct _zend_function_entry *functions;
    	int (*module_startup_func)(INIT_FUNC_ARGS);
    	int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);
    	int (*request_startup_func)(INIT_FUNC_ARGS);
    	int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS);
    	void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS);
    	const char *version;
    	size_t globals_size;
    #ifdef ZTS
    	ts_rsrc_id* globals_id_ptr;
    #else
    	void* globals_ptr;
    #endif
    	void (*globals_ctor)(void *global TSRMLS_DC);
    	void (*globals_dtor)(void *global TSRMLS_DC);
    	int (*post_deactivate_func)(void);
    	int module_started;
    	unsigned char type;
    	void *handle;
    	int module_number;
    	char *build_id;
    };
    

    Так как в общем случае расширение может реализовывать не один, а сразу несколько классов, или же наоборот — экспортировать одни лишь функции (на ООП свет клином не сошелся, как говорится), рассмотрим, как это повлияло на дизайн вышеупомянутой структуры:

    1. const struct _zend_function_entry *functions — указатель на массив функций, экспортируемых расширением. По аналогии с zend_class_entry.builtin_functions.
    2. int (*module_startup_func)(INIT_FUNC_ARGS) — указатель на функцию, которая будет вызвана при подключении расширения. В частности, если расширение экспортирует классы, именно в этой функции классы должны быть зарегистрированы во внутреннем реестре классов ZE 2.
    3. int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS) — указатель на функцию, которая вызывается при выгрузке расширения. Здесь мы должны подтереть за собой.


    Иерархия типов данных



    В предыдущем пункте мы рассмотрели путь от рядового zval до генерала zend_module_entry. Умело оперируя этими типами, можно создать свое расширение PHP и организовать слаженную фабрику по производству объектов. На самом деле, модуль расширения PHP похож на школу по подготовке кадров для userspace. Сначала ее нужно построить (вызвать PHP_MINIT_FUNCTION) и зарегистрировать на бирже труда (объявив в PHP_MINIT_FUNCTION экспортируемые классы или функции) в качестве рекрутерского агенства с определенной направленностью, а затем по первому запросу на получение сотрудника (нового экземпляра класса) запускать цикл по подготовке бойца (создания объекта). Подготовка заключается в выделении памяти под создаваемый объект, связывании его со специфическими обработчиками событий (zend_object_handlers) и собственным классом (zend_class_entry), в котором содержатся методы будущего объекта, и регистрации объекта в Zend Object Storage с последующим присвоением ему уникального идентификатора. Такая подготовка обычно помещается в функцию имя_расширения_objects_new и связывается с полем zend_class_entry.create_object.

    Схематически, структуру расширения можно изобразить в следующем виде:



    А чтобы более наглядно представить себе иерархию типов данных в расширении приведу следующую схемку:



    Заключение


    В статье получилось много текста и совсем мало кода, но без экскурса в мир типов данных ZE 2 пытаться понять назначение вызовов тех или иных функций было бы достаточно сложно. В следующей части я приведу объяснение первых шагов, которые нужно выполнить, чтобы создать свой модуль расширения PHP, но перед этим затрону тему работы с zval`ами и управления выделением памятью.

    P.S. Отличная подборка статей для интересующихся лежит здесь.
    Поделиться публикацией

    Похожие публикации

    Комментарии 38

      +2
      Замечательная тема.
      Вы дойдёте до структуры байт кода и создания своего бинарного компилятора?
        0
        Нет, оставим это на совести g++.
          +4
          Я имел в виду не машинный байткод, а для типа того, что генерирует zend encoder. Чтобы потом этот же модуль его мог преобразовать в обычные структуры zend_op и выполнить цепочку.
            +1
            Загляните в исходный код всевозможных PHP акселераторов/байт-код кэшеров, там всё есть.
              +3
              Знаю. Но расписать эту тему на хабре было бы очень интересно для разработчиков, я считаю.
        0
        До таких глубин я еще не добрался. Но мало ли, чем черт не шутит =)
          +1
          Как минимум, будем точно знать сермяжную правду про типы данных в ПХП :)
          +1
          Я год назад пытался на работе написать модуль, который бы подставлял вместо встроенных php любые другие, определённые пользователем. Тогда оно всё так и зависло, хотя кое-какой подстановки добился, но сейчас вспомнить бы было кстати.
            –5
            проще на рубях переписать аналог проекта.
              +7
              На чём знаем, на том пишем :) И сколько бы люди не холиварили на чём проще, лучше и т.д. Факт остаётся фактом, что чем больше знаешь инструментов, тем лучше, быстрее и проще можно получить конечный результат.
                0
                Зная пых, я бы на нем такую систему не затевал. Нестабильно.
                Сайт да, демон возможно, но не такие конструкции.
                Исхожу из удобства пользования и личного опыта.
                Минусующим привет)
                  0
                  Эмм, не понял вашего изъяснения. Если вы про то, что я делал, то изначально был готовый проект на php, надо было сделать для него такой вот финт ушами.
                    0
                    Ясно) Там просто с этим делом так себе. нет настолько распространенных подсистем, как rvm например. Да и в php меньшая зависимость от версий. Если только 4.x, 5.x, 5.3, 5.4, но это все можно и в коде описать при надобности без таких заморочек. Даже лучше в коде описать. Весьма портабельно.
            +4
            Документация по Zend2 ужасная — лишь поверхностное описание структур.
              +5
              Чтобы правильно поняли: на сайте у zend'а, а не в статье.
              +1
              Большое спасибо. очень доступно и актуально, жду продолжения.
                +2
                Интересно узнать у автора — вы сами используете самописные модули на c++ в своих проектах? Если да, то по возможности расскажите как и для чего?
                  +8
                  С недавних пор да. Как использую — подключаю в PHP ini. =) Для чего — для увеличения производительности.

                  А если серьезно, был один класс PHP, который активно использовался в проекте. Профилирование показало, что много времени уходит именно на выполнение работы этим классом. После портирования на C++ этот участок ускорился в 2 раза, но это далеко не предел, так как оптимизации еще и близко не было.

                  ZE написан на C, я же привык к языкам чуть более высокого уровня. Изначально по привычке стал активно использовать std::string, std::vector, std::map и т.п. В результате получилось слишком большое количество ненужных преобразований из zval в стандартные типы C++ (так как из userspace все параметры приходят именно в zval`ах). Производительность была на уровне оригинального класса PHP. Отказавшись от использования вектора в качестве массива строк в пользу зендовской HashTable получил двукратный прирост производительности за счет сокращения ненужных операций по работе с памятью. Думаю, переезд со стандартных строк на zval`ы даст еще больший прирост, так как специфика класса такова, что он очень активно работает именно со строковыми данными. Неудобство же работы с сырыми zval`ами в качестве массивов (HashTable) я обошел, написав легкую обвеску в виде класса-массива вокруг зендовской HashTable.
                    0
                    > С недавних пор да. Как использую — подключаю в PHP ini. =) Для чего — для увеличения производительности.

                    Ну вы мне не ответ написали ) Про php.ini и пр. я бы и в документации прочитал сам, если бы не знал. «для увеличения производительности» — это вроде как всем и так уже всем вроде понято. Я тут не для холиваров и пр. Интересно суть проблемы узнать. Под вопросом «как и где» я имел ввиду реальные примеры. Просто я как-то раз думал о внедрении, но тут проблема тестирования встала, да и просто надежности.

                    Хочется узнать для каких именно задач вы именно вы используете.

                    Не знаю будет ли следующее в тему, но скажу. Я вот совершенно в очередной раз перечитывал мануал недавно наткнулся на «SplDoublyLinkedList» в структуру этого типа можно делать push объектов (это важно!) раза в 2,5 — 3 быстрее, чем в стандартные массивы.
                      0
                      Spl-массивы действительно работают значительно быстрее обычных. Когда заранее известен размер массива SplFixedArray вообще оказывается отличным решением.

                      Внедрения модуля еще не было. Класс, который мы портировали, изначально был разработан не нами, тестами он не покрыт, во всяком случае в той комплектации, в которой он предлагается для скачивания. При этом он используется уже ни один год (и не только нами, я уверен), так что о надежности кода на PHP можно не сильно волноваться.
                      Я написал собственные тесты, которые прогоняют некоторый объем функциональности исходного класса и моего порта, при этом производится сравнение результатов выполнения обоих классов между собой. Результаты работы оригинального класса принимаются за expected, а моего — за actual.
                      +1
                      У меня задача была уменьшить потребление памяти программы. Она уходила именно на большое кол-во объектов > 5 млн. со свойствами и пр. (предлагаю не обсуждать зачем мне это надо было, это сейчас не важно). Так вот я реализовал простой класс-убийцу класс-заменитель на C++ как extension для php и всё было круто — память почти не кушалась, по сравнению с php, но вот уже бизнес-логику в классы было добавит сложнее. И тут я подумал — а не сделать ли мне наследника этого класса, не переобъявляя свойства cpp-класса? Сделал — и память тут же начала кушаться как раньше.

                      Следующая проблема — поддержка таких классов. Много знаете программистов которые смогут разобраться в этом коде?

                      И тут я понял — может просто стоит изолировать такие места в отдельные подпрограммы на cpp, если уж так важна скорость? На этом я и остановился. Но вообще вопрос интересный, так-то я не против ускорить узкое место, просто понимать стоит ли оно того.
                        0
                        Я правильно понимаю, что Вы в сишную часть перенесли только свойства объектов, а методы при этом решили реализовывать на PHP?
                          0
                          Да, но вот я только что подумал о том, что я не пробовал сишный класс как единственное свойство моего «бизнес» класса сделать и там всю инфу хранить — так может и отрулило бы. Правда это какой-то антипаттерн получается — объект для простого хранения инфы.
                            0
                            Просто я не могу понять, какой смысл хранить данные отдельно от того места, где они будут обрабатываться. Если решили хранить данные внутри расширения, то и обрабатывайте их там же. Если обрабатываете их в PHP, то и храните их в PHP. В противном же случае от создания своего расширения ничего, кроме накладных расходов Вы не поимеете. Возможно, Вам просто стоит пересмотреть архитектуру своего проекта.
                              0
                              Я писал, что я отказался от варианта расширений в принципе, так как обрабатывать данные внутри него — это довольно сложно, для этого нужен уже не php разработчик, а c++ программист. И тут встает вопрос — а оно надо? Может просто эту часть проекта на c++ переписать и всё будет очень быстро.

                              По сути вы не библиотеку для всех пишите (имею ввиду php-pg, php-redis и подобные), а я так понимаю часть бизнес логики. Вот как раз это, думаю, просто надо писать отдельно от php кода.
                                0
                                Если Вы имеете ввиду мой класс, то он не содержит ни грамма бизнес-логики. Это обычный шаблонизатор и его можно использовать в любом проекте. Потому, собсно, я и решил вынести его в модуль расширения.
                                  +1
                                  Да, тогда я с вами солидарен.
                                    0
                                    Но это не означает, что модуль не может, в принципе, решать задачи бизнес-логики. Но безусловно, чтобы перенести бизнес-логику в C/C++ нужны навыки программирования именно на этих языках, а не на PHP. Одно только ручное управление памятью чего стоит =)

                                    Между прочим, в коде на C доступно очень много функций, к которым все так привыкли в PHP. Они помечены сигнатурой PHPAPI в коде ZE. Есть вещи вроде php_var_dump, php_explode, php_stristr и т.д. Все с нуля писать не придется в случае чего.
                                      0
                                      Ну как-то я всё равно побаиваюсь самописные модули внедрять на работающие крупные проекты — как бы php я доверяю, а модули — это что-то за гранью добра и зла не особо распространенное, поэтому не хочется нервничать.

                                      А сорцы шаблонизатора вы выложили куда-нибудь?
                                        0
                                        Пока еще нет. Есть некоторые мотивы. А вопрос доверия достаточно спорный — мой стаж программирования на C++ в несколько раз превышает стаж программирования на PHP. Так что своего коду на C++ я доверяю больше, например.
                    0
                    Я писал вручную модуль для PHP на Си где-то в 2006 году, но тогда у SWIG не было поддержки PHP. А какой смысл делать это сейчас?
                      0
                      Такой, что, когда PHP класс, который нужно портировать, активно работает с окружением PHP. Например, как организовать доступ к $GLOBALS в коде на C++ с использованием SWIG? Там есть какие-то бешеные примеры с мешаниной сишного и PHP кода в одном файле, но это как-то уж слишком.
                      Я смотрел первое время в сторону SWIG. Не вышло. Хотя, возможно, я просто не умею его готовить.
                        0
                        Ох, что-то у меня с родным языком сегодня проблемы… Какой уж тут PHP =)
                      +2
                      Спасибо большое за статью, я как раз везде искал информацию об этом, но в основном крохи находил. Надеюсь скоро будет продолжение.
                        +1
                        В институте я писал курсовую, которая была посвящена этой теме — созданию расширений для PHP на C++. Я тогда писал модуль для линейного программирования, который экспортировал в userspace одну функцию — для решения задачи симплекс-методом.

                        Очень жаль, что тогда не было такой статьи. Я собирал информацию по кусочкам, многого не понял и оставил многое не доведенным до правильного состояния. Очень интересная тема.
                          0
                          Интересно. А что за расширение-то писали?
                          +2
                          Спасибо огромное. С нетерпением жду следующую часть.

                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                          Самое читаемое