Подтвердить что ты не робот

Получить ссылочный счетчик объекта в PHP?

Я понимаю, что реакция на коленный рефлекс на этот вопрос заключается в том, что "вы нет", но выслушайте меня.

В основном я запускаю систему активной записи на SQL и для предотвращения дублирования объектов для одной и той же строки базы данных я сохраняю "массив" в factory с каждым загруженным в данный момент объектом (с использованием автоинкремента) id 'в качестве ключа).

Проблема в том, что когда я пытаюсь обработать 90 000+ строк через эту систему в нечетном случае, PHP сталкивается с проблемами памяти. Это очень легко решить, запустив сбор мусора каждые несколько сотен строк, но, к сожалению, поскольку factory хранит копию каждого объекта - сборка мусора PHP не освобождает ни один из этих узлов.

Единственное решение, о котором я могу думать, - проверить, равен ли ссылочный счет объектов, хранящихся в factory, один (т.е. ничто не ссылается на этот класс), и если да, освободите их. Это решит мою проблему, однако PHP не имеет метода подсчета ссылок? (кроме debug_zval_dump, но это почти невозможно).

4b9b3361

Ответ 1

Кажется, что лучшим ответом по-прежнему был подсчет ссылок, хотя debug_zval_dump и ob_start были слишком уродливыми, чтобы включить в мое приложение.

Вместо этого я закодировал простой PHP-модуль с функцией refcount(), доступной по адресу: http://github.com/qix/php_refcount

Ответ 2

Функция Sean debug_zval_dump выглядит так, как будто она выполнит задание об отказе, но действительно, пересчет не поможет вам в конечном итоге.

Вы должны использовать ограниченный массив для работы в качестве кеша; что-то вроде этого:

<?php
class object_cache {
   var $objs = array();
   var $max_objs = 1024; // adjust to fit your use case

   function add($obj) {
      $key = $obj->getKey();
      // remove it from its old position
      unset($this->objs[$key]);
      // If the cache is full, retire the eldest from the front
      if (count($this->objs) > $this->max_objs) {
         $dead = array_shift($this->objs);
         // commit any pending changes to db/disk
         $dead->flushToStorage();
      }
      // (re-)add this item to the end
      $this->objs[$key] = $obj;
   }

   function get($key) {
      if (isset($this->objs[$key])) {
          $obj = $this->objs[$key];
          // promote to most-recently-used
          unset($this->objs[$key]);
          $this->objs[$key] = $obj;
          return $obj;
      }
      // Not cached; go and get it
      $obj = $this->loadFromStorage($key);
      if ($obj) {
          $this->objs[$key] = $obj;
      }
      return $obj;
   }
}

Здесь getKey() возвращает уникальный идентификатор объекта, который вы хотите сохранить. Это зависит от того, что PHP запоминает порядок вставки в свои хэш-таблицы; каждый раз, когда вы добавляете новый элемент, он логически добавляется к массиву.

Функция get() гарантирует, что объекты, к которым вы обращаетесь, хранятся в конце массива, поэтому фронт массива будет последним использованным элементом, и это тот, который мы хотим избавить когда мы решаем, что пространство низкое; array_shift() делает это для нас.

Этот подход также известен как самый недавно используемый или кеш MRU, поскольку он кэширует самые последние использованные элементы. Идея состоит в том, что вы с большей вероятностью получите доступ к элементам, к которым вы обращались совсем недавно, поэтому вы их поддерживаете.

То, что вы получаете здесь, - это способность управлять максимальным количеством объектов, которые вы держите вокруг, и вам не нужно мешать в деталях реализации php, которые намеренно труднодоступны.

Ответ 3

Да, вы можете получить refcount из PHP. К сожалению, refcount не так легко получить, потому что у него нет аксессора, встроенного в PHP. Это нормально, потому что у нас есть PREG!

<?php
function refcount($var)
{
    ob_start();
    debug_zval_dump($var);
    $dump = ob_get_clean();

    $matches = array();
    preg_match('/refcount\(([0-9]+)/', $dump, $matches);

    $count = $matches[1];

    //3 references are added, including when calling debug_zval_dump()
    return $count - 3;
}
?>

Источник: PHP.net

Ответ 4

Я знаю, что это очень старая проблема, но она по-прежнему стала лучшим результатом поиска, поэтому я подумал, что дам вам "правильный" ответ на вашу проблему.

К сожалению, подсчет ссылок, как вы нашли, является минным полем, но на самом деле вам это не нужно для 99% проблем, которые могут ему понадобиться.

То, что вы действительно хотите использовать, это класс WeakRef, просто он содержит слабую ссылку на объект, срок действия которого истекает если нет других ссылок на объект, что позволяет его очистить сборщик мусора. Он должен быть установлен через PECL, но это действительно то, что вы хотите в каждой установке PHP.

Вы бы использовали его так (прошу простить любые опечатки):

class Cache {
    private $max_size;
    private $cache = [];
    private $expired = 0;

    public function __construct(int $max_size = 1024) { $this->max_size = $max_size; }

    public function add(int $id, object $value) {
        unset($this->cache[$id]);
        $this->cache[$id] = new WeakRef($value);

        if ($this->max_size > 0) && ((count($this->cache) > $this->max_size)) {
            $this->prune();
            if (count($this->cache) > $this->max_size) {
                array_shift($this->cache);
            }
        }
    }

    public function get(int $id) { // ?object
        if (isset($this->cache[$id])) {
            $result = $this->cache[$id]->get();
            if ($result === null) {
                // Prune if the cache gets too empty
                if (++$this->expired > count($this->cache) / 4) {
                    $this->prune();
                }
            } else {
                // Move to the end so it is culled last if non-empty
                unset($this->cache[$id]);
                $this->cache[$id] = $result;
            }
            return $result;
        }
        return null;
    }

    protected function prune() {
        $this->cache = array_filter($this->cache, function($value) {
            return $value->valid();
        });
    }
}

Это версия overkill, которая использует как слабые ссылки, так и максимальный размер (для этого отключить -1). В основном, если он становится слишком полным или слишком много результатов было истекло, тогда он будет обрезать кеш любых пустых ссылок, чтобы сделать пространство, и удалять непустые ссылки, если это нужно для здравого смысла.