PHP回调创建内部和延迟初始化的性能



首先,看看这个PHP 5.5.8代码,它使用Trait:实现类属性的延迟初始化

trait Lazy
{
private $__lazilyLoaded = [];
protected function lazy($property, $initializer)
{
echo "Initializer in lazy() parameters has HASH = "
. spl_object_hash($initializer) . "n";
if (!property_exists($this, $property)
|| !array_key_exists($property, $this->__lazilyLoaded))
{
echo "Initialization of property " . $property . "n";
$this->__lazilyLoaded[$property] = true;
$this->$property = $initializer();
}
return $this->$property;
}
}

class Test
{
use Lazy;
private $x = 'uninitialized';
public function x()
{
return $this->lazy('x', function(){
return 'abc';
});
}
}
echo "<pre>";
$t = new Test;
echo $t->x() . "n";
echo $t->x() . "n";
echo "</pre>";

输出如下:

uninitialized
Initializer in lazy() parameters has HASH = 000000001945aafc000000006251ed62
Initialization of property x
abc
Initializer in lazy() parameters has HASH = 000000001945aafc000000006251ed62
abc

以下是我的问题和我想讨论和改进的地方,但我不知道如何改进

  1. 根据报告的HASH值,初始化器函数可能只创建过一次。但实际上,并不能保证不同时存在于内存中的对象之间的唯一性。因此,这个问题仍然没有答案——初始化器是否只创建一次,我认为这对性能很重要,但我不确定。

  2. 现在实现它的方式不是很安全,因为如果我重构代码并将属性$x更改为其他内容,我可能会忘记将'x'值作为lazy()方法的第一个参数进行更改。我很乐意使用& $this->x作为第一个参数,但在lazy()函数中,我没有用于$__lazilyLoaded数组的键来跟踪哪些已经初始化,哪些还没有初始化。我该如何解决这个问题?使用散列作为密钥是不安全的,也不能为array($object, 'methodName')之类的回调生成散列

  3. 如果$this->x是私有属性,那么外部世界调用x()方法是安全的,但对于类的方法,访问原始$this->x属性仍然是不安全的,因为它可能尚未初始化。所以我想知道有没有更好的方法——也许我应该把所有的值都保存在特拉特的字段中?

全球目标是:

a) 快速-对于中小型软件应用程序来说足够可接受

b) 语法简洁-尽可能广泛地用于利用Lazy特性的类的方法中。

c) 模块化-如果对象仍然拥有自己的属性,那就太好了;我不喜欢用一个超全局存储延迟初始化值的想法。

感谢您的帮助、想法和提示!

因此,问题仍未得到解答-初始值设定项只创建一次,这对性能I很重要想一想,但我不确定。

好吧,闭包实例只创建一次。但无论如何,性能将不取决于闭包实例创建时间(因为它无关紧要),而是取决于闭包执行时间。

我很乐意使用&this->x作为第一个参数,但是在lazy()函数内部,我没有用于$__lazyLoaded的键数组,以跟踪哪些已初始化,哪些未初始化。怎样我能解决这个问题吗?使用hash作为密钥是不安全的,也不能为类似数组($object,'methodName')的回调生成

我可以提出以下解决方案:

<?php
trait Lazy
{
private $_lazyProperties = [];
private function getPropertyValue($propertyName) {
if(isset($this->_lazyProperties[$propertyName])) {
return $this->_lazyProperties[$propertyName];
}
if(!isset($this->_propertyLoaders[$propertyName])) {
throw new Exception("Property $propertyName does not have loader!");
}
$propertyValue = $this->_propertyLoaders[$propertyName]();
$this->_lazyProperties[$propertyName] = $propertyValue;
return $propertyValue;
}
public function __call($methodName, $arguments) {
if(strpos($methodName, 'get') !== 0) {
throw new Exception("Method $methodName is not implemented!");
}
$propertyName = substr($methodName, 3);
if(isset($this->_lazyProperties[$propertyName])) {
return $this->_lazyProperties[$propertyName];
}
$propertyInializerName = 'lazy' . $propertyName;
$propertyValue = $this->$propertyInializerName();
$this->_lazyProperties[$propertyName] = $propertyValue;
return $propertyValue;
}
}
/**
* @method getX()
**/
class Test
{
use Lazy;
protected function lazyX() {
echo("Initalizer called.rn");
return "X THE METHOD";
}
}
echo "<pre>";
$t = new Test;
echo $t->getX() . "n";
echo $t->getX() . "n";
echo "</pre>";

结果:

c:Temp>php test.php
<pre>X THE METHOD
X THE METHOD
</pre>
c:Temp>php test.php
<pre>Initalizer called.
X THE METHOD
X THE METHOD
</pre>
c:Temp>

你不可能总是被保护不忘记某件事,但当所有的事情都很接近时,你会更容易记住。因此,我建议将懒惰加载器实现为具有特定名称的相应类上的方法。为了提供自动完成,可以使用@method注释。在一个好的IDE中,注释中的重构方法名称将允许在所有项目中重命名方法。延迟加载函数将在同一个类中声明,因此重命名它也不是问题。

在我的例子中,通过用一个以"lazy"开头的名称声明一个函数,你们都声明了一个相应的访问器函数,名称以"get"开头,它是lazy-loader。

如果$this->x是私有属性,那么外部世界调用x()方法是安全的,但对于类的方法来说,调用访问原始的$this->x属性,因为它仍然可以未初始化。所以我想知道有没有更好的方法——也许我应该把所有的值都保存在特拉特的田地?

特性字段在使用特定特性的类中可用。甚至是私人领域。记住,这是组合,而不是继承。我认为最好创建私有特征数组字段,并将您的惰性属性存储在那里。无需为每个属性创建新字段。

但我不能说我喜欢整个计划。你能为你解释一下它的用途吗?也许我们可以找到更好的解决方案。

最新更新