【问题标题】:Does unsetting array values during iterating save on memory?迭代期间取消设置数组值是否会节省内存?
【发布时间】:2011-06-08 03:10:44
【问题描述】:

这是一个简单的编程问题,因为我不了解 PHP 如何在 foreach 循环期间处理数组复制和取消设置。就像这样,我有一个来自外部源的数组,它以我想要更改的方式格式化。一个简单的例子是:

$myData = array('Key1' => array('value1', 'value2'));

但我想要的是这样的:

$myData = array([0] => array('MyKey' => array('Key1' => array('value1', 'value2'))));

所以我采用第一个$myData 并将其格式化为第二个$myData。我对我的格式化算法完全没问题。我的问题在于找到一种节省内存的方法,因为这些数组可能有点笨拙。因此,在我的foreach 循环中,我将当前数组值复制到新格式中,然后从原始数组中取消设置我正在使用的值。例如:

$formattedData = array();
foreach ($myData as $key => $val) {
    // do some formatting here, copy to $reformattedVal

    $formattedData[] = $reformattedVal;

    unset($myData[$key]);
}

在这里致电unset() 是个好主意吗?即,由于我已经复制了数据并且不再需要原始值,它是否可以节省内存?或者,PHP是否会自动垃圾收集数据,因为我没有在任何后续代码中引用它?

代码运行良好,到目前为止,我的数据集的大小可以忽略不计,无法测试性能差异。我只是不知道我是否会为以后出现一些奇怪的错误或 CPU 命中做好准备。

感谢您提供任何见解。
-sR

【问题讨论】:

  • 除非您的数据结构非常庞大(占 RAM 的很大一部分),否则您无需担心任何事情。如果php用完一个menory它会告诉你,你可以在php.ini中增加它。
  • 这是一个愚蠢的想法。您刚刚介绍了一个副作用,稍后可能会因某些 微优化 而被遗忘:-/ 不,PHP(也不是我所知道的任何其他标准 GC 语言)能够生成数据contained 在可用于回收的数据结构中,而对 container 的引用存在(这不包括软/弱引用等概念)。 unset 可以/将导致 PHP GC 启动,但是由于释放的内存压力而获得的实际性能(如果有的话)并非易事。如果这成为问题,然后解决它。
  • 这个数组的大小是多少?
  • 感谢您的回复。我想知道我是否无缘无故地进行了微优化,所以我很高兴被称为愚蠢。

标签: php foreach unset memory-optimization


【解决方案1】:

除非您通过引用访问元素,否则取消设置将不会执行任何操作,因为您无法在迭代器中更改数组。

也就是说,修改您正在迭代的集合通常被认为是不好的做法 - 更好的方法是将源数组分解成更小的块(一次只加载一部分源数据)和处理这些,随时取消设置每个整个数组“块”。

【讨论】:

  • “取消设置不会做任何事情” - 这是不正确的,他的代码将从原始数组中取消设置变量
  • @Andy 我明确指出,如果它不通过引用访问,它不会做任何事情。来自 PHP 手册 - “除非引用数组,否则 foreach 对指定数组的副本而不是数组本身进行操作。”
  • 正确,但你会注意到他的代码是从原始数组中取消设置变量,而不是副本。
  • @Andy 你没有得到的是原始和副本共享内存,从原始取消设置不会释放内存,因为副本仍然包含引用。
【解决方案2】:

使用& 运算符在foreach 循环中使用对变量的引用。这样可以避免在内存中复制数组以供 foreach 迭代。

编辑: 正如Artefacto 指出的那样,取消设置变量只会减少对原始变量的引用次数,因此节省的内存仅在指针上而不是变量的值上。奇怪的是,使用引用实际上会增加总内存使用量,因为推测该值被复制到新的内存位置而不是被引用。

除非数组被引用, foreach 在副本上运行 指定数组而不是数组 本身。 foreach 有一些副作用 在数组指针上。不要依赖 期间或之后的数组指针 foreach 而不重置它。

使用memory_get_usage() 确定您正在使用多少内存。

有一篇很好的关于内存使用和分配的文章here

这是查看内存分配的有用测试代码 - 尝试取消注释行以查看不同情况下的总内存使用情况。

echo memory_get_usage() . PHP_EOL;
$test = $testCopy = array();
$i = 0;
while ($i++ < 100000) {
    $test[] = $i;
}
echo memory_get_usage() . PHP_EOL;
foreach ($test as $k => $v) {
//foreach ($test as $k => &$v) {
    $testCopy[$k] = $v;
    //unset($test[$k]);
}
echo memory_get_usage() . PHP_EOL;

【讨论】:

  • 感谢您的回复和有用的信息。使用您的代码示例,我看到使用 unset() 时内存使用量大约有 5MB 差异。此外,当在 foreach 中引用数组时(而不使用 unset()),内存使用率会上升。很有趣……虽然花了足够的时间。
【解决方案3】:

如果在“格式化”中的任何时候您执行以下操作:

$reformattedVal['a']['b'] = $myData[$key];

然后执行unset($myData[$key]); 与内存无关,因为您只是减少了变量的引用计数,该变量现在存在于两个位置(在$myData[$key]$reformattedVal['a']['b'] 内部)。实际上,您节省了在原始数组中索引变量的内存,但这几乎没有。

【讨论】:

  • 这是不正确的 - 默认情况下变量不是通过引用传递的,只有对象是
  • @Andy 首先没有人传递任何东西(你看到任何函数吗?),其次,在$a = $b 的赋值中,在正常情况下,两个变量之间不会复制内存(PHP 实现了 copy-on -write),即使它的行为就像内存已被复制。
  • 我的错误,我打算分配而不是传递参数。我在答案中添加了测试代码,以演示使用 unset() 节省的内存。
  • @Andy 我并不是说你不节省内存。但是,如果将原始数据复制到最终数据的子数组中,您节省的内存不是(可能是巨大的)变量占用的内存,而是在原始数组中索引它所占用的内存(只有几个字节)。跨度>
  • 我明白你的意思,并且(作为事后的想法!)Apache 在脚本使用期间不释放内存变得没有意义。在这种情况下,仅使用参考是有益的,我会更新我的答案。
【解决方案4】:

请记住rules of Optimization Club

  1. 优化俱乐部的第一条规则是,你不要优化。
  2. 优化俱乐部的第二条规则是,没有衡量就不要优化。
  3. 如果您的应用运行速度快于底层传输协议,则优化结束。
  4. 一次一个因素。
  5. 没有市场机器人,没有市场机器人时间表。
  6. 只要需要,测试就会继续进行。
  7. 如果这是您在优化俱乐部的第一晚,您必须编写一个测试用例。

规则#1 和#2 在这里特别重要。除非你知道你需要优化,除非你已经衡量了需要优化,否则不要这样做。添加 unset 将增加运行时命中,并使未来的程序员为什么要这样做。

别管它。

【讨论】:

  • “Marketroid”是指营销部门的人。从更广泛的意义上说,不要让非技术人员对你的程序应该能够做的事情发号施令。
【解决方案5】:

在循环中处理文本 (xml) 文件的行时内存不足。对于任何有类似情况的人,这对我有用:

while($data = array_pop($xml_data)){
     //process $data
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-04-02
    • 1970-01-01
    • 2021-07-11
    • 1970-01-01
    • 1970-01-01
    • 2017-11-20
    • 1970-01-01
    相关资源
    最近更新 更多