Как оптимизировать реализацию ArrayIterator в PHP?

У меня есть давний 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

Решение

Ваш 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);
}

Таким образом, делая $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);
}
}
2

Другие решения

Я заменил вызовы метода итератора на цикл в копии массива. Я предполагаю, что это дает прямой доступ к внутреннему хранилищу, так как PHP реализует копирование при записи. Родной foreach намного быстрее звонка rewind(), valid(), current(), а также next(), Также предварительный расчет strtolower на объекте Column помог. Это получил производительность по сравнению с 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 использовать массив с ключом по имени вместо использования внутреннего хранилища. Вышесказанное хорошо работает для простой замены.

1

По вопросам рекламы [email protected]