我有一个长期运行的PHP守护程序,其中包含一个扩展ArrayIterator
的集合类。这包含一组自定义Column
个对象,通常少于1000个。通过xdebug
探查器运行它我发现我的find
方法消耗了 35%周期。
如何以优化的方式在内部迭代项目?
class ColumnCollection extends \ArrayIterator
{
public function find($name)
{
$return = null;
$name = trim(strtolower($name));
$this->rewind();
while ($this->valid()) {
/** @var Column $column */
$column = $this->current();
if (strtolower($column->name) === $name) {
$return = $column;
break;
}
$this->next();
}
$this->rewind();
return $return;
}
}
答案 0 :(得分:2)
您的find()
方法显然只返回带有查询$name
的第一个Column对象。在这种情况下,按名称索引数组可能是有意义的,例如以对象的名称存储对象。然后你的查找变成O(1)调用。
ArrayIterator
实施ArrayAccess
。这意味着您可以像这样向集合添加新项目:
$collection = new ColumnCollection;
$collection[$someCollectionObject->name] = $someCollectionObject;
并通过方括号表示法检索它们:
$someCollectionObject = $collection["foo"];
如果您不想更改客户端代码,只需在ColumnCollection中覆盖offsetSet
:
public function offsetSet($index, $newValue)
{
if ($index === null && $newValue instanceof Column) {
return parent::offsetSet($newValue->name, $newValue);
}
return parent::offsetSet($index, $newValue);
}
这样,执行$collection[] = $column
会自动按名称添加$列。有关演示,请参阅http://codepad.org/egAchYpk。
如果您使用append()
方法添加新元素,只需将其更改为:
public function append($newValue)
{
parent::offsetSet($newValue->name, $newValue);
}
但是,ArrayAccess
比本机数组访问慢,因此您可能希望将ColumnCollection更改为以下内容:
class ColumnCollection implements IteratorAggregate
{
private $columns = []; // or SplObjectStorage
public function add(Column $column) {
$this->columns[$column->name] = $column;
}
public function find($name) {
return isset($this->data[$name]) ? $this->data[$name] : null;
}
public function getIterator()
{
return new ArrayIterator($this->data);
}
}
答案 1 :(得分:1)
我用数组副本上的循环替换了迭代器方法调用。我认为这可以直接访问内部存储,因为PHP实现了copy-on-write。原生foreach
比调用rewind()
,valid()
,current()
和next()
要快得多。还预先计算了Column对象上的strtolower
。这使得性能从35%的周期下降到0.14%。
public function find($name)
{
$return = null;
$name = trim(strtolower($name));
/** @var Column $column */
foreach ($this->getArrayCopy() as $column) {
if ($column->nameLower === $name) {
$return = $column;
break;
}
}
return $return;
}
还尝试使用@Gordon建议使用键入名称的数组而不是使用内部存储。以上内容适用于简单的直接替换。