Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

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

Anna | 31.05.2014 | нет комментариев
Как вестимо, переменная в 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]». При желании он легко исправляется.

P.S. Данный пост был написан огромнее года назад — в сентябре 2012 и всё это время валялся в песочнице. Как я теперь узнал, на гитхабе реализовали сериализацию в отдельной ветке 3 месяца назад.

So I went ahead and pretty much copied the implementation from the Habr, but dropped negative keys support in the process

.
github.com/orieg/php-judy/issues/13
github.com/tony2001/php-judy/tree/serialize

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB