Как известно, переменная в PHP это не только ценные данные, а целый контейнер zval. А массив таких переменных занимает в памяти чересчур много места.
В PHP есть реализация Judy массивов — быстрых, ассоциативных и потребляющих на порядок меньше памяти. Но, к сожалению, методов сериализации\десериализации для них не существует.
Меня интересовала сериализация только целочисленных массивов, т.е. как и индекс массива, так и его значение только целые числа. Её я и реализовал, добавив необходимые методы в исходный код расширения. Принцип сериализации прост:
Массив ( 200 => 0, 300 => -5000, -100 =>2000 ) превращается в строку
"+c8+0+12c-1388-64+7d0"
В PHP есть реализация Judy массивов — быстрых, ассоциативных и потребляющих на порядок меньше памяти. Но, к сожалению, методов сериализации\десериализации для них не существует.
Меня интересовала сериализация только целочисленных массивов, т.е. как и индекс массива, так и его значение только целые числа. Её я и реализовал, добавив необходимые методы в исходный код расширения. Принцип сериализации прост:
Массив ( 200 => 0, 300 => -5000, -100 =>2000 ) превращается в строку
"+c8+0+12c-1388-64+7d0"
Есть два места, где можно взять исходники:
Было выбрано второе. Все изменения сделаны в php_judy.c
Жирным шрифтом показаны добавленные строки.
- Заявляем поддержку сериализации:
/* implements some interface to provide access to judy object as an array */ zend_class_implements(judy_ce TSRMLS_CC, 1, zend_ce_arrayaccess, zend_ce_iterator);
zend_class_implements(judy_ce TSRMLS_CC, 1, zend_ce_serializable);
judy_ce->ce_flags |= ZEND_ACC_FINAL_CLASS;
- Объявляем методы:
PHP_METHOD(judy, memoryUsage);
PHP_METHOD(judy, serialize);
PHP_METHOD(judy, unserialize);
PHP_METHOD(judy, count);
- Входные параметры для unserialize:
ZEND_BEGIN_ARG_INFO_EX(arginfo_judy_unserialize, 0, 0, 1) ZEND_ARG_INFO(0,str) ZEND_END_ARG_INFO()
- Описания видимых методов:
PHP_ME(judy, memoryUsage, NULL, ZEND_ACC_PUBLIC)
PHP_ME(judy, serialize, NULL, ZEND_ACC_PUBLIC)
PHP_ME(judy, unserialize, arginfo_judy_unserialize, ZEND_ACC_PUBLIC)
PHP_ME(judy, count, arginfo_judy_count, ZEND_ACC_PUBLIC)
- Сами методы и одна вспомогательная функция:
PHP_METHOD(judy, serialize) { JUDY_METHOD_GET_OBJECT if ( intern->type == TYPE_INT_TO_INT ) { long int index = 0, i = 0, l; long int * PValue; char * s; Word_t idx1 = 0, idx2 = -1, Rc_word; JLC(Rc_word, intern->array, idx1, idx2); l = 64 + ( Rc_word << 3 ); s = emalloc ( l ); if ( s == NULL ) RETURN_NULL(); JLF ( PValue, intern->array, index ); while ( PValue != NULL && PValue != PJERR ) { JLG ( PValue, intern->array, index ); if ( index < 0 ) { if ( *PValue < 0 ) sprintf ( s+i, "-%x-%x", -index, -*PValue ); else sprintf ( s+i, "-%x+%x", -index, *PValue ); } else { if ( *PValue < 0 ) sprintf ( s+i, "+%x-%x", index, -*PValue ); else sprintf ( s+i, "+%x+%x", index, *PValue ); } i += strlen ( s+i ); if ( i > l - 50 ) { l <<= 1; s = erealloc ( s, l ); if ( s == NULL ) RETURN_NULL(); } JLN ( PValue, intern->array, index ); } *(s+i) = 0; RETURN_STRING ( s, 0 ); } RETURN_NULL(); } long int Hex2Int ( char * s, long int * j, long int l ) { long int v = 0; char sym; if ( s[*j] == '+' ) { (*j)++; sym = s[*j]; while ( sym != '+' && sym != '-' && *j < l ) { v <<= 4; if (( sym >= '0' ) && ( sym <= '9' )) v += ( sym - '0' ); else v += ( sym - 'a' + 10 ); (*j)++; sym = s[*j]; } } else { (*j)++; sym = s[*j]; while ( sym != '+' && sym != '-' && *j < l ) { v <<= 4; if (( sym >= '0' ) && ( sym <= '9' )) v -= ( sym - '0' ); else v -= ( sym - 'a' + 10 ); (*j)++; sym = s[*j]; } } return v; } PHP_METHOD(judy, unserialize) { JUDY_METHOD_GET_OBJECT intern->counter = 0; intern->type = TYPE_INT_TO_INT; intern->array = (Pvoid_t) NULL; char * s; long int l, j = 0; Word_t * PValue, index, value; if ( zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &s, &l) == FAILURE) return; if ( l < 4 ) return; while ( j < l ) { index = Hex2Int ( s, &j, l ); value = Hex2Int ( s, &j, l ); JLI ( PValue, intern->array, index ); if ( PValue == NULL || PValue == PJERR ) return; *PValue = value; } return; }
- Компилируем:
phpize ./configure make make install
- Не забываем прописать в php.ini
extension=judy.so
Небольшой тест\бенчмарк
<?php function InitArray ( &$ar, $init_srand, $max ) { srand ( $init_srand ); for ( $i = -$max; $i < $max; $i++ ) { $index = rand ( -$max, $max ); $value = rand ( -1000, 1000 ); $ar[$index] = $value; } } function ShowTime ( $st, $text ) { printf ( "$text in %.2f sec.\n", microtime ( true ) - $st ); } $init_srand = time (); $max = 500000; echo "srand = $init_srand, max = $max\n"; $init_mem = memory_get_usage(); $st = microtime ( true ); $a = array (); InitArray ( $a, $init_srand, $max ); ShowTime ( $st, "Initialized std. array" ); echo "Elements in std. array: " , count ( $a ) , "\n"; echo "Used memory: " , (memory_get_usage()-$init_mem) , " bytes\n\n"; $st = microtime ( true ); $j = new Judy(Judy::INT_TO_INT); InitArray ( $j, $init_srand, $max ); ShowTime ( $st, "Initialized Judy array" ); echo "Elements in Judy array: " , count ( $j ) , "\n"; echo "Used memory: " , $j->memoryUsage() , " bytes\n\n"; $st = microtime ( true ); $a_ser = serialize ( $a ); ShowTime ( $st, "Serialized std. array" ); $st = microtime ( true ); $j_ser = serialize ( $j ); ShowTime ( $st, "Serialized Judy array" ); echo "Serialized std. length: " , strlen ( $a_ser ) , "\n"; echo "Serialized Judy length: " , strlen ( $j_ser ) , "\n\n"; $st = microtime ( true ); $a_unser = unserialize ( $a_ser ); ShowTime ( $st, "Unserialized std. array" ); $st = microtime ( true ); $j_unser = unserialize ( $j_ser ); ShowTime ( $st, "Unserialized Judy array" ); if (count ( $j_unser ) != count ( $a )) die ( "ERROR: arrays size mismatch\n" ); foreach ( $a as $index => $value ) { if ( $j_unser[$index] != $value ) { die ( "ERROR: arrays data mismatch\n" ); } } echo "OK: std. array and unserialized Judy array are identical\n"; ?>
На выходе получаем
srand = 1348822174, max = 500000 Initialized std. array in 0.76 sec. Elements in std. array: 632067 Used memory: 49703784 bytes Initialized Judy array in 0.98 sec. Elements in Judy array: 632067 Used memory: 3247108 bytes Serialized std. array in 0.33 sec. Serialized Judy array in 0.18 sec. Serialized std. length: 9904186 Serialized Judy length: 6061744 Unserialized std. array in 0.13 sec. Unserialized Judy array in 0.08 sec. OK: std. array and unserialized Judy array are identical
Пара замечаний
- Конечно, Вы может изменить алгоритм выделения памяти под строку с сериализованными данными.
- На данный момент в реализации итератора foreach для Judy есть баг:
Если в массиве одновременно присутствуют индексы 0 и -1, то foreach входит в бесконечный цикл
<?php $j = new Judy(Judy::INT_TO_INT); $j[0] = 456; $j[-1] = 123; foreach ( $j as $index => $value ) { echo "$index $value\n"; } ?>
Был создан запрос «foreach [0] [-1]». При желании он легко исправляется.
ссылка на оригинал статьи http://habrahabr.ru/post/200246/
Добавить комментарий