PHP 自作 TypedArray (闇)


[PHP 自作 TypedArray](http://qiita.com/yoya/items/68634ab031145860938b) の闇版です。
PHP の配列が沢山メモリを使うのは何故？という話。(タイトル失敗しました。。)

闇といってもダークサイドではなく、裏方である PHP内部実装(C言語)の話です。あと、あくまで PHP5 の話です。PHP7 は色々と違います。

## はじめに



PHP の array は単に番号を指定して値を格納出来るだけでなく。文字列をキーに格納して連想配列として使ったり、かつ格納した順番で取り出せたりと、やりすぎといって良いほど高性能な代物です。

C言語では宣言した型の値を番号指定で格納する事しか出来ないので、それらを実現する為に相当頑張っています。
参考の為に、array の管理データである HashTable の構造体を引用します。

## HashTable

```c:c:php-5.5.28/Zend/zend_hash.h
typedef struct bucket {
        ulong h;                                                /* Used for nume
ric indexing */
        uint nKeyLength;
        void *pData;
        void *pDataPtr;
        struct bucket *pListNext;
        struct bucket *pListLast;
        struct bucket *pNext;
        struct bucket *pLast;
        const char *arKey;
} Bucket;

typedef struct _hashtable {
        uint nTableSize;
        uint nTableMask;
        uint nNumOfElements;
        ulong nNextFreeElement;
        Bucket *pInternalPointer;       /* Used for element traversal */
        Bucket *pListHead;
        Bucket *pListTail;
        Bucket **arBuckets;
        dtor_func_t pDestructor;
        zend_bool persistent;
        unsigned char nApplyCount;
        zend_bool bApplyProtection;
#if ZEND_DEBUG
        int inconsistent;
#endif
} HashTable;
```

図にすると以下のような感じです。

![image](https://qiita-image-store.s3.amazonaws.com/0/1798/9d563e49-471a-f512-dd0e-5e2626fdd0ce.png)
引用元) https://drive.google.com/file/d/0B3UKOMH_4lgBUTdjUGxIZ3l1Ukk/view

ゆるふわに簡略化した分かりやすい図も見つけました。

![image](https://qiita-image-store.s3.amazonaws.com/0/1798/657db0d0-32d0-d3ee-c860-196f8a734972.png)
引用元) https://catchy.io/slides/PHP-Data-Structures-and-the-impact-of-PHP-7-on-them.html

なんだか色々絡み合ってます。メモリを沢山使うのは何となくイメージ出来ると思います。
そして、PHP5.3 で導入された SplFixedArray は長さを固定長にして真ん中の構造をばさっと省略して、メモリ使用量が半分位まで減らしてます。

ですけど所詮それでも半分です。PHP の標準array も SplFixedArray も要素としてどんな型でも入る変数コンテナとして保存する為です。

## Zval 構造体

PHPの変数コンテナは、皆さん、お馴染みの Zval 構造体として表現されます。

```c:php-5.5.28/Zend/zend.h
typedef union _zvalue_value {
        long lval;                                      /* long value */
        double dval;                            /* double value */
        struct {
                char *val;
                int len;
        } str;
        HashTable *ht;                          /* hash table value */
        zend_object_value obj;
} zvalue_value;

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

zvalue_value の C言語 union(共用体) は内部で宣言したどの型でも使えるように、その中で一番大きな型に合わせてメモリを確保します。その上 zval_struct の管理データ量も馬鹿になりません。

![image](https://qiita-image-store.s3.amazonaws.com/0/1798/08437545-c275-7f85-2ecc-3e17ac8a1c63.png)
引用元) http://www.slideshare.net/hnw/yamiphp-20140315/13

つまり、配列要素にどんな小さな(レンジの)値を保存しても、変数ひとつあたり 24byte(Zval構造体) + 8byte(リストからのポインタ)で34byteが必要という事です。

## string (文字列型)

一方、PHP の string は所詮 zvalue_value の中の struct str だけであって、char* と len というベタな構造です。

```c:php-5.5.28/Zend/zend.h
typedef union _zvalue_value {
＜略＞
        struct {
                char *val;
                int len;
        } str;

} zvalue_value;
```

保存したい値が byte 単位であれば、複数の byte を pack してこの string として保存すれば、1byte のデータは 1byte 分しか消費しません。

array を使わずに string として格納して省メモリというのは以前からやっていたのですが、配列風にアクセス出来る class を作ってみた。というのが「PHP 自作 TypedArray」のネタでした。

P.S. これのPHP拡張を作ってエントリにするつもりでしたが、あまり需要がないので(今のところコメント貰えたのが2名)、そのうち暇になったらやります。もしくは誰かやってください。:-)