PHP拡張。 TraversableとArrayAccessを使用した単純な配列の作成

この記事では、単純な配列の例を使用して、内部TraversableおよびArrayAccessインターフェースが正確に機能する方法を検討することを提案します。



多くのリンクが続くリソースのリストを提供します。



プラットフォームについて:ubuntuでコードを書いたので、他のLinuxディストリビューション(およびOS X)では、最小限の変更(apt-getの変更)が必要になります。 Windowsで記述したい場合は、他のインターネットで情報を検索する必要があります(Windowsでコードを記述する人はいません )。







PHPをビルドする



はじめに、デバッグバージョンのPHPをビルドしましょう。 もちろん、通常のバージョンで拡張機能を作成することもできますが、デバッグフラグを2、3回使用すると、PHPの話がもっとおしゃべりになります。



コンソールを開き、PHPソースをプルするディレクトリ(〜/ dev / c /など)に移動し、gitリポジトリからphpコードを取得します。

git clone http://git.php.net/repository/php-src.git cd php-src
      
      





新しいブランチに切り替えます。

 git checkout PHP-5.6
      
      





PHPのビルドに必要なプログラムをインストールします(ほとんどの場合、すでにそれらを持っています)。

 sudo apt-get install build-essential autoconf automake libtool
      
      





今では、bisonをインストールします。 バージョン14以降のubuntuでは、bisonはバージョン3以上になりますが、PHPはこれを消化しません。 バージョン2.7が必要です。

 wget http://launchpadlibrarian.net/140087283/libbison-dev_2.7.1.dfsg-1_amd64.deb wget http://launchpadlibrarian.net/140087282/bison_2.7.1.dfsg-1_amd64.deb sudo dpkg -i libbison-dev_2.7.1.dfsg-1_amd64.deb sudo dpkg -i bison_2.7.1.dfsg-1_amd64.deb
      
      







デフォルトで拡張機能なしのバージョンを構築するため、libxml2は必要ありません。 それ以外の場合は、libxml2-devをインストールする必要があります。

 sudo apt-get install libxml2-dev
      
      





設定して、拡張機能なしでデバッグバージョンが必要であることを示します。 --prefixパラメーターで、PHPがインストールされるディレクトリを指定します。

 ./buildconf ./configure --disable-all --enable-debug --prefix=$HOME/dev/bin/php make && make install
      
      







オーケ、PHPの準備ができました。 -vフラグを使用して、新しく収集したphpを実行し、必要なものと必要な場所を収集したことを確認します(ただし、あなたは決して知りません)。

 ~/dev/bin/php/bin/php -v
      
      







拡張機能を置く



拡張機能の「スケルトン」は、PHPソースのあるディレクトリにあるext_skelを使用して迅速に生成できます。 ext_skelを拒否します。便利な.gitignoreに加えて、何百もの不要なコメントをファイルに詰め込むからです。 .gitignoreはここで取得できます



ext_skelが本当に必要な場合は、次のパラメーターを使用して実行する必要があります。拡張機能の名前は--extnameで指定し、スケルトンフォルダーへのパスは--skelで指定します。

 ~/dev/c/php-src/ext/ext_skel --extname=jco --skel=$HOME/dev/c/php-src/ext/skeleton/
      
      







いずれにしても、次のファイルを含むディレクトリを取得する必要があります。

 jco/ .gitignore config.m4 config.w32 jco.c php_jco.h
      
      







config.m4を開き、次を記述します。

 if test "$PHP_JCO" = "yes"; then AC_DEFINE(HAVE_JCO, 1, [Whether you have Jco]) PHP_NEW_EXTENSION(jco, jco.c, $ext_shared) fi
      
      





config.m4でさらにすべて、PHP_NEW_EXTENSIONの行のみに触れ、そこに新しいファイルを追加します。



次に、拡張機能のメインヘッダーファイルphp_jco.hを作成します。 php_%extension name%.hという名前にする必要があります

 #ifndef PHP_JCO_H #define PHP_JCO_H 1 extern zend_module_entry jco_module_entry; #define phpext_jco_ptr &jco_module_entry //   - ,     . #ifdef ZTS #include "TSRM.h" #endif #endif
      
      





このファイルでは、拡張に関する情報を含むzend_module_entry型の変数を宣言します。 変数名は、 %% extension name%_module_entryの形式である必要があります



jco.cを開き、次の内容を書き込みます。

jco.c
 #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "php.h" #include "php_jco.h" //    PHP_FUNCTION(hello_from_jco) { //   ,       . RETURN_STRING("JCO ENABLED! YEY!", 1); } //  PHP    ,      . const zend_function_entry jco_functions[] = { PHP_FE(hello_from_jco, NULL) PHP_FE_END }; //  ,  php       PHP_MINIT_FUNCTION(jco_init) { return SUCCESS; } zend_module_entry jco_module_entry = { STANDARD_MODULE_HEADER, "jco", //   jco_functions, PHP_MINIT(jco_init), NULL, // MSHUTDOWN NULL, // RINIT NULL, // RSHUTDOWN NULL, // MINFO "0.1", //  STANDARD_MODULE_PROPERTIES }; #ifdef COMPILE_DL_JCO ZEND_GET_MODULE(jco) #endif
      
      







ここでの主なことは、モジュールに関する情報を持つ変数の定義です。ここでは、関数のテーブル、開始関数、およびその他の必要なデータを示しました(またはNULLを置き換えて指定しませんでした)。 ZEND_GET_MODULEは、ライブラリ用のget_module関数を作成するだけで、変数jco_module_entryを返します。



これで、拡張機能を収集する準備が整いました。 phpizeを実行します。これにより、拡張機能ピッカーの構成ビルダーの構成が作成されます(さらに深くする必要があります!)

 ~/dev/bin/php/bin/phpize
      
      





そして、拡張機能を収集します。 --with-php-configパラメーターで、デバッグバージョンのPHPのphp-configファイルへのパスを指定します

 ./configure --with-php-config=$HOME/dev/bin/php/bin/php-config make && make install
      
      





すべてがエラーなしでうまくいった場合は、拡張子を付けてphpを実行します(そうでない場合は、編集して実行します)。

 ~/dev/bin/php/bin/php -dextension=jco.so --r "hello_from_jco();" JCO ENABLED! YEY!
      
      







zvalと関数について簡単に説明します



クラスに移る前に、関数と変数を操作するためにPHPが提供するものを簡単に確認します。



関数を宣言するには、マクロPHP_FUNCTION、PHP_NAMED_FUNCTION、またはPHP_METHODを使用します。 それらは、受け取った関数の名前のみが異なります。

 void prefix_name(int ht, zval *return_value, zval **return_value_ptr, zval *this_ptr, int return_value_used void ***tsrm_ls)
      
      





どこで





関数の引数は、マクロZEND_ARG_INFO_ *を使用して定義されます。

 // , _,   ,    ZEND_BEGIN_ARG_INFO_EX(arginfo_construct, 0, 0, 1) //   ,   ZEND_ARG_INFO(0, var1) ZEND_ARG_INFO(0, var2) ZEND_END_ARG_INFO() /*    static const zend_arg_info arginfo_construct[] = { { NULL, 0, NULL, 2, 0, 0, 0, 0 }, { "var1", sizeof("var1")-1, NULL, 0, 0, 0, 0, 0 }, { "var2", sizeof("var2")-1, NULL, 0, 0, 0, 0, 0 }, } */
      
      





ZEND_BEGIN_ARG_INFO_EXZEND_ARG_INFOおよびZEND_END_ARG_INFOは、 zend_arg_info構造体の配列になります。 さらに、配列の最初の要素はzend_internal_function_info型にキャストされます。 フィールドの数とタイプは同じで、名前のみが異なります。



さらに、makos PHP_FE、PHP_ME、PHP_ME_MAPPINGを使用した関数は、 zend_function_entryタイプの要素を持つモジュール/クラスの関数の表にリストされています。

 typedef struct _zend_function_entry { const char *fname; //     PHP void (*handler)(INTERNAL_FUNCTION_PARAMETERS); //   const struct _zend_arg_info *arg_info; //    zend_uint num_args; //   ( ,    arg_info) zend_uint flags; //   } zend_function_entry
      
      





モジュールを登録すると、関数はグローバル関数テーブル(function_table)に入力されます。 クラスを登録するとき-クラス関数のテーブル内。



引数を取得するには、次のパラメーターで呼び出されるzend_parse_parameters関数を使用します。



zend_parse_parametersについてはこちらをご覧ください



PHPは変数を操作するために、値自体をzvalue_value 、型、参照カウンター、および変数が参照によって使用されていることを示すフラグに保存するzvalを使用します。

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





zvalにメモリを割り当てるには、マクロALLOC_ZVAL (メモリの割り当てのみ)、 MAKE_STD_ZVAL (ALLOC_ZVAL +値の初期化)などを使用する必要があります。



これは、zvalの代わりにALLOC_ZVALが_zval_gc_infoにメモリを割り当てるためです 。これは、循環リンクを検索するための情報をさらに格納します。



zvalを削除するには、 zval_ptr_dtor関数を使用します。 zval_dtorとは異なり、zval_ptr_dtorは最初に参照カウントを減らし、カウンターがゼロになった場合にのみzvalを削除します。



また、すべての値のzvalue_valueは、数値がポインターを格納するよりも複雑であることを考慮する価値があります。 したがって、メモリ内の同じ行に対する2つのzval参照がある場合、それらの1つを削除すると、2番目の参照はすでにメモリの誤ったセクションを参照します。



zvalの詳細を読むphpintenralsbookで読むことができます。 PHPマニュアルの循環リンクについて。



クラス



拡張機能に戻り、最初のクラスを追加しましょう。 jco_darray.hファイルを作成し、そこに以下を記述します。

 #ifndef PHP_JCO_DARRAY_H #define PHP_JCO_DARRAY_H 1 extern zend_class_entry *jco_darray_ce; void jco_darray_init(TSRMLS_D); #endif
      
      





ここでは、zend_class_entry型のjco_darray_ce変数と、クラス用に初期化する関数を宣言しました。



次に、jco_darray.cファイルを作成します。

jco_darray.c
 #include "php.h" #include "jco_darray.h" zend_class_entry *jco_darray_ce; PHP_METHOD(jco_darray, sayHello) { RETURN_STRING("Hello from darray!", 1); } const zend_function_entry jco_darray_functions[] = { //  ,  , arginfo,  PHP_ME(jco_darray, sayHello, NULL, ZEND_ACC_PUBLIC) PHP_FE_END }; void jco_darray_init(TSRMLS_D) { zend_class_entry tmp_ce; INIT_CLASS_ENTRY(tmp_ce, "JCO\\DArray", jco_darray_functions); jco_darray_ce = zend_register_internal_class(&tmp_ce TSRMLS_CC); return; }
      
      







ここでは、jco_darray_init関数のみが興味深いものです。 まず、tmp_ceクラスの一時的な構造を作成し、INIT_CLASS_ENTRYを設定します。 マクロの2番目のパラメーターは、 namespaceを含む 、PHPから使用可能なクラス名を指定します



クラスzend_register_internal_classを使用して、クラスをクラステーブル(class_table)に登録します。



次に、jco_darray_init関数呼び出しをjco_init関数(jco.hファイル)に追加します。

 PHP_MINIT_FUNCTION(jco_init) { jco_darray_init(TSRMLS_C); return SUCCESS; }
      
      





そして、新しいjco_darray.cファイルをconfig.m4に追加します(ファイルのリストはコンマなしで指定されます)。

 PHP_NEW_EXTENSION(jco, jco.c jco_darray.c, $ext_shared)
      
      





config.m4を変更したため、phpizeを再度実行する必要があります

 ~/dev/bin/php/bin/phpize --clean ~/dev/bin/php/bin/phpize ./configure --with-php-config=$HOME/dev/bin/php/bin/php-config make && make install
      
      





拡張機能をテストするためのphpスクリプトを作成しましょう(元の名前はjco.phpとしましょう)

 <?php $darray = new \JCO\DArray(); echo $darray->sayHello() . PHP_EOL; ?>
      
      





そして、拡張機能を使用してスクリプトを実行します

 ~/dev/bin/php/bin/php -dextension=jco.so jco.php
      
      







ダイナミックのD



「こんにちは」と言う方法しかわからないクラスでは、遠くまで行けません。 特に、配列として意図されている場合。 この配列を取得して書き込む時間。



dsディレクトリを作成し、そこにdarray.hファイルを追加します。このファイルで、配列の構造と関数を宣言します。

ds / drray.h
 #ifndef PHP_JCO_DS_DARRAY_H #define PHP_JCO_DS_DARRAY_H 1 #include "php.h" typedef struct jco_ds_darray { size_t count; //  NULL  size_t length; //    size_t min_length; //    size_t capacity; //  -      void *elements; //   (zval) } jco_ds_darray; jco_ds_darray *jco_ds_darray_create(size_t size, size_t capacity); void jco_ds_darray_destroy(jco_ds_darray *array); #define jco_ds_darray_length(array) ((array)->length) #define jco_ds_darray_min_length(array) ((array)->min_length) #define jco_ds_darray_capacity(array) ((array)->capacity) #define jco_ds_darray_count(array) ((array)->count) #define jco_ds_darray_first(array) ((zval *)(array)->elements) #endif
      
      







次に、ds / darray.cファイルで、上記で宣言した関数を定義します。 これまでのところ、これは構造の作成と削除のみです。

ds / darray.c
 #include "ds/darray.h" #include "php.h" jco_ds_darray *jco_ds_darray_create(size_t size, size_t capacity) { jco_ds_darray *array = emalloc(sizeof(jco_ds_darray)); if (!array) { return NULL; } array->count = 0; array->length = 0; array->min_length = size; array->capacity = capacity; array->elements = NULL; return array; } void jco_ds_darray_destroy(jco_ds_darray *array) { if (!array) { return; } efree(array); }
      
      







クラスと配列があり、何らかの方法でそれらを接続する必要があります。 これを行うために、phpがオブジェクトをどのように扱うかを明確にしましょう。



オブジェクトを変数(zval)に保存するには、次のフィールドを持つzend_object_value構造体を使用します。

 typedef struct _zend_object_value { zend_object_handle handle; const zend_object_handlers *handlers; } zend_object_value;
      
      







したがって、必要なのは、zend_objectを拡張する構造を与えることだけです。 これを行うには、create_object関数と、構造に割り当てられたメモリを解放する関数を作成します。 jco_darray_ce宣言の後に追加します。

jco_darray.c
 zend_object_handlers jco_darray_handlers; typedef struct jco_darray { zend_object std; jco_ds_darray *array; } jco_darray; static void jco_darray_free_object_storage(jco_darray *intern TSRMLS_DC) { zend_object_std_dtor(&intern->std TSRMLS_CC); if (intern->array) { jco_ds_darray_destroy(intern->array); } efree(intern); } zend_object_value jco_darray_create_object(zend_class_entry *class_type TSRMLS_DC) { zend_object_value retval; jco_darray *intern = emalloc(sizeof(jco_darray)); memset(intern, 0, sizeof(jco_darray)); // :         zend_object_std_init(&intern->std, class_type TSRMLS_CC); //     object_properties_init(&intern->std, class_type); //      (object_store) retval.handle = zend_objects_store_put( intern, (zend_objects_store_dtor_t) zend_objects_destroy_object, //    (zend_objects_free_object_storage_t) jco_darray_free_object_storage, //      NULL //  ,    TSRMLS_CC ); //     -   retval.handlers = &jco_darray_handlers; return retval; }
      
      







zend_objects_store_putは3つの関数を取ります:





jco_darray_init関数で、次の行を追加します

  //     zend_object jco_darray_ce->create_object = jco_darray_create_object; //     memcpy(&jco_darray_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
      
      





そして、配列はどこにありますか? そして、コンストラクターで配列を作成します。

jco_darray.c
 PHP_METHOD(jco_darray, __construct) { jco_darray *intern; long size = 0; long capacity = 0; zend_error_handling error_handling; //   ,          zend_replace_error_handling(EH_THROW, NULL, &error_handling TSRMLS_CC); //      (l - long),  -    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ll", &size, &capacity) == FAILURE) { zend_restore_error_handling(&error_handling TSRMLS_CC); return; } //     zend_restore_error_handling(&error_handling TSRMLS_CC); if (size <= 0) { zend_throw_exception(NULL, "Array size must be positive", 0 TSRMLS_CC); return; } if (capacity < 0) { zend_throw_exception(NULL, "Array capacity must be positive or 0", 0 TSRMLS_CC); return; } //    handle intern = zend_object_store_get_object(getThis() TSRMLS_CC); intern->array = jco_ds_darray_create((size_t)size, (size_t)capacity); if (!intern->array) { zend_throw_exception(NULL, "Failed to allocate array", 0 TSRMLS_CC); } return; }
      
      







__constructを関数テーブルに追加します。

 ZEND_BEGIN_ARG_INFO_EX(arginfo_construct, 0, 0, 1) //   ,   ZEND_ARG_INFO(0, size) ZEND_ARG_INFO(0, capacity) ZEND_END_ARG_INFO() const zend_function_entry jco_darray_functions[] = { PHP_ME(jco_darray, __construct, arginfo_construct, ZEND_ACC_PUBLIC) PHP_ME(jco_darray, sayHello, arginfo_void, ZEND_ACC_PUBLIC) PHP_FE_END };
      
      







拡張機能を構築し、すべてが正常にコンパイルされることを確認する時間です。 phpizeを起動して、config.m4の変更をキャッチします(これが最後であることを約束します)

 ~/dev/bin/php/bin/phpize --clean ~/dev/bin/php/bin/phpize ./configure --with-php-config=$HOME/dev/bin/php/bin/php-config make && make install
      
      





そして、テストスクリプトを実行します

 ~/dev/bin/php/bin/php -dextension=jco.so jco.php
      
      







配列アクセス



ds / darray.hファイルで、配列を操作するための関数宣言を追加します:get、set、unset(および同時にclone)。

 jco_ds_darray *jco_ds_darray_create(size_t size, size_t capacity); jco_ds_darray *jco_ds_darray_clone(jco_ds_darray *array); void jco_ds_darray_destroy(jco_ds_darray *array); zval *jco_ds_darray_get(jco_ds_darray *array, size_t index); zval *jco_ds_darray_set(jco_ds_darray *array, size_t index, zval *value); void jco_ds_darray_unset(jco_ds_darray *array, size_t index);
      
      





そして、これらの関数を書きます

ds / darray.s
  #include "ds/darray.h" #include "php.h" #define ELEM_SIZE (sizeof(zval)) //     . //   index,     capacity , //     index-  static inline int _jco_ds_darray_expand(jco_ds_darray *array, size_t index) { if (array && array->capacity > 0) { size_t capacity = array->capacity; size_t max_elements = array->length; size_t expand_count; if (index) { expand_count = ((index + 1) / capacity) * capacity + capacity; } else { expand_count = (max_elements + capacity); } zval *elements; if (max_elements == 0 && !array->elements) { elements = (zval *)emalloc(ELEM_SIZE * expand_count); } else { elements = (zval *)erealloc((void *)array->elements, ELEM_SIZE * expand_count); } if (elements) { zval *ptr = (elements + max_elements); memset(ptr, 0, array->capacity * ELEM_SIZE); array->length = expand_count; array->elements = elements; return 1; } return 0; } return 0; } jco_ds_darray *jco_ds_darray_create(size_t size, size_t capacity) { jco_ds_darray *array = emalloc(sizeof(jco_ds_darray)); if (!array) { return NULL; } array->length = 0; array->min_length = size; array->capacity = size; array->count = 0; array->elements = NULL; if (size > 0 && !_jco_ds_darray_expand(array, 0)) { efree(array); return NULL; } array->length = size; array->capacity = capacity; return array; } void jco_ds_darray_destroy(jco_ds_darray *array) { if (!array) { return; } if (array->length > 0) { zval *elem = (zval *)array->elements; while (array->length--) { if (elem != NULL && Z_REFCOUNT_P(elem) > 0) { zval_dtor(elem); } elem++; } } if (array->elements) { efree(array->elements); } efree(array); } jco_ds_darray *jco_ds_darray_clone(jco_ds_darray *array) { if (!array) { return NULL; } jco_ds_darray *new_array = emalloc(sizeof(jco_ds_darray)); if (!new_array) { return NULL; } new_array->count = array->count; new_array->length = array->length; new_array->min_length = array->min_length; new_array->capacity = array->capacity; new_array->elements = (zval *)emalloc(ELEM_SIZE * array->length); if (!new_array->elements) { efree(new_array); return NULL; } memcpy(new_array->elements, array->elements, ELEM_SIZE * array->length); //memcpy    zval`,         //        zval_copy_ctor size_t index; for (index = 0; index < array->length; index++) { zval *elem = (zval *)new_array->elements + index; if (elem != NULL && Z_REFCOUNT_P(elem) > 0) { zval_copy_ctor(elem); } } return new_array; } zval *jco_ds_darray_get(jco_ds_darray *array, size_t index) { if (!array || array->length < (index + 1)) { return NULL; } zval *elem = (zval *)(array->elements) + index; if (!elem || Z_TYPE_P(elem) == IS_NULL) { return NULL; } //   ,  is_ref__gc = 0 Z_UNSET_ISREF_P(elem); return elem; } void jco_ds_darray_unset(jco_ds_darray *array, size_t index) { if (!array || array->length < (index + 1)) { return; } zval *elem = (zval *)array->elements + index; if (elem != NULL && Z_REFCOUNT_P(elem) > 0) { if (Z_TYPE_P(elem) != IS_NULL) { array->count--; } zval_dtor(elem); *elem = (zval) {0}; } } zval *jco_ds_darray_set(jco_ds_darray *array, size_t index, zval *value) { if (!array) { return; } if ((index + 1) > array->length) { if (array->capacity == 0) { return NULL; } if (!_jco_ds_darray_expand(array, index)) { return NULL; } } zval *elem = (zval *)array->elements + index; int prev_is_not_null = 0; if (Z_REFCOUNT_P(elem) > 0 && Z_TYPE_P(elem)) { zval_dtor(elem); prev_is_not_null = 1; } elem->value = value->value; elem->type = value->type; elem->refcount__gc = 1; elem->is_ref__gc = 0; zval_copy_ctor(elem); if (prev_is_not_null && Z_TYPE_P(elem) == IS_NULL) { array->count--; } else if (!prev_is_not_null && Z_TYPE_P(elem) != IS_NULL) { array->count++; } return elem; }
      
      







ご覧のとおり、jco_ds_darray_set ALLOC_ZVALでは使用しませんでしたが、以前に割り当てられたメモリを使用しました。 この場合、要素の配列がメモリ内で連続していることが重要です。 さらに、配列要素をユーザーコードに直接提供しないため、GCは不要です。 したがって、zval_ptr_dtorの代わりにzval_dtorを使用して削除します。



次に、新しい機能を使用して、ArrayAccessインターフェイスを実装します。

jco_darray.c
  PHP_METHOD(jco_darray, count) { jco_darray *intern; long count; intern = zend_object_store_get_object(getThis() TSRMLS_CC); count = (long)jco_ds_darray_count(intern->array); ZVAL_LONG(return_value, count); } PHP_METHOD(jco_darray, length) { jco_darray *intern; long length; intern = zend_object_store_get_object(getThis() TSRMLS_CC); length = (long) jco_ds_darray_length(intern->array); ZVAL_LONG(return_value, length); } PHP_METHOD(jco_darray, offsetSet) { jco_darray *intern; zval *val; long index; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lz", &index, &val) == FAILURE) { zend_throw_exception(NULL, "Failed to parse arguments", 0 TSRMLS_CC); return; } intern = zend_object_store_get_object(getThis() TSRMLS_CC); jco_ds_darray_set(intern->array, (size_t)index, val); } PHP_METHOD(jco_darray, offsetUnset) { jco_darray *intern; long index; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &index) == FAILURE) { zend_throw_exception(NULL, "Invalid index passed", 0 TSRMLS_CC); return; } intern = zend_object_store_get_object(getThis() TSRMLS_CC); jco_ds_darray_unset(intern->array, (size_t)index); } PHP_METHOD(jco_darray, offsetGet) { jco_darray *intern; long index; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &index) == FAILURE) { zend_throw_exception(NULL, "Invalid index passed", 0 TSRMLS_CC); return; } intern = zend_object_store_get_object(getThis() TSRMLS_CC); zval *val = jco_ds_darray_get(intern->array, (size_t)index); if (val) { //, ,  zval_copy_ctor,  zval_ptr_dtor ZVAL_ZVAL(return_value, val, 1, 0); } else { ZVAL_NULL(return_value); } } PHP_METHOD(jco_darray, offsetExists) { jco_darray *intern; long index; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &index) == FAILURE) { zend_throw_exception(NULL, "Invalid index passed", 0 TSRMLS_CC); return; } intern = zend_object_store_get_object(getThis() TSRMLS_CC); zval *val = jco_ds_darray_get(intern->array, (size_t)index); if (val) { ZVAL_TRUE(return_value); } else { ZVAL_FALSE(return_value); } }
      
      









クラス関数テーブルに関数を追加します。

jco_darray.c
 ZEND_BEGIN_ARG_INFO_EX(arginfo_jco_darray_offset, 0, 0, 1) ZEND_ARG_INFO(0, offset) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_jco_darray_offset_value, 0, 0, 2) ZEND_ARG_INFO(0, offset) ZEND_ARG_INFO(0, value) ZEND_END_ARG_INFO() const zend_function_entry jco_darray_functions[] = { PHP_ME(jco_darray, __construct, arginfo_construct, ZEND_ACC_PUBLIC) PHP_ME(jco_darray, offsetSet, arginfo_jco_darray_offset_value, ZEND_ACC_PUBLIC) PHP_ME(jco_darray, offsetGet, arginfo_jco_darray_offset, ZEND_ACC_PUBLIC) PHP_ME(jco_darray, offsetUnset, arginfo_jco_darray_offset, ZEND_ACC_PUBLIC) PHP_ME(jco_darray, offsetExists, arginfo_jco_darray_offset, ZEND_ACC_PUBLIC) PHP_ME(jco_darray, count, arginfo_void, ZEND_ACC_PUBLIC) PHP_ME(jco_darray, length, arginfo_void, ZEND_ACC_PUBLIC) PHP_FE_END };
      
      









次に、クラスにArrayAccessインターフェイスを実装することをphpに伝えます

  zend_class_implements(jco_darray_ce TSRMLS_CC, 1, zend_ce_arrayaccess);
      
      





関数の最後のパラメーターは、カンマで区切られたインターフェイスとclass_entryインターフェイスの数です。



zend_ce_arrayaccessはzend_interfaces.hファイル(zend_ce_traversable、zend_ce_aggregate、zend_ce_iterator、およびzend_ce_serializableとともに)で宣言されており、jco_darray.cファイルに含める必要があります。

 #include "php.h" #include "zend_interfaces.h" #include "jco_darray.h" #include "ds/darray.h"
      
      





テストコードを書いて、同時にクラスを通常の配列と比較しましょう

 <?php ini_set("memory_limit", "512M"); $data = range(1, 500000); $t1 = microtime(true); $m1 = memory_get_usage(); $jar = new \JCO\DArray(500000, 0); foreach($data as $index => &$val) { $jar[$index] = $val * 3; } echo "JCO\Darray" . PHP_EOL; echo "TIME: " . (microtime(true) - $t1) . PHP_EOL; echo "MEMORY: " . ((memory_get_usage() - $m1)/1048576) . PHP_EOL; gc_collect_cycles(); $t1 = microtime(true); $m1 = memory_get_usage(); $ar = []; foreach($data as $index => &$val) { $ar[$index] = $val * 3; } echo "AR" . PHP_EOL; echo "TIME: " . (microtime(true) - $t1) . PHP_EOL; echo "MEMORY: " . ((memory_get_usage() - $m1)/1048576) . PHP_EOL; gc_collect_cycles(); ?>
      
      







拡張機能を使用してphpをコンパイルおよび実行します

  make && make install
      
      





 ~/dev/bin/php/bin/php -dextension=jco.so jco.php JCO\Darray TIME: 0.43633484840393 MEMORY: 11.44548034668 Array TIME: 0.3345410823822 MEMORY: 137.51664733887
      
      







ちょっと...私たちのコードは標準のphp配列よりも遅いです!



オブジェクトハンドラー



配列が非常に遅いことが判明した理由を見てみましょう。これを行うには、上記object_handlersを思い出してください

 ZEND_API zend_object_handlers std_object_handlers = { zend_objects_store_add_ref, /* add_ref */ zend_objects_store_del_ref, /* del_ref */ zend_objects_clone_obj, /* clone_obj */ zend_std_read_property, /* read_property */ zend_std_write_property, /* write_property */ zend_std_read_dimension, /* read_dimension */ zend_std_write_dimension, /* write_dimension */ zend_std_get_property_ptr_ptr, /* get_property_ptr_ptr */ NULL, /* get */ NULL, /* set */ zend_std_has_property, /* has_property */ zend_std_unset_property, /* unset_property */ zend_std_has_dimension, /* has_dimension */ zend_std_unset_dimension, /* unset_dimension */ zend_std_get_properties, /* get_properties */ zend_std_get_method, /* get_method */ NULL, /* call_method */ zend_std_get_constructor, /* get_constructor */ zend_std_object_get_class, /* get_class_entry */ zend_std_object_get_class_name, /* get_class_name */ zend_std_compare_objects, /* compare_objects */ zend_std_cast_object_tostring, /* cast_object */ NULL, /* count_elements */ zend_std_get_debug_info, /* get_debug_info */ zend_std_get_closure, /* get_closure */ zend_std_get_gc, /* get_gc */ NULL, /* do_operation */ NULL, /* compare */ };
      
      







オブジェクトを配列として使用するには、read_dimension、write_dimension、has_dimension、unset_dimensionの関数を使用します。



コードzend_std_read_dimensionを見ると、ここでArrayAccessインターフェイスのチェックが行われ、対応するoffsetGetメソッドが呼び出されていることがわかります。そして、php関数の呼び出しは、非常に(非常に!)遅いということです。



解決策は明らかです。関数を作成します(同時に、カウントとクローンを作成します)。

jco_darray.c
 // ,   zval  long static inline long zval_to_long(zval *zv) { if (Z_TYPE_P(zv) == IS_LONG) { return Z_LVAL_P(zv); } else { zval tmp = *zv; zval_copy_ctor(&tmp); convert_to_long(&tmp); return Z_LVAL(tmp); } } static zend_object_value jco_darray_clone(zval *object TSRMLS_DC) { jco_darray *old_object = zend_object_store_get_object(object TSRMLS_CC); zend_object_value new_object_val = jco_darray_create_object(Z_OBJCE_P(object) TSRMLS_CC); jco_darray *new_object = zend_object_store_get_object_by_handle(new_object_val.handle TSRMLS_CC); //   zend_objects_clone_members( &new_object->std, new_object_val, &old_object->std, Z_OBJ_HANDLE_P(object) TSRMLS_CC ); new_object->array = jco_ds_darray_clone(old_object->array); if (!new_object->array) { zend_throw_exception(NULL, "Failed to clone jco_darray", 0 TSRMLS_CC); } return new_object_val; } static zval *jco_darray_read_dimension(zval *object, zval *zv_offset, int type TSRMLS_DC) { jco_darray *intern = zend_object_store_get_object(object TSRMLS_CC); if (intern->std.ce->parent) { return zend_get_std_object_handlers()->read_dimension(object, zv_offset, type TSRMLS_CC); } if (!zv_offset) { zend_throw_exception(NULL, "Cannot append to a jco_darray", 0 TSRMLS_CC); return NULL; } long offset = zval_to_long(zv_offset); if (offset < 0 || offset > jco_ds_darray_length(intern->array)) { zend_throw_exception(NULL, "Offset out of range", 0 TSRMLS_CC); return NULL; } zval *return_value; zval *value = jco_ds_darray_get(intern->array, offset); if (value) { if (type != BP_VAR_R && type != BP_WAR_RW) { return_value = value; Z_SET_ISREF_P(return_value); } else { MAKE_STD_ZVAL(return_value); ZVAL_ZVAL(return_value, value, 1, 0); Z_DELREF_P(return_value); } } else { MAKE_STD_ZVAL(return_value); ZVAL_NULL(return_value); Z_DELREF_P(return_value); } return return_value; } static void jco_darray_write_dimension(zval *object, zval *zv_offset, zval *value TSRMLS_DC) { jco_darray *intern = zend_object_store_get_object(object TSRMLS_CC); if (intern->std.ce->parent) { return zend_get_std_object_handlers()->write_dimension(object, zv_offset, value TSRMLS_CC); } if (!zv_offset) { zend_throw_exception(NULL, "Cannot append to a jco_darray", 0 TSRMLS_CC); } long offset = zval_to_long(zv_offset); if (offset < 0) { zend_throw_exception(NULL, "Offset out of range", 0 TSRMLS_CC); } zval *saved_val = jco_ds_darray_set(intern->array, (size_t)offset, value); if (saved_val == NULL) { zend_throw_exception(NULL, "Error occured during dimension write", 0 TSRMLS_CC); } } static int jco_darray_has_dimension(zval *object, zval *zv_offset, int check_empty TSRMLS_DC) { jco_darray *intern = zend_object_store_get_object(object TSRMLS_CC); if (intern->std.ce->parent) { return zend_get_std_object_handlers()->has_dimension(object, zv_offset, check_empty TSRMLS_CC); } long offset = zval_to_long(zv_offset); if (offset < 0 || offset > jco_ds_darray_length(intern->array)) { return 0; } zval *value = jco_ds_darray_get(intern->array, offset); if (value == NULL) { return 0; } if (check_empty) { return zend_is_true(value); } else { return Z_TYPE_P(value) != IS_NULL; } } static void jco_darray_unset_dimension(zval *object, zval *zv_offset TSRMLS_DC) { jco_darray *intern = zend_object_store_get_object(object TSRMLS_CC); if (intern->std.ce->parent) { return zend_get_std_object_handlers()->unset_dimension(object, zv_offset TSRMLS_CC); } long offset = zval_to_long(zv_offset); if (offset < 0 || offset > jco_ds_darray_length(intern->array)) { zend_throw_exception(NULL, "Offset out of range", 0 TSRMLS_CC); } jco_ds_darray_unset(intern->array, offset); } int jco_darray_count_elements(zval *object, long *count TSRMLS_DC) { jco_darray *intern = zend_object_store_get_object(object TSRMLS_CC); if (intern->std.ce->parent) { return zend_get_std_object_handlers()->count_elements(object, count TSRMLS_CC); } if (intern && intern->array) { *count = (long)jco_ds_darray_count(intern->array); return SUCCESS; } else { *count = 0; return FAILURE; } }
      
      









ここで、jco_darray_read_dimension関数は興味深いもので、3番目のパラメーターとして整数型を取ります。これは、関数が呼び出されたコンテキストを示すフラグであり、BP_VAR_R、BP_VAR_W、BP_VAR_RW、BP_VAR_IS、またはBP_VAR_UNSETの値を取ることができます。

 $var[0][1]; //   read_dimension BP_VAR_R $var[0][1] = 1; // [0] - read_dimension BP_VAR_W,  [1] - write_dimension isset($var[0][1]); // [0] - read_dimension BP_VAR_IS, [1] - has_dimension
      
      





型を無視し、常に値のコピーを返す場合、2番目の場合、上記では何も起こらず、配列内の配列の値は変更されません。BP_VAR_Wの場合にこれを修正するには、ガベージコレクターが削除しようとしないように、配列から直接値を与えます。zval-> is_ref__gc = 1(このようなハック)を設定します。



各関数で、存在を確認します(intern-> std.ce-> parent)。これは、誰かがクラスから継承し、ArrayAccessメソッドを上書きする場合です。



phpが標準の関数ではなく関数を使用するには、jco_darray_initに次の行を追加します

  jco_darray_handlers.has_dimension = jco_darray_has_dimension; jco_darray_handlers.read_dimension = jco_darray_read_dimension; jco_darray_handlers.write_dimension = jco_darray_write_dimension; jco_darray_handlers.unset_dimension = jco_darray_unset_dimension; jco_darray_handlers.count_elements = jco_darray_count_elements; jco_darray_handlers.clone_obj = jco_darray_clone;
      
      







拡張機能を使用してphpをコンパイルおよび実行します

  make && make install
      
      





 ~/dev/bin/php/bin/php -dextension=jco.so jco.php JCO\Darray TIME: 0.18597507476807 MEMORY: 11.44548034668 Array TIME: 0.33455300331116 MEMORY: 137.51664733887
      
      







メモリ消費量は1桁低く、実行速度はほぼ2倍です。成功!



通過可能





オブジェクトを非常に配列にするためには、反復可能にする必要があります。 object_handlersには反復する関数はありませんが、zend_class_entryにはget_iterator関数とiterator_funcs構造体がすぐにあります



get_iteratorリターンのzend_object_iterator反復のために使用され、(例えば内のforeach)。

 struct _zend_object_iterator { void *data; //   .   zend_object_iterator_funcs *funcs; //        ulong index; //  .      };
      
      







私が理解しているように、iterator_funcsは、ユーザーコードが機能するために必要です:IteratorまたはIteratorAggregateインターフェースを実装するクラス。フィールドzf_ *-(キャッシュ?)対応するユーザーphp関数。funcsは、_zend_object_iteratorのフィールドに似ています。コメントでiterator_funcsがどのように使用されているかについて、誰かがより完全な説明を与えてくれたら嬉しいです。



jco_darray.cファイルで、jco_darray構造を定義した後、反復に必要なデータを格納する構造を追加します。

 typedef struct _jco_darray_iterator_data { zval *object_zval; //  php  (,        ) jco_darray *object; //   zend_object size_t offset; //   zval *current; //   } jco_darray_iterator_data;
      
      







次に、get_iterator関数を作成しましょう。jco_darray.cで、count_elements関数の後に、jco_darray_get_iterator関数を追加します。

 //by_ref - ,      . zend_object_iterator *jco_darray_get_iterator(zend_class_entry *ce, zval *object, int by_ref TSRMLS_DC) { zend_object_iterator *iter; jco_darray_iterator_data *iter_data; if (by_ref) { zend_throw_exception(NULL, "UPS, no by reference iteration!", 0 TSRMLS_CC); return NULL; } iter = emalloc(sizeof(zend_object_iterator)); iter->funcs = &jco_darray_iterator_funcs; iter_data = emalloc(sizeof(jco_darray_iterator_data)); iter_data->object_zval = object; Z_ADDREF_P(object); iter_data->object = zend_object_store_get_object(object TSRMLS_CC); iter_data->offset = 0; iter_data->current = NULL; iter->data = iter_data; return iter; }
      
      







そして反復関数。個別に宣言しないようにするには、get_iterator関数の前に追加します。

jco_darray.c
 static void jco_darray_iterator_dtor(zend_object_iterator *intern TSRMLS_DC) { jco_darray_iterator_data *data = (jco_darray_iterator_data *)intern->data; if (data->current != NULL) { zval_ptr_dtor(&data->current); } zval_ptr_dtor((zval **)&data->object_zval); efree(data); efree(intern); } static int jco_darray_iterator_valid(zend_object_iterator *intern TSRMLS_DC) { jco_darray_iterator_data *data = (jco_darray_iterator_data *)intern->data; return jco_ds_darray_length(data->object->array) > data->offset ? SUCCESS : FAILURE; } // static void jco_darray_iterator_get_current_data(zend_object_iterator *intern, zval ***data TSRMLS_DC) { jco_darray_iterator_data *iter_data = (jco_darray_iterator_data *)intern->data; if (iter_data->current != NULL) { zval_ptr_dtor(&iter_data->current); iter_data->current = NULL; } if (iter_data->offset < jco_ds_darray_length(iter_data->object->array)) { zval *value = jco_ds_darray_get(iter_data->object->array, iter_data->offset); if (value != NULL) { MAKE_STD_ZVAL(iter_data->current); ZVAL_ZVAL(iter_data->current, value, 1, 0); *data = &iter_data->current; } else { *data = NULL; } } else { *data = NULL; } } #if ZEND_MODULE_API_NO >= 20121212 //  php 5.5+ static void jco_darray_iterator_get_current_key(zend_object_iterator *intern, zval *key TSRMLS_DC) { jco_darray_iterator_data *data = (jco_darray_iterator_data *) intern->data; ZVAL_LONG(key, data->offset); } #else //             //   HASH_KEY_IS_STRING, HASH_KEY_IS_LONG  HASH_KEY_NON_EXISTANT static int jco_darray_iterator_get_current_key(zend_object_iterator *intern, char **str_key, uint *str_key_len, ulong *int_key TSRMLS_DC) { jco_darray_iterator_data *data = (jco_darray_iterator_data *) intern->data; *int_key = (ulong) data->offset; return HASH_KEY_IS_LONG; } #endif static void jco_darray_iterator_move_forward(zend_object_iterator *intern TSRMLS_DC) { jco_darray_iterator_data *data = (jco_darray_iterator_data *) intern->data; data->offset++; } static void jco_darray_iterator_rewind(zend_object_iterator *intern TSRMLS_DC) { jco_darray_iterator_data *data = (jco_darray_iterator_data *) intern->data; data->offset = 0; data->current = NULL; } static zend_object_iterator_funcs jco_darray_iterator_funcs = { jco_darray_iterator_dtor, jco_darray_iterator_valid, jco_darray_iterator_get_current_data, jco_darray_iterator_get_current_key, jco_darray_iterator_move_forward, jco_darray_iterator_rewind, NULL };
      
      









クラスのget_iteratorを指定するのはjco_darray_initのみです。

  jco_darray_ce->get_iterator = jco_darray_get_iterator; jco_darray_ce->iterator_funcs.funcs = &jco_darray_iterator_funcs;
      
      







テストスクリプトにforeachを追加する

 foreach($jar as $val) { if(($val % 100000) == 0) { echo $val . PHP_EOL; } }
      
      







拡張機能を使用してphpをコンパイルおよび実行します

  make && make install
      
      





 ~/dev/bin/php/bin/php -dextension=jco.so jco.php
      
      







おわりに



それは、原則として、すべてです。TraversableインターフェイスとArrayAccessインターフェイスを使用して、PHPの標準配列よりも1桁少ないメモリを消費する高速インデックス配列を作成しました。シリアル化は船外に残されましたが、PHP内部の本の対応するを参照することをお勧めします



新しいphpngで多くの変更が行われたという事実(とりわけ、配列が大幅に最適化されましたにもかかわらず、執筆時点でのオブジェクトの内部インターフェイスの実装は変更されていません。githubリポジトリに



リンクします



そして、はい、私は嘘をついた。これがあなたのための写真です。










All Articles