字符串赋值中的累积内存使用:$ a = $ a。 $ b vs $ a。= $ b

时间:2014-08-20 20:03:42

标签: php string memory

有些人可能熟悉PHP如何处理不同字符串情况下的内存。

当再次分配字符串时,它不会被“更新”,而是被克隆。至少这是我目前的理解。

$a = 'a';
$b = 'b';
$a = $a . $b; // uses sizeof($a)*2 + sizeof($b) bytes
$a .= $b; // uses sizeof($a) + sizeof($b) bytes

在我正在开发的模板引擎中,这意味着巨大的内存消耗。我使用超过128mb的内存用于页面字符串,实际上,这个内存小于512kb。这是因为字符串被反复复制。

简而言之,每次我做这样的事情时都会制作这些副本:

$page = str_replace($find, $replace, $page)

一般来说,是否存在不创建此克隆的解决方法?

我对这个标记了一点,这将产生相同的输出,但具有完全不同的内存消耗。第一个消耗大量内存,但第二个只消耗实际字符串大小。

$iterations = 100000;
$a = 'a';
$b = 'b';
echo "start peak memory usage " . (memory_get_peak_usage()/1024).'k<br>';
echo "start current memory usage " . (memory_get_usage()/1024).'k<br>';

for($i = 0; $i<$iterations; $i++) {
    $a = $a . $b;
}
echo "end peak memory usage " . (memory_get_peak_usage()/1024).'k<br>';
echo "end current memory usage " . (memory_get_usage()/1024).'k<br>';

$iterations = 100000;
$a = 'a';
$b = 'b';
echo "start peak memory usage " . (memory_get_peak_usage()/1024).'k<br>';
echo "start current memory usage " . (memory_get_usage()/1024).'k<br>';

for($i = 0; $i<$iterations; $i++) {
    $a .= $b;
}
echo "end peak memory usage " . (memory_get_peak_usage()/1024).'k<br>';
echo "end current memory usage " . (memory_get_usage()/1024).'k<br>';

因此,就模板引擎而言,避免不必要的内存消耗的最佳方法是什么?在开发环境中它不是问题,但在生产中它可能成为可扩展性问题

自然速度对我来说也是一个问题,因此替代方案应与此速度大致相同。

最后,我认为这也与变量范围有关。随意纠正我,因为我不是专业人士。我的理解是,当一个函数或方法结束时,PHP垃圾收集器(?)“取消设置”变量,但在我的情况下,我们正在处理的$page自然存在于脚本的整个持续时间内,因为它是一个类变量,并被访问$this->page,因此旧实例不能“取消设置”。

编辑16.10.2014: 为了跟进这个问题,我做了一些测试,并且倾向于将页面爆炸成部分提到的解决方案。这是一个粗略,简单的结构草图,然后向下解释。

class PageObjectX {
    $_parent;
    __constructor(&$parent) { $this->_parent = $parent; }
    /* has a __toString() method, handles how the variable/section is outputted. */
}

class Page {
    $_parts;
    $_source_parts;
    $_variables;

    public function __constructor($s) {
        $this->_source_parts = preg_split($s, ...);
        foreach($this->_source_parts as $part) {
            $this->_parts[] = new PageObject($this, ...); }
    }

    public function ___toString() { return implode('', $this->_parts); }

    public function setVariables($k, $v) { $this->_variables[$k] = $v; }
}

我所做的是将模板字符串分解为一系列部件。常规字符串,变量,从数据库获取的字符串以及区域/部分。 零件阵列管理封装在Page类中。该数组具有对象作为元素: PageVariable,PageString,PageRepeatable,PagePlaintext。每个对象都提供了一个toString()方法,它允许不同类型的部件控制它们的显示方式,并有助于使类保持相当小和易于管理。在某种程度上让我觉得“干净”。

每个PageN -class通过引用它的父类从主类中获取它的数据。所以所有全局变量都设置为Page类,页面类处理对数据库进行单个查询以获取所有已翻译的字符串等等。

重复可能不是直截了当的。我使用可重复显示列表或可以重复多次的内容,如新闻项目。内容发生变化,结构不然。所以我将以下数组传递给Page,当可重复名称'news'查找它的数据时,它会获取两个新闻项的数据。

$regions['news'][0]['news title'] = 'Todays news';
$regions['news'][0]['news desc'] = 'The united nations...';
$regions['news'][1]['news title'] = 'Yesterdays news';
$regions['news'][1]['news desc'] = 'Meanwhile in Afghanistan the rebels...';

如果页面元素没有数据,则很容易将其排除在__toString()中。这减少了对模板中未使用部件进行清理的需要。

这种方法的整体表现似乎相当不错。在初始比较中,内存消耗约为一半。 2M vs 4M。由于测试页面非常简单,我认为它在大页面中的比例要好一些。 与字符串版本相比,速度增加非常显着,其中清理占用了相当多的果汁。字符串版本为0.1s vs. 0.6s。

我会发布最终结果的更新,但这就是我的目标。希望这可以帮助那些在谷歌这个页面上绊倒的人;)

2 个答案:

答案 0 :(得分:2)

在您的具体示例($page = str_replace($find, $replace, $page);)中,无法避免复制$page。这适用于要求通过值传递的所有函数(与字符串相关或不相关)。但是,PHP的垃圾收集应该定期释放那些未使用的副本。

如果您仍然遇到过多的内存使用情况,我强烈建议您检查一下代码。确保变量具有明确定义的范围,并且仅存储所需的数据。有一些工具可以帮助诊断PHP内存使用情况,例如php-memprof

此外,我还要验证您是否使用最新的PHP版本作为垃圾回收is continuously improved upon

答案 1 :(得分:0)

您使用哪种系统?对我来说,它并没有那么大的差异:
用简单的脚本: 峰值325.1k,电流218.7k,峰值219.6k,电流218.7k
在一个类的功能: 峰值327.2k,电流220.8k,峰值221.8k,电流220.8k

我预计峰值的差异可能来自上一次操作,其中$ a连接在一起,旧的$ a值仍在使用中。这可以解释近10万的峰值。