PHP数组中的内存优化

30

我正在处理一个大型数组,这是一个高度图,大小为1024x1024,但是我被内存限制所困扰。在我的测试机器上,如果需要,我可以将内存限制增加到1GB,但在只有256 RAM的小型VPS中,这不是一个选择。

我在Stack和Google上搜索过,并发现了一些“好吧,你正在使用PHP而不是因为内存效率,放弃它并改用C ++”这样的建议,老实说,我承认PHP喜欢占用内存。

但是,当深入研究PHP内存管理时,我没有找到每种数据类型消耗的内存,或者将其转换为另一种数据类型是否会减少内存消耗等信息。

唯一的“优化”技术是取消变量和数组,就这样。

使用一些PHP解析器将代码转换为C ++能解决问题吗?

谢谢!


3
在PHP中,数组实际上是字典,因此非常占用内存。如果你可以放弃一些速度(很多!),你可以像在C语言中一样仿造二进制数组,我猜2D数据结构也是如此。但也许你真的想要研究HipHop PHP to C++编译器 - mario
你需要削减多少内存使用量?正如你所读到的,PHP 中可以进行的内存管理很少。虽然有一些“优化”可以做,但可能没有什么能够削减你所需的那么多。 - simshaun
PHP 中的每个变量都有与之相关的开销。不仅需要存储变量的值,还需要存储变量的名称、类型等等...即使是一个简单的 $x[1] = 2; 也会带着大量额外的东西。 - Marc B
@mario,我想知道为什么链接的帖子停留在十六进制编码上,而不直接使用字符串的完整字节。看起来用一点数学可能会更快...但我不使用PHP(它对有效的多字节序列等没有考虑) :-) - user166390
1
@pst:我实际上有另一个版本,使用pack()来处理二进制字符串。但那并不是真正更快;只是节省了两倍的内存。(在PHP中只能做到这么多;) - mario
3个回答

59

如果你想要一个真正的索引数组,可以使用 SplFixedArray。它使用的内存更少。此外,PHP 5.3拥有更好的垃圾回收器。

除此之外,PHP将比一个更仔细编写的C/C++等价物使用更多的内存。

1024x1024个整数的数组的内存使用情况:

  • 标准数组:218,756,848
  • SplFixedArray:92,914,208

memory_get_peak_usage()测量得出。

$array = new SplFixedArray(1024 * 1024); // array();
for ($i = 0; $i < 1024 * 1024; ++$i)
  $array[$i] = 0;

echo memory_get_peak_usage();
请注意,使用64位整数的C中相同的数组将为8M。
正如其他人建议的那样,您可以将数据打包到字符串中。这样做速度较慢,但内存效率要高得多。如果使用8位值,这很容易:
$x = str_repeat(chr(0), 1024*1024);
$x[$i] = chr($v & 0xff); // store value $v into $x[$i]
$v = ord($x[$i]);        // get value $v from $x[$i]

这里的内存只会约为1.5MB(即仅考虑PHP的全部开销以及此整数字符串数组)。

出于兴趣,我创建了一个简单的基准测试,创建了1024x1024个8位整数,然后循环一次。打包版本都使用了ArrayAccess,以便用户代码保持相同。

                   mem    write   read
array              218M   0.589s  0.176s
packed array       32.7M  1.85s   1.13s
packed spl array   13.8M  1.91s   1.18s
packed string      1.72M  1.11s   1.08s

这些压缩数组使用了本地64位整数(只使用7个字节来避免处理有符号数据),而压缩字符串则使用了ordchr。显然实现细节和计算机规格会对结果产生一定影响,但我希望你可以获得类似的结果。

因此,虽然数组快了6倍,但它也用了比下一个最佳替代方案——压缩字符串多125倍的内存。如果你的内存不足,速度就无关紧要了。(当我直接使用压缩字符串而没有使用ArrayAccess类时,它们只比本机数组慢3倍。)

简而言之,总结一下,如果速度很重要,我建议使用除纯PHP以外的其他工具来处理这些数据。


显然,有超过4个字节的开销... 这篇帖子表明,即使是一个微不足道的值,它可能需要高达36(或x64上的72)字节。这表明打包非常有益(从内存使用的角度来看)。假设8位输入和32位架构,4个值将占用约36字节,而如果打包,则为约144字节;而在x64机器上,8个值将占用约72字节,而如果打包,则为约576字节!(天哪!) - user166390
1
@pst,我已经添加了一些关于将数据打包成字符串的内容。当处理8位整数(例如高度图)时,那么一个字符串整数数组基本上与C语言等效的大小相同。当然,速度会比本机整数慢得多。 - Matthew
@konforce 将数据打包成 PHP 整型值 :) 对于许多操作,速度应该接近未打包的速度(只需一个掩码和一个额外的移位)-- 在内存方面更有效率(但不如将其混合到字符串中那样高效)。 - user166390
@pst,抱歉我把你的建议和Mario的建议搞混了。是的,使用SplFixedArray,如果打包8位整数,则大小应该约为22MB。它应该比1.5MB的打包字符串等效快,虽然在PHP中永远不知道。 (例如,整数是有符号的,因此处理高位可能需要更复杂的移位)。最快的内存友好型解决方案是编写本地整数数组作为C扩展,并将其公开为PHP类。 - Matthew
哎呀,我忘了我的数字是64位的,所以带有紧密整数的SplFixedArray应该大约为12MB。 - Matthew
显示剩余3条评论

13

除了已经接受的答案和评论中的建议之外,我想建议使用PHP Judy数组实现

快速测试显示出有趣的结果。使用常规PHP数组数据结构的1百万条目的数组需要大约200 MB。SplFixedArray使用大约90兆字节。Judy只使用8兆字节。性能上的权衡是,Judy花费的时间是普通php数组实现的两倍。


我会去看看,不错不错!在我的情况下,为了节省一些内存,我可以接受性能损失。 - Gabriel
正是我所需要的!Judy Array 真是太棒了。高性能和低内存使用率。 - Tiago Fischer
@FlycKER - 我很高兴有人决定使用这个很棒的数组实现 :) - N.B.
朱迪数组不就是在内部使用打包的PHP字符串吗? - Pacerier
@N.B.,不是没有其他更有效地在PHP中实现它的方法吗? - Pacerier
@Pacerier - 我不太明白你的问题。Judy是PHP的一个扩展,所有的内存分配和工作都由扩展管理,而不是PHP内部数据结构,特别是不是字符串。 - N.B.

6

稍微有点晚了,但如果你有一个多维数组,当你将整个数组存储为json时,可以节省大量RAM。

$array = [];

$data = [];
$data["a"] = "hello";
$data["b"] = "world";

存储该数组只需使用:

$array[] = json_encode($data);

替代

$array[] = $data;

如果你想获取数组,只需使用类似以下的代码:

$myData = json_decode($array[0], true);

我有一个大数组,有275,000个集合,节省了约36%的RAM消耗。

编辑: 我找到了一种更好的方法,当你压缩json字符串时:

$array[] = gzencode(json_encode($data));

需要时,请解压缩它:

$myData = json_decode(gzdecode($array[0], true));

这让我节省了近75%的RAM峰值使用率。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接