如何在PHP中优化ArrayIterator实现?

时间:2017-03-09 14:11:13

标签: php optimization arrayiterator

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

2 个答案:

答案 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建议使用键入名称的数组而不是使用内部存储。以上内容适用于简单的直接替换。