Сериализация целочисленных массивов Judy в PHP

от автора

Как известно, переменная в PHP это не только ценные данные, а целый контейнер zval. А массив таких переменных занимает в памяти чересчур много места.
В PHP есть реализация Judy массивов — быстрых, ассоциативных и потребляющих на порядок меньше памяти. Но, к сожалению, методов сериализации\десериализации для них не существует.

Меня интересовала сериализация только целочисленных массивов, т.е. как и индекс массива, так и его значение только целые числа. Её я и реализовал, добавив необходимые методы в исходный код расширения. Принцип сериализации прост:
Массив ( 200 => 0, 300 => -5000, -100 =>2000 ) превращается в строку
"+c8+0+12c-1388-64+7d0"

Есть два места, где можно взять исходники:

Было выбрано второе. Все изменения сделаны в php_judy.c
Жирным шрифтом показаны добавленные строки.

  1. Заявляем поддержку сериализации:
    /* 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;
  2. Объявляем методы:
    PHP_METHOD(judy, memoryUsage);
    PHP_METHOD(judy, serialize);
    PHP_METHOD(judy, unserialize);

    PHP_METHOD(judy, count);
  3. Входные параметры для unserialize:
    ZEND_BEGIN_ARG_INFO_EX(arginfo_judy_unserialize, 0, 0, 1)     ZEND_ARG_INFO(0,str) ZEND_END_ARG_INFO() 

  4. Описания видимых методов:
    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)
  5. Сами методы и одна вспомогательная функция:
    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; } 

  6. Компилируем:
    phpize ./configure make make install 

  7. Не забываем прописать в 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 

Пара замечаний
  1. Конечно, Вы может изменить алгоритм выделения памяти под строку с сериализованными данными.
  2. На данный момент в реализации итератора 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *