你们中的一些人可能熟悉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类通过对父级的引用从主类获取其数据。因此,所有全局变量都设置为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.6秒,而此方法仅需0.1秒。
我会发布最终结果的更新,但这就是我目前所拥有的。希望这对那些从谷歌上偶然发现此页面的人有所帮助;)
str_replace吗?这对我来说似乎是你方法的一个普遍概念问题。为了解决这个问题,我的第一个想法是首先解析你的模板并将其分成几个部分,例如"static text <DYNAMIC CONTENT> static text"可以转换为array("static text ", new DynamicContent(), " static text")或类似的东西。然后,您可以使用此解析结果数据结构使用简单的echo命令构建页面。您甚至可以缓存已解析的数据以获得更好的模板处理性能。 - Hauke P.{?user}或<?content>将被填充。还有一些可以重复的区域 - 比如右侧的“热门问题”链接。部分内容可以以多种方式显示、隐藏和操作。其思想是将HTML和PHP分开。页面可以完全重新设计,添加和删除元素而不需要更改PHP中的任何一行代码。唯一的目的不是 str_replace,但它经常用于将 {?user} 操作为 "Joh Doe"。 - Willem van Schevikhoven{?user}字符串的数组,以及一个对应的Joh Doe数组。然后他们使用一个str_replace()函数。这意味着源代码和数组中的所有字符串只被分配一次。(复制数组只会增加每个字符串的引用计数。) - Alexis Wilke