2

我有一个长期运行的 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;
    }
}
4

2 回答 2

2

您的find()方法显然只是返回带有查询的第一个 Column 对象$name。在这种情况下,按名称对数组进行索引可能是有意义的,例如,按对象的名称作为键存储对象。然后您的查找变成 O(1) 调用。

ArrayIterator实现ArrayAccess。这意味着您可以像这样将新项目添加到您的收藏中:

$collection = new ColumnCollection;
$collection[$someCollectionObject->name] = $someCollectionObject;

并通过方括号表示法检索它们:

$someCollectionObject = $collection["foo"];

如果您不想更改客户端代码,您可以简单地offsetSet在 ColumnCollection 中覆盖:

public function offsetSet($index, $newValue)
{
    if ($index === null && $newValue instanceof Column) {
        return parent::offsetSet($newValue->name, $newValue);
    }
    return parent::offsetSet($index, $newValue);
}

这样,doing$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);
    }
}
于 2017-03-09T15:21:01.863 回答
1

我用数组副本上的循环替换了迭代器方法调用。我认为这可以直接访问内部存储,因为 PHP 实现了写时复制。本机foreach比调用rewind(), valid(),current()和快得多next()。预先计算strtolowerColumn 对象也有帮助。这使性能从 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 的建议,即使用以名称为键的数组而不是使用内部存储。以上对于简单的直接替换来说效果很好。

于 2017-03-09T14:15:13.480 回答