【问题标题】:How to optimize an ArrayIterator implementation in PHP?如何优化 PHP 中的 ArrayIterator 实现?
【发布时间】:2017-07-30 13:38:33
【问题描述】:

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

【问题讨论】:

    标签: php optimization arrayiterator


    【解决方案1】:

    我用数组副本上的循环替换了迭代器方法调用。我认为这可以直接访问内部存储,因为 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 的建议,即使用以名称为键的数组而不是使用内部存储。以上对于简单的直接替换来说效果很好。

    【讨论】:

      【解决方案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);
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2016-03-31
        • 2012-09-06
        • 1970-01-01
        • 2018-12-31
        • 2015-08-14
        • 2020-10-07
        • 2019-06-23
        • 2016-11-17
        • 2023-03-13
        相关资源
        最近更新 更多