如何优化 PHP 中的 ArrayIterator 实现?
How to optimize an ArrayIterator implementation in PHP?
我有一个很长的 运行 PHP 守护程序,其中包含一个扩展 ArrayIterator
的集合 class。它包含一组自定义 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;
}
}
我用数组副本上的循环替换了迭代器方法调用。我认为这可以直接访问内部存储,因为 PHP 实现了写时复制。本机 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 的建议,即使用以名称为键的数组而不是使用内部存储。以上对于简单的直接替换很有效。
您的 find()
方法显然只是 returns 第一列 object 和查询的 $name
。在这种情况下,按名称索引数组可能是有意义的,例如将 object 的名称存储为键。然后你的查找变成了 O(1) 调用。
ArrayIterator
实施 ArrayAccess
。这意味着您可以像这样向 Collection 添加新项目:
$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
会自动按名称添加 $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);
}
}
我有一个很长的 运行 PHP 守护程序,其中包含一个扩展 ArrayIterator
的集合 class。它包含一组自定义 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;
}
}
我用数组副本上的循环替换了迭代器方法调用。我认为这可以直接访问内部存储,因为 PHP 实现了写时复制。本机 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 的建议,即使用以名称为键的数组而不是使用内部存储。以上对于简单的直接替换很有效。
您的 find()
方法显然只是 returns 第一列 object 和查询的 $name
。在这种情况下,按名称索引数组可能是有意义的,例如将 object 的名称存储为键。然后你的查找变成了 O(1) 调用。
ArrayIterator
实施 ArrayAccess
。这意味着您可以像这样向 Collection 添加新项目:
$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
会自动按名称添加 $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);
}
}