如何优化 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);
    }
}