Реализация классов в PHP5 экстеншене Часть 4 (заключительная), приложение

Ирокез

бессмертный пони
Команда форума
Партнер клуба
Реализация классов в PHP5 экстеншене Часть 4 (заключительная), приложение

Ну, вот, наверно подошла очередь собрать все вместе (или хотя-бы отдельные части) и написать, что-то похожее на функциональный экстеншен.
Сейчас я работаю над созданием, экстеншена, для кеширования zval объектов (аля memcache) посему, немножко давнгрейдив код, разберем, как реализовать кэш с минимальными усилиями и небольшим функционалом.

Исходный код

Основное положения экстеншена:
- реализация паттерна singleton
- перегрузка 'magic' методов __get,__set,__isset,__unset
- работа с api HashTable
- сериализация zval

Проект состоит из основных файлов:
- php_cache.c - тут определены функции и структуры, самого экстеншена (особого вимиания он не заслуживает :) )
- cache_class.c - собственно это определения методов класса, таблицы функций и есть одно интересное понятие, которое не использовалось в предыдущих частях, а именно, определение параметров метода (это особенно актуально, когда вы перегружаете какой-нибудь метод)

PHP:
...
static
ZEND_BEGIN_ARG_INFO_EX(cache_arg_get, 0, 0, 1)
ZEND_ARG_INFO(0, param_name)
ZEND_END_ARG_INFO() 

static
ZEND_BEGIN_ARG_INFO_EX(cache_arg_set, 0, 0, 2)
ZEND_ARG_INFO(0, param_name)
ZEND_ARG_INFO(0, param_value)
ZEND_END_ARG_INFO() 
...
Собственно, если посмотреть, исходники zend_api.h, то кричащие названия параметров ZEND_BEGIN_ARG_INFO_EX, говорят сами за себя, но на всякий случай поясню (по крайней мере как я понял и что понял, надеюсь меня поправят, если что :) )
name - имя которое в дальнейшие будет использоваться в том числе и добавлении метода, в таблицу методов класса,
pass_rest_by_reference - (поправьте плиз )
return_reference - результат будет возвращен как ссылка
required_num_args - количество обязательных аргументов
Далее ZEND_ARG_INFO, тут все просто, первый параметр, аргумент является ссылка на объект, второй параметр имя аргумента.

Следующая остановка, это реализация паттерна singleton:

Первая его часть, определение статического свойства класса
PHP:
int cache_class_register(TSRMLS_D)
{
	...

        zend_declare_property_null(g_ClassEntry,"__classInstanace",16,ZEND_ACC_PRIVATE|ZEND_ACC_STATIC TSRMLS_CC);
	
	...
}
Вторая часть, определение статического метода instance в таблице методов класса
PHP:
zend_function_entry class_methods[] = {
    ...
    PHP_ME(Cache,instance,NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
    ...
};
Третья часть, определение самого метода instance
PHP:
PHP_METHOD(Cache, instance)
{
    zval*   __classInstance;

    __classInstance = zend_read_static_property(g_ClassEntry,"__classInstanace",16,1 TSRMLS_CC);

    if(Z_TYPE_P(__classInstance) == IS_NULL)
    {
	// Впринципе понятно, что если объект не создан, то создаем его
        Z_TYPE_P(return_value) = IS_OBJECT;
        object_init_ex(return_value,g_ClassEntry);
        return_value->refcount = 1;
        return_value->is_ref = 1;
        zend_update_static_property(g_ClassEntry,"__classInstanace",16,return_value TSRMLS_CC);
        return;
    }

    RETURN_ZVAL(__classInstance,NULL,NULL);
}
Четвертую часть, переопределение __clone, я выкинул (это-же лишь пример реализации :) )

Собственно, что нас еще интересует в cache_class.c, так это таблица методов, где мы перегружаем "magic" методы.
PHP:
zend_function_entry class_methods[] = {
    ...
    PHP_ME(Cache,__set,cache_arg_set, ZEND_ACC_PUBLIC|ZEND_ACC_PROTECTED)
    PHP_ME(Cache,__get,cache_arg_get, ZEND_ACC_PUBLIC|ZEND_ACC_PROTECTED)
    PHP_ME(Cache,__isset,cache_arg_get, ZEND_ACC_PUBLIC|ZEND_ACC_PROTECTED)
    PHP_ME(Cache,__unset,cache_arg_get, ZEND_ACC_PUBLIC|ZEND_ACC_PROTECTED)
    ...
};
Собственно осталось посмотреть как определены сами методы класса cache_class.c.

- cache_ctrl - тут определены функции для работы с кешем (cache_init,cache_destroy,cache_get,cache_set,cache_isset,cache_unset)

Остановимся на основном:

Инициализация HashTable, интересным здесь является, определения callback функции cache_zval_dtor, которая будет вызываться при удалении объект из HashTable, и предпоследний параметр "1", говорит о том, что память будет выделяться не с помощью emalloc, а malloc, что для нас очень существенно, потому как, нам надо хранить переменные между запросами в противном случае уборщик мусора, вежливо удалит, все что мы на создавали (segmentation fault обеспечен )
PHP:
int cache_init(int default_cache_size TSRMLS_DC)
{
    return zend_hash_init_ex(CO, default_cache_size, NULL, (dtor_func_t)cache_zval_dtor, 1, 0);
}
и как бы основное, чтобы долго не мучатся, сохранением zval, все сложные типы array, object, просто сериализуем при записи и ансериалайзим при обращении.

Сериализация вообще-то проста, а вот при восстановлении не обошлось без казусов
PHP:
static inline zval* cache_zval_serialize(zval *pZval TSRMLS_DC)
{
    ...
    default:
        {
            php_serialize_data_t value_hash;
            smart_str            string = {0};

            PHP_VAR_SERIALIZE_INIT(value_hash);
            php_var_serialize(&string, &pZval, &value_hash TSRMLS_CC);
            PHP_VAR_SERIALIZE_DESTROY(value_hash);

            if (string.c == NULL || string.len == 0) 
                return NULL;

            Z_STRLEN_P(_res) = string.len;
            Z_STRVAL_P(_res) = (char *)cache_malloc(Z_STRLEN_P(_res));
            cache_cpy(Z_STRVAL_P(_res),string.c,Z_STRLEN_P(_res));
        }

        break;
    ...
}
изначально было вот так:
PHP:
static inline zval* cache_zval_unserialize(zval *pZval TSRMLS_DC)
{
    default:
        {
            php_unserialize_data_t var_hash;

            PHP_VAR_UNSERIALIZE_INIT(var_hash);
            if (!php_var_unserialize(&_res, &Z_STRVAL_P(pZval), Z_STRVAL_P(pZval) + Z_STRLEN_P(pZval), &var_hash TSRMLS_CC)) 
            {
                PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
                efree(_res);
            }

            PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
        }
        break;
}
а потом стало:
PHP:
static inline zval* cache_zval_unserialize(zval *pZval TSRMLS_DC)
{
    default:
        {
            php_unserialize_data_t var_hash;
            char*                  unserialize_begin = Z_STRVAL_P(pZval); 
            char*                  unserialize_end = unserialize_begin + Z_STRLEN_P(pZval); 

            PHP_VAR_UNSERIALIZE_INIT(var_hash);
            if (!php_var_unserialize(&_res, &unserialize_begin, unserialize_end, &var_hash TSRMLS_CC)) 
            {
                PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
                efree(_res);
            }

            PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
        }
        break;
}
Отличия собственно не существенные, но если кто попробует первый вариант, то увидит, что после вызова php_var_unserialize, указатель Z_STRVAL_P(pZval), станет на конец строки и при следующем обращении, начнет ансерелайзить мусор :)

Вот и все!

Спасибо всем кто читал, может хоть кому-то пригодится.

PS: данный экстеншен можно брать только за основу, не более, он оставляет большое поле для деятельности (синхронизация и прочее).

Исходники, и откомпилированная dll для Windows (php 5.2.4) в комплекте (см. вверху).
 

Alexandre

PHPПенсионер
еще раз Спасибо!

а как пробежаться по всем элементам hashtable не зная ключей?
 

Ирокез

бессмертный пони
Команда форума
Партнер клуба
Код:
zend_hash_internal_pointer_reset(ht);

while(zend_hash_get_current_data(ht,...) == SUCCESS)
{
...
zend_hash_move_forward(ht);
... 
}
 

tony2001

TeaM PHPClub
Код:
for(zend_hash_internal_pointer_reset(ht); zend_hash_get_current_data(ht,...) == SUCCESS; zend_hash_move_forward(ht)) {}
имо так красивее.
 

atv

Новичок
А вопрос о том, чтобы выложить на FAQ или как статью решился? А то потом затеряется в глубинах форума. Лучше, всё-таки, чтоб на видном месте :)
 

Ирокез

бессмертный пони
Команда форума
Партнер клуба
atv
Ну nw обещался в phpinside опубликовать, если нет, то оформлю для FAQ или в статьи, если админы запостят
 

ONK

Пассивист PHPСluba
Ирокез, Жду с нетерпением пятницы ;)
Интересно, на сколько это будет быстрее работать чем memcache?.
 

Ирокез

бессмертный пони
Команда форума
Партнер клуба
ONK
Производительность не измерял :) , но по исходникам memcache, видно, что он все zval сериализует (целые, дробные и т.д.), у меня же, сериализуются, только массивы (в extended версии не будут сериализоваться) и объекты. Что скорее всего позитивно скажется на производительности. Есть еще пару задумок, как не продублировать мемкэш :)

Прогноз оптимистичен, но практика иногда рушит, даже самые оптимистичные прогнозы, так-что жду, пятницы :)
 

tony2001

TeaM PHPClub
>Сейчас я работаю над созданием, экстеншена, для кеширования zval объектов
APC ?
[m]apc_store[/m]
 

Ирокез

бессмертный пони
Команда форума
Партнер клуба
Автор оригинала: tony2001
>Сейчас я работаю над созданием, экстеншена, для кеширования zval объектов
APC ?
[m]apc_store[/m]
Ну хочется внести, что-то своё. Поддержка, к примеру межсерверного кеширования через БД, поддержка кеширования запросов к БД (это только мысли на вскидку).
 

tony2001

TeaM PHPClub
>Поддержка, к примеру межсерверного кеширования через БД

это же memcache.

>поддержка кеширования запросов к БД (это только мысли на вскидку).

а это просто прослойка в userspace - сначала спрашиваем memcache (или APC), потом базу..
я просто толку не вижу в изобретении нового велосипеда при наличии достаточного кол-ва старых.
 

Ирокез

бессмертный пони
Команда форума
Партнер клуба
tony2001
Возможно и велосипед, но собственный :). Стоять на месте нет никакого желания (чтобы найти русло, в котором можно двигаться, надо изобрести ни один велосипед, нет ведь дяди, который говорит, куда надо грести :) ).

Через прослойку теряется часть, тех бесценных тактов процессора, которые позволяют заработать лишний "центик".

memcache - работает, как сервис, что не всегда приемлемо, к примеру при прохождении сертификации PCI DSS, необходимо указывать что за сервис, зачем он слушает порт и можно-ли без него (честно, я глубоко не знаю, всех тонкостей memcache, а настраивать, компилировать его, выкачивая тонны библиотек тяжело)

apc - не заточен для межсерверного кеширования, юзерспейс прослойка, потеря производительности.

Вообщем-то это статья, не более чем пример.

Мечта у меня поучаствовать в разработке PHP Seven, вот и набираюсь сил :) (шутю)
 

tony2001

TeaM PHPClub
>честно, я глубоко не знаю, всех тонкостей memcache
там кода - копейки..

>а настраивать
да нет у него никаких настроек почти =)

>компилировать его, выкачивая тонны библиотек тяжело
ага. судя по всему, ты даже не смотрел =)

>apc - не заточен для межсерверного кеширования
всё верно.
именно поэтому он может не сериализовывать простые переменные.

а как ты думал хранить сложные структуры, при этом их не сериализуя?
 

Ирокез

бессмертный пони
Команда форума
Партнер клуба
>там кода - копейки..
165 кб чистых исходников )

>ага. судя по всему, ты даже не смотрел =)
не компилировал, это точнее :)

>apc - не заточен для межсерверного кеширования
всё верно.
именно поэтому он может не сериализовывать простые переменные.

а как ты думал хранить сложные структуры, при этом их не сериализуя?

Только сериализация, но к примеру для экономии памяти и увеличения производительности, в бинарном виде (да, необходимо будет учитывать little-endian, big-endian).

Ну нравиться мне писать экстеншены для PHP :D
 

tony2001

TeaM PHPClub
>>там кода - копейки..
>165 кб чистых исходников )

сравни с PHP =)

>Только сериализация, но к примеру для экономии памяти и увеличения
>производительности, в бинарном виде (да, необходимо будет учитывать little-
>endian, big-endian).

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

>Ну нравиться мне писать экстеншены для PHP

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

например, нет желания присоединиться к разработке экстеншена для Sphinx?
 

atv

Новичок
Ну нравиться мне писать экстеншены для PHP
У меня тоже есть предложение, правда поскромнее :)

Есть полезное расширение runkit. Оно сейчас заброшено, много незакрытых багов ещё с 2005 года. Спрос на него есть, а вот поддерживать некому. Может тебя заинтересует работа над ним.
 

atv

Новичок
Автор оригинала: tony2001
runkit - это из разряда извращений, которые не надо хотеть.
Тем неменее, я нашёл ему очень полезное применение :), а именно - Аспектно-Ориентированное Программирование, причём с возможностью динамического (в рантайм) включения и выключения.
 
Сверху