数组键的微优化

时间:2016-10-06 02:17:08

标签: php arrays php-internals

我有一个数组,我正在使用一些项来构造更多的数组,下面是一个粗略的例子。

$rows = [
    [1, 2, 3, 'a', 'b', 'c'],
    [4, 5, 6, 'd', 'e', 'f'],
    [4, 5, 6, 'g', 'h', 'i'],
];

$derivedData = [];

foreach ($rows as $data) {

    $key = $data[0] . '-' . $data[1] . '-' . $data[2];

    $derivedData['itemName']['count'] ++;
    $derivedData['itemName']['items'][$key]['a'] = $data[3];
    $derivedData['itemName']['items'][$key]['count'] ++;
}

现在,如果我转储数组,它将看起来像

derivedData: [
    itemName: [
        count: 3
        items: [
            1-2-3: [
                a: a,
                count: 1
            ],
            4-5-6: [
                a: g,
                count: 2
            ],
        ]
    ]
]

正如您所看到的,derivedData.itemName.count.items中的键是字符串。如果我做这样的事情,我会获得任何好处吗?

$uniqueId = 0;
$uniqueArray = [];

$rows = [
    [1, 2, 3, 'a', 'b', 'c'],
    [4, 5, 6, 'd', 'e', 'f'],
    [4, 5, 6, 'g', 'h', 'i'],
];

$derivedData = [];

foreach ($rows as $data) {

    $uniqueArrayKey = $data[0] . '-' . $data[1] . '-' . $data[2];

    if (!isset($uniqueArray[$uniqueArrayKey])) {
        $uniqueArray[$uniqueArrayKey] = $uniqueId++;
    }

    $uniqueKey = $uniqueArray[$uniqueArrayKey];

    $derivedData['itemName']['count'] ++;
    $derivedData['itemName']['items'][$uniqueKey ]['a'] = $data[3];
    $derivedData['itemName']['items'][$uniqueKey ]['count'] ++;
}

现在我将有一个索引数组和实际的数据数组。

uniqueArray: [
    1-2-3: 0,
    4-5-6: 1
]

derivedData: [
    itemName: [
        count: 3
        items: [
            0: [
                a: a,
                count: 1
            ],
            1: [
                a: g,
                count: 2
            ],
        ]
    ]
]

我问自己的问题是PHP在使用字符串键时是否在内部执行此操作,即将它们保存在某处并将它们作为键的指针引用而不是每次都复制它们?

换句话说 - 假设我有变量$a,如果我将其用作不同数组中的键,则$a的值将作为键或每个数组使用(并复制)将使用内存中的指针,这基本上是我的问题?

2 个答案:

答案 0 :(得分:2)

  

换句话说 - 假设我有变量$ a,如果我将它用作不同数组中的键,那么$ a的值将被用作(并复制)每个数组作为键或内存中的指针将被使用,这基本上是我的问题?

PHP> = 5.4& PHP 7,它取决于您的环境。我不是PHP专家,我的答案 可能是错误的 但是我已经编程了很长一段时间的PHP扩展,我试图根据我的问题回答你的问题观察。

在{5.6}的源代码zend_hash.c中,我们可以找到这个函数:

ZEND_API int _zend_hash_add_or_update(HashTable *ht, const char *arKey, uint nKeyLength, void *pData, uint nDataSize, void **pDest, int flag ZEND_FILE_LINE_DC)
{
// omitted
        if (IS_INTERNED(arKey)) {
                p = (Bucket *) pemalloc(sizeof(Bucket), ht->persistent);
                p->arKey = arKey;
        } else {
                p = (Bucket *) pemalloc(sizeof(Bucket) + nKeyLength, ht->persistent);
                p->arKey = (const char*)(p + 1);
                memcpy((char*)p->arKey, arKey, nKeyLength);
        }
// omitted
}

似乎复制字符串是根据IS_INTERNED()的值确定的,那么它在哪里?首先,在ZendAccelerator.h中,我们可以找到:

#if ZEND_EXTENSION_API_NO > PHP_5_3_X_API_NO
// omitted
#else
# define IS_INTERNED(s)             0
// omitted
#endif

因此,“实习字符串”的概念从PHP 5.4开始存在。该字符串将始终在PHP 5.3之前和之前复制。但是由于PHP< = 5.3真的已经过时了,我想把它从这个答案中删除。那么PHP 5.4-5.6怎么样?在zend_string.h

#ifndef ZTS

#define IS_INTERNED(s) \
        (((s) >= CG(interned_strings_start)) && ((s) < CG(interned_strings_end)))

#else

#define IS_INTERNED(s) \
        (0)

#endif

哦,哦,坚持,另一个宏,又在哪里?在zend_globals_macros.h

#ifdef ZTS
# define CG(v) TSRMG(compiler_globals_id, zend_compiler_globals *, v)
int zendparse(void *compiler_globals);
#else
# define CG(v) (compiler_globals.v)
extern ZEND_API struct _zend_compiler_globals compiler_globals;
int zendparse(void);
#endif

因此,在没有Zend线程安全的PHP 5.4-5.6中,如果字符串已经存在于此特定进程的内存中,则将使用引用;但是对于ZTS,它将始终被复制。 (仅供参考,我们在Linux中很少需要ZTS)。

  

为了澄清,在这种情况下,$uniqueKey字符串将不会被实现,因为它是在运行时创建的。 Interning仅适用于编译时已知(文字)字符串。    @NikiC感谢您的澄清

PHP 7怎么样?在zend_hash.c中,PHP 7.0.11的源代码,

static zend_always_inline zval *_zend_hash_add_or_update_i(HashTable *ht, zend_string *key, zval *pData, uint32_t flag ZEND_FILE_LINE_DC)
{
        zend_ulong h;
        uint32_t nIndex;
        uint32_t idx;
        Bucket *p;

        IS_CONSISTENT(ht);
        HT_ASSERT(GC_REFCOUNT(ht) == 1);

        if (UNEXPECTED(!(ht->u.flags & HASH_FLAG_INITIALIZED))) {
                CHECK_INIT(ht, 0);
                goto add_to_hash;
        } else if (ht->u.flags & HASH_FLAG_PACKED) {
                zend_hash_packed_to_hash(ht);
        } else if ((flag & HASH_ADD_NEW) == 0) {
                p = zend_hash_find_bucket(ht, key);

                if (p) {
// omitted
                }
        }

        ZEND_HASH_IF_FULL_DO_RESIZE(ht);        /* If the Hash table is full, resize it */

add_to_hash:
        HANDLE_BLOCK_INTERRUPTIONS();
        idx = ht->nNumUsed++;
        ht->nNumOfElements++;
        if (ht->nInternalPointer == HT_INVALID_IDX) {
                ht->nInternalPointer = idx;
        }
        zend_hash_iterators_update(ht, HT_INVALID_IDX, idx);
        p = ht->arData + idx;
        p->key = key;
        if (!ZSTR_IS_INTERNED(key)) {
                zend_string_addref(key);
                ht->u.flags &= ~HASH_FLAG_STATIC_KEYS;
                zend_string_hash_val(key);
        }
// omitted
}

ZEND_API zval* ZEND_FASTCALL _zend_hash_str_add(HashTable *ht, const char *str, size_t len, zval *pData ZEND_FILE_LINE_DC)
{
        zend_string *key = zend_string_init(str, len, ht->u.flags & HASH_FLAG_PERSISTENT);
        zval *ret = _zend_hash_add_or_update_i(ht, key, pData, HASH_ADD ZEND_FILE_LINE_RELAY_CC);
        zend_string_release(key);
        return ret;
}

FYI,

#define ZSTR_IS_INTERNED(s)                 (GC_FLAGS(s) & IS_STR_INTERNED)
哇,所以PHP 7实际上引入了一个新的,令人惊叹的zend_string结构,它可以解决RC和垃圾收集问题!这比PHP 5.6更有效!

简而言之,如果你使用现有的字符串作为哈希表中的键,当然你保持不变,在PHP&lt; = 5.3中,很可能被复制;在PHP 5.4中没有ZTS,引用;在PHP 5.4中使用ZTS,复制;在PHP 7中,引用。

此外,我发现了一篇很棒的文章供你阅读(我稍后会再阅读):http://jpauli.github.io/2015/09/18/php-string-management.html

答案 1 :(得分:0)

虽然我假设内部有一段时间没有改变,this article,但是它们基本上是哈希表,有一些细微差别以避免关键冲突。所以在某种程度上,是的,它确实做了你所说的内幕。