php - 字符串分配中的累积内存使用 : $a = $a . $b vs $a .= $b

标签 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,因此不能“取消设置”旧实例。

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

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 类通过对其父类的引用从主类获取数据。因此所有全局变量都设置为页面类,页面类处理对数据库进行单个查询以获取所有已翻译的字符串等。

可重复性可能不是直截了当的。我正在使用 repeatable 来显示列表或可以重复多次的东西,比如新闻项目。内容变了,结构不变。因此,我将以下数组传递给 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 与 4M。我希望它在大页面中的比例更好,因为测试页非常简单。 与清理占用相当多果汁的字符串版本相比,速度增益非常显着。 0.1 秒与字符串版本的 0.6 秒。

我将发布最终结果的更新,但这就是我目前所拥有的。希望这对那些从谷歌偶然发现这个页面的人有所帮助;)

最佳答案

在您的具体示例中 ($page = str_replace($find, $replace, $page);) 将无法避免复制 $page。这适用于所有需要参数按值传递 的函数(无论是否与字符串相关)。然而,PHP 的垃圾回收应该定期释放那些未使用的副本。

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

此外,我还会确认您使用的是最新可用版本的 PHP 作为垃圾收集 is continuously improved upon .

关于php - 字符串分配中的累积内存使用 : $a = $a . $b vs $a .= $b,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25413472/

相关文章:

sql-server - 有人可以解释我的 Windows/SQL Server 内存使用情况吗

php - 用户登录(使用 session )问题

php - WordPress - 它可以工作,但我不知道为什么

php - 为什么必须在 C++ 中转义字符串文字中的反斜杠?

excel - 在 excel vba 中,将最后一个字符视为数字,对字符串进行排序的最佳方法是什么

c++ - 根据 MSVC++ 中的 unicode 设置自动在 std::string 和 std::wstring 之间切换?

javascript - 我如何模仿文本溢出 : ellipsis in Firefox?

memory - 速度比较eeprom-flash-sram

php - 使用 .val() 检查多文件上传文件的表单值

python - 尝试将 R 对象文件加载到 python numpy 数组中时出现内存错误