一个百万整数的PHP数组,占16.8 MB。给它加一个单字符的键,占用跳到41.9 MB。一个键,25 MB。 这不是段子,是在PHP 8.4上实测出来的数字。有人最近重构一段遗留PHP代码时发现,同一个数据数组被用出了完全不同的两种形态,内存表现天差地别。顺着这条线查下去,结论是:PHP数组只要还是紧凑列表,它就是非常高效的数据结构;一旦变成哈希表,内存消耗立刻起飞。对大数据集来说,关联数组的内存占用经常接近普通类型化对象的两倍。 为什么一个键这么贵 PHP数组是哈希表这件事,听过太多次,听到已经不当回事了,和“浮点数不精确”一起躺在同一个抽屉里。但真去建一个百万元素的数组、一边操作一边盯着 memory_get_usage(),数字会说话。 测试从PHP最擅长优化的场景开始:按顺序填充的紧凑列表。然后看这种优化是怎么悄悄消失的。再然后,是每个代码库里都有的那种场景——从查询里取出一百万行,每行都是一个关联数组。真正的成本就在这些行上,而修复方案比问题本身小得多。 所有测试跑在PHP 8.4.21上,官方 php:8.4-cli Docker镜像,64位Linux,memory_limit设为-1,避免中途被内存限制干掉。涉及版本差异的地方,同一个脚本也在8.1.34上跑了一遍,因为8.2改动了其中一个数字,需要前后对照。数据是 memory_get_usage() 的差值。PHP手册说这些值会按分配器的粒度向上取整,所以最后几位数字当作噪声看。 测量代码本身不复杂 思路很直接:建数组,两次调用 memory_get_usage() 相减,再除以元素个数。 关键对比是两组循环,用的是同一批键、同一批值,唯一区别是一个从0往上数,一个从N-1往下数。顺序填充的紧凑列表,每个元素约16.78字节。 同样的键,倒序填充,结果就变了。这就是PHP数组优化消失的那个瞬间——它不再是一个紧凑列表,而是一个哈希表。 对每天和PHP打交道的人来说,这个数字值得记住:16.8 MB 和 41.9 MB 之间,隔着一个键。而一百万行查询结果,每一行都是关联数组的时候,这个差距会被放大到整个应用的内存曲线上。

打开网易新闻 查看精彩图片