有些人可能熟悉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。
我会发布最终结果的更新,但这就是我的目标。希望这可以帮助那些在谷歌这个页面上绊倒的人;)
答案 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万的峰值。