我总是听到并搜索新的PHP“良好的写作练习”,例如:检查数组键是否存在比搜索数组更好(性能),但对内存来说似乎也更好:
假设我们有:
$array = array
(
'one' => 1,
'two' => 2,
'three' => 3,
'four' => 4,
);
这会分配1040个字节的内存,
和
$array = array
(
1 => 'one',
2 => 'two',
3 => 'three',
4 => 'four',
);
需要1136个字节
我知道key
和value
肯定会有不同的存储机制,但是
请你真的指出我的原理是如何运作的?
示例2 (对于@teuneboon):
$array = array
(
'one' => '1',
'two' => '2',
'three' => '3',
'four' => '4',
);
1168字节
$array = array
(
'1' => 'one',
'2' => 'two',
'3' => 'three',
'4' => 'four',
);
1136字节
消耗相同的内存:
4 => 'four',
'4' => 'four',
答案 0 :(得分:24)
注意,以下答案适用于PHP 之前到版本7,因为在PHP 7中引入了主要更改,其中还涉及值结构。
你的问题实际上并不是关于&#34;内存如何在PHP&#34; 中运行(在这里,我认为,你的意思是&#34;内存分配&#34;),但是关于< em>&#34;数组如何在PHP&#34; 中运行 - 这两个问题是不同的。总结下面的内容:
ulong
(无符号长)键散列映射中,因此真正的差异将在值中,其中string-keys选项具有整数(固定长度)值,而整数-keys选项具有字符串(与字符相关的长度)值。但由于可能的碰撞,这可能并非总是如此。'4'
)将被视为整数密钥并转换为整数哈希结果,因为它是整数密钥。因此,'4'=>'foo'
和4 => 'foo'
是相同的。此外,重要提示:此处的图片版权归PHP internals book
PHP数组和C数组
你应该意识到一个非常重要的事情:PHP是用C语言编写的,其中包括&#34;关联数组&#34;根本不存在。所以,在C&#34;数组&#34;正是&#34; array&#34;是 - 即它只是存储器中的一个连续区域,可以通过连续偏移来访问。你的&#34;键&#34;可能只是数字,整数而且只是连续的,从零开始。例如,您不能将3
,-6
,'foo'
作为&#34;密钥&#34;那里。
为了实现PHP中的数组,有哈希映射选项,它使用哈希函数来哈希你的密钥并将它们转换为整数,可用于C阵列。但是,该函数永远无法在字符串键及其整数散列结果之间创建bijection。并且很容易理解为什么:因为设置的cardinality字符串比整数集的基数大得多。让我们举例说明:我们将重新计算最长为10的所有字符串,这些字符串只包含字母数字符号(因此,0-9
,a-z
和A-Z
,总计62):它可能有62个 10 总串。它在 8.39E + 17 附近。将它与我们对无符号整数(长整数,32位)类型的 4E + 9 进行比较,你会得到这个想法 - 会有碰撞
PHP哈希映射键&amp;碰撞强>
现在,为了解决冲突,PHP只会将具有相同哈希函数结果的项放入一个链表中。因此,hash-map不仅仅是散列元素的列表&#34;而是它将存储指向元素列表的指针(某些列表中的每个元素将具有相同的散列函数键)。这就是你要指出它将如何影响内存分配的地方:如果你的数组有字符串键,这不会导致冲突,那么这些列表中就不需要额外的指针,因此内存量会减少(实际上,它和#39;开销非常小,但是,由于我们正在谈论精确的内存分配,因此应该考虑到这一点)。并且,同样地,如果您的字符串键将导致许多冲突,那么将创建更多的附加指针,因此总内存量将更多一些。
为了说明这些列表中的关系,这里有一个图形:
上面介绍了PHP在应用哈希函数后如何解决冲突。因此,您的一个问题部分就在于此处,指出了碰撞解决方案列表中的指针。此外,链接列表的元素通常称为 buckets ,并且包含指向这些列表头部的指针的数组在内部称为arBuckets
。由于结构优化(因此,为了使元素删除,更快),真正的列表元素有两个指针,前一个元素和下一个元素 - 但是这只会在非碰撞/碰撞的存储量方面产生差异数组稍微宽一点,但不会改变概念本身。
还有一个清单:订单
要完全支持PHP中的数组,还需要维护 order ,以便通过另一个内部列表实现。数组的每个元素也是该列表的成员。它在内存分配方面没有什么不同,因为在这两个选项中都应该维护这个列表,但是为了全面了解,我提到了这个列表。这是图形:
除了pListLast
和pListNext
之外,还存储了指向订单列表头部和尾部的指针。同样,它与您的问题没有直接关系,但我还会转储内部存储桶结构,这些存在这些指针。
内部的数组元素
现在我们已经准备好了解一下:什么是数组元素,所以,bucket:
typedef struct bucket {
ulong h;
uint nKeyLength;
void *pData;
void *pDataPtr;
struct bucket *pListNext;
struct bucket *pListLast;
struct bucket *pNext;
struct bucket *pLast;
char *arKey;
} Bucket;
我们在这里:
h
是key的整数(ulong)值,它是hash函数的结果。对于整数键,与键本身相同(哈希函数返回自身)pNext
/ pLast
是碰撞解决链接列表中的指针pListNext
/ pListLast
是订单分辨率链接列表中的指针pData
是指向存储值的指针。实际上,值与创建数组时插入的值相同,它的 copy ,但为了避免不必要的开销,PHP使用pDataPtr
(所以pData = &pDataPtr
)从这个角度来看,你可能会得到下一个不同之处:因为字符串键将被哈希(因此,h
总是ulong
,因此,相同的大小),它将是一个存储在值中的内容。因此,对于您的字符串键数组,将存在整数值,而对于整数键数组,将存在字符串值,这会产生差异。但是 - 不,它不是一个神奇的:你不能节省内存&#34;一直以这种方式存储字符串键,因为如果你的键很大并且会有很多键,它会导致冲突开销(很好,很可能,但当然不能保证)。它会&#34;工作&#34;仅适用于任意短串,不会导致多次碰撞。
哈希表本身
已经讨论了元素(存储桶)及其结构,但也有散列表本身,实际上是数组数据结构。所以,它被称为_hashtable
:
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;
我不会描述所有字段,因为我已经提供了很多信息,这只与问题有关,但我会简要介绍一下这个结构:
arBuckets
就是上面描述的,桶存储,pListHead
/ pListTail
是订单分辨率列表的指针nTableSize
确定哈希表的大小。这与内存分配直接相关:nTableSize
总是2的幂。因此,无论你是否在数组中有13个或14个元素:实际大小为16。想要估算数组大小时要考虑到这一点。很难预测,在你的情况下,一个阵列会比另一个阵列大。是的,有一些指导原则是内部结构,但如果字符串键的长度与整数值相当(例如样本中的'four'
,'one'
) - 真正的区别在于 - 发生了多少次冲突,分配了多少字节来保存值。
但选择合适的结构应该是感觉问题,而不是记忆。如果您打算构建相应的索引数据,那么选择总是显而易见的。上面的帖子只有一个目标:显示数组如何在PHP中实际工作,以及在哪里可以找到样本中内存分配的差异。
您还可以查看有关数组和文章的文章。 PHP中的哈希表:PHP内部书籍Hash-tables in PHP:我从那里使用了一些图形。另外,要了解如何在PHP中分配值,请查看zval Structure文章,它可以帮助您理解字符串和字符串之间的差异。整数分配数组的值。我没有在这里包含解释,因为对我来说更重要的一点是 - 显示数组数据结构以及在你的问题的字符串键/整数键的上下文中可能有什么不同。
答案 1 :(得分:3)
尽管两个数组都以不同的方式访问(即通过字符串或整数值),但内存模式大致相似。
这是因为字符串分配是作为zval创建的一部分或需要分配新数组键时发生的;不同的是,数字索引不需要整个zval结构,因为它们被存储为(无符号)长。
观察到的内存分配差异很小,很大程度上可归因于memory_get_usage()
的不准确性或由于额外的存储桶创建而导致的分配。
如何使用阵列必须成为选择索引方式的指导原则;当你用完它时,内存只会成为这个规则的一个例外。
答案 2 :(得分:3)
从PHP手册垃圾收集http://php.net/manual/en/features.gc.php
gc_enable(); // Enable Garbage Collector
var_dump(gc_enabled()); // true
var_dump(gc_collect_cycles()); // # of elements cleaned up
gc_disable(); // Disable Garbage Collector
PHP不能很好地返回释放的内存;它在线的主要用途并不需要它,有效的垃圾收集需要时间来提供输出;当脚本结束时,无论如何都会返回内存。
垃圾收集发生。
当你告诉
时 int gc_collect_cycles ( void )
离职时
更好地了解来自网络主机的PHP垃圾收集(无联属关系)。 http://www.sitepoint.com/better-understanding-phps-garbage-collection/
如果您正在逐字节地考虑如何在内存中设置数据。不同的端口将影响这些值。当数据位于64位字的第一位时,64位CPU的性能最佳。对于最大性能,一个特定的二进制文件,它们将在第一个位上分配一块内存的开始,最多留下7个字节未使用。这个CPU特定的东西取决于编译PHP.exe的编译器。我不能提供任何方法来预测确切的内存使用情况,因为它将由不同的编译器以不同的方式确定。
Alma Do,post转到发送给编译器的源的细节。 PHP源请求和编译器优化的内容。
查看您发布的具体示例。当密钥是ascii字母时,它们每个条目占用4个字节(64位)...这对我来说(假设没有垃圾或内存漏洞等),ascii密钥大于64位,但是数字键适合64位字。它向我建议你使用64位计算机,你的PHP.exe是为64位CPU编译的。
答案 3 :(得分:1)
PHP中的数组实现为哈希映射。因此,用于密钥的值的长度对数据要求几乎没有影响。在旧版本的PHP中,大型数组的性能显着下降,因为散列大小在数组创建时得到修复 - 当冲突开始发生然后增加的哈希值数量时,将映射到值的链接列表,然后必须进一步搜索(使用一个O(n)算法)而不是单个值,但最近哈希似乎要么使用更大的默认大小或动态调整大小(它只是工作 - 我真的不能阅读源代码)
从脚本中保存4个字节不会导致Google出现任何不眠之夜。如果您正在编写使用大型数组的代码(节省可能更为重要),那么您可能做错了 - 填充数组所花费的时间和资源可以更好地用于其他地方(例如索引存储)。 / p>