【问题标题】:PHP sudden increase of memory usage during array operationsPHP在数组操作期间突然增加内存使用量
【发布时间】:2014-01-10 17:45:58
【问题描述】:

我正在用 PHP 编写维护脚本。该脚本将大约 100,000 个键值对保存在一个关联数组中,并将一堆其他数据与该数组进行比较。

密钥是 12 或 16 字节的十六进制字符串。

这些值是包含 1-10 个字符串的数组。每个字符串大约 50 个字节。

我通过在循环中使用fgets() 逐行读取文本文件来填充我的数组。

在我敲了大约 44,000 个键之前一切都很好,但在那之后内存使用量突然飙升。

无论我增加多少内存限制(目前我不愿意给它超过 256MB),内存使用量会呈指数增长,直到达到新的限制。这很奇怪!

下面是一张表格,左边是key的数量,右边是内存使用情况。

10000     6668460
20000    12697828
30000    18917768
40000    25045068
41000    25658148
42000    26760304
43000    27350368
44000    27920400
45000    33438520
46000    77800344
47000   114203960
48000   161989660
49000   168419992
50000   206265572
Fatal error: Allowed memory size of 268435456 bytes exhausted

如您所见,在达到 44,000 个键之前,内存使用率一直保持在 620-660 字节/键。之后,内存使用量突然开始增加,直到在 50,000 个键时达到每个键超过 4KB。这很奇怪,因为我的键和值的大小总是相似的。

似乎我在数组中可以拥有的键数量达到了某种内部限制,超过了这个限制,一切都会变得非常低效。

如果我可以保持每个键 620-600 字节的内存使用量(考虑到使用数组的通常开销,这听起来很合理),我的整个数据集应该适合大约 620-600 字节。 64MB 内存,因此当我需要稍后在同一脚本中引用它时可以轻松访问。这是我第一次开始编写脚本时的假设。它是从 CLI 运行的维护脚本,所以偶尔使用 64MB 内存是可以的。

但是如果内存使用量像上面一样不断增加,我将别无选择,只能将键值数据集卸载到外部守护程序,如 Memcached、Redis 或 SQL 数据库,网络开销将大大减慢维护脚本。

到目前为止我尝试了什么:

  • 我尝试将二维数组展平为多个一维数组。不走运。
  • 我尝试将大数组拆分为多个较小的数组。不走运。
  • 我尝试完全不使用数组并将每个键都变成一个单独的变量。不走运。
  • 我不能使用SplFixedArray,因为我的键不是数字(并且不能转换为整数范围内的数字),并且数组需要是可变的。
  • 我宁愿不使用 Quickhash、Judy 或任何其他以 C 扩展形式编写的替代数组实现。
  • 抱歉,此脚本需要使用 PHP。不要问我为什么...

测试服务器是运行 Ubuntu 12.04 LTS,32 位,PHP 5.3.10-1ubuntu3.9 的虚拟机。

有什么想法吗?

  • 这是在较新版本的 PHP 中修复的问题吗?
  • 我应该将数据集提供给像 Memcached 这样的外部守护程序吗?

谢谢!

【问题讨论】:

    标签: php arrays memory memory-management


    【解决方案1】:

    我认为这是垃圾收集。在某些时候,您正在使用分配临时空间的操作,然后在艰苦的工作中无法释放这些空间,因此 php 会占用您的内存而没有真正的目的。

    当我遇到这个问题时,我终于得出结论,垃圾只在特定事件中被丢弃,比如退出函数。所以你应该尝试的是,在几个较小的步骤中完成这项工作,让你的变量在它们之间“放松”——创建一个一次只执行一千个元素的函数,然后再次调用它以从它停止的地方继续。

    希望这会有所帮助。

    【讨论】:

    • GC 似乎无法解释 44,000 个键后内存使用量的突然增加。如果有任何垃圾在while ($line = fgets($fp)) 循环内累积,内存使用量应该保持线性增加,而不是突然增加。
    • 是的,但我看不到您的循环,也看不到您阅读的文件。我不知道里面发生了什么。我可以想象一些事情,但不想在黑暗中拍摄。例如,如果有一个 $x 溢出的 fseek($x),它可能会导致这样的效果。
    • 抱歉,我无法发布循环的实际内容。但是对于它的价值,它只是一堆explode('|', trim($line)),其中第一部分用作键,其余部分连接成为值。没有对象,没有手动搜索,只有基本的字符串操作。总之,谢谢你的建议。
    • 是的,您正在解析它。现在无论那里发生什么,您都在创建 tmp 数组。试试我提到的。这可能是内存碎片的事情,这就是为什么我说它是 gc。例如,暴涨可能来自内存分配中的“不足”漏洞。
    • 是的,如果下一个临时变量大于前一个,就会出现问题。这会导致无法用下一个填充的孔。
    猜你喜欢
    • 1970-01-01
    • 2014-03-12
    • 2014-03-10
    • 1970-01-01
    • 2018-02-13
    • 2013-01-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多