PHP Extensionの開発②

# 概要
[前回](http://qiita.com/takeyoshi-k/items/2a993eefbe06be047eff)に続き、今回はPHPとPHP Extensionで同じ仕様の関数を実装してみます。

## 関数の仕様
配列（\$rows）から指定したキー（\$key）と値（\$value）に一致する配列を取得する。

## PHPでの実装

```
function array_key_value_column(array $rows, $key, $value) {
    $res = [];
    $rows_by_key = array_column($rows, $key);
    $indexes = array_keys($rows_by_key, $value);
    foreach ($indexes as $index) {
        $res[] = $rows[$index];
    }
    return $res;
}
```

## PHP Extension（C言語）での実装

```
PHP_FUNCTION(array_key_value_column)
{
	zval *array,
		 *key,
		 *value, *val,
		 *entry;
	zval tmp;
	HashTable *arr_hash;
	HashTable out_hash;
	zend_long idx, count;

	ZEND_PARSE_PARAMETERS_START(3, 3)
		Z_PARAM_ARRAY(array)
		Z_PARAM_ZVAL_DEREF(key)
		Z_PARAM_ZVAL_DEREF(value)
	ZEND_PARSE_PARAMETERS_END();

	arr_hash = HASH_OF(array);

	// valueの詰め替え
	tmp = *value;
	// 引数はarray_columnやarray_keysにそのまま渡しているため、それらの関数内で利用しない第3引数はクリア
	ZVAL_NULL(value);

	// array_columnの実行
	php_array_column(INTERNAL_FUNCTION_PARAM_PASSTHRU);

	// array_keysの第1引数をarray_columnの結果（return_value）に置き換え
	ZVAL_COPY(array, return_value);

	// array_keysの第2引数をvalueに置き換え
	*key = tmp;

	// array_keysの実行
	php_array_keys(INTERNAL_FUNCTION_PARAM_PASSTHRU);

	// array_keysの結果（return_value）から該当する配列要素を取得
	zend_hash_init(&out_hash, zend_hash_num_elements(arr_hash), NULL, ZVAL_PTR_DTOR, 0);
	ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(return_value), entry) {
		count = 0;
		idx = Z_LVAL_P(entry);
		while ((val = zend_hash_get_current_data(arr_hash)) != NULL) {
			if (idx == count) {
				zend_hash_next_index_insert_new(&out_hash, val);
			}
			count++;
			zend_hash_move_forward(arr_hash);
		}
		zend_hash_internal_pointer_reset(arr_hash);
	} ZEND_HASH_FOREACH_END();

	// 上で取得した配列要素を結果（return_value）に置き換え
	RETVAL_ARR(zend_array_dup(&out_hash));
}
```

以下の関数は、[array.c](https://github.com/php/php-src/blob/master/ext/standard/array.c)に定義されている関数をコピーして利用しています。
php_array_column
php_array_keys

前回と比べると、以下HashTableを利用した値の参照と更新が特徴です。
HashTableを操作するマクロや関数は[zend_hash.c](https://github.com/php/php-src/blob/master/Zend/zend_hash.c)に定義されています。

```zend_types.h
typedef struct _zend_array HashTable;

struct _zend_array {
	zend_refcounted_h gc;
	union {
		struct {
			ZEND_ENDIAN_LOHI_4(
				zend_uchar    flags,
				zend_uchar    nApplyCount,
				zend_uchar    nIteratorsCount,
				zend_uchar    consistency)
		} v;
		uint32_t flags;
	} u;
	uint32_t          nTableMask;
	Bucket           *arData;
	uint32_t          nNumUsed;
	uint32_t          nNumOfElements;
	uint32_t          nTableSize;
	uint32_t          nInternalPointer;
	zend_long         nNextFreeElement;
	dtor_func_t       pDestructor;
};
```

## 動作確認

- テストコード

```
<?php
  $array = array(
    0 => array('id' => 1, 'name' => 'test1'),
    1 => array('id' => 2, 'name' => 'test2'),
    2 => array('id' => 3, 'name' => 'test3'),
    3 => array('id' => 2, 'name' => 'test4')
  );

  $result = array_key_value_column($array, 'id', 2);

  foreach ($result as $key => $value) {
	echo "- key=" . "$key\n";
	foreach ($value as $k => $v) {
		echo "k=" . "$k\n";
		echo "v=" . "$v\n";
	}
  }
?>
```

- 出力結果

```
- key=0
k=id
v=2
k=name
v=test2
- key=1
k=id
v=2
k=name
v=test4
```

PHP、PHP Extensionで実装した関数ともに同じ結果を得ることができます。


### パフォーマンスの比較
テストコードを以下のように変更し、パフォーマンスを比較してみました。

```
<?php
  define("LOOP_COUNT", 100000);

  $array = array(
    0 => array('id' => 1, 'name' => 'test1'),
    1 => array('id' => 2, 'name' => 'test2'),
    2 => array('id' => 3, 'name' => 'test3'),
    3 => array('id' => 2, 'name' => 'test4')
  );

  $time_start = microtime(true);
  for ($i = 0; $i < LOOP_COUNT; $i++) {
    $result = array_key_value_column($array, 'id', 2);
  }
  $time = microtime(true) - $time_start;
  echo "{$time} 秒\n";
?>
```

結果はほぼ変わらない、もしくはPHPで実装した関数の方がPHP Extensionで実装した関数より若干早いものでした。
これは既存の関数（php_array_column、php_array_keys）を呼び出しているため引数の詰め替えなどが冗長であることが原因だと考えられます。


# 所感
PHP Extensionの開発に関して理解を深めると、PHP内部での変数の扱いやメモリ管理など、PHPを利用した開発の上ではあまり意識しないことについて理解を深めることができます。

また、誰でも[基本的なルール](http://git.php.net/?p=php-src.git;a=blob_plain;f=README.MAILINGLIST_RULES;hb=HEAD)さえ順守していれば、既存の関数のバグの修正やリファクター、パフォーマンスの改善など、PHPプロジェクトへの貢献も可能です。
PHPは多くの人の支援により開発が進められています。
その[プロセス](https://wiki.php.net/rfc)や、実際の[やりとり](http://news.php.net/)を見るだけでも面白いと思います。