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

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

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

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

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

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

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

<?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, которые намеренно труднодоступны.

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

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

Да, вы можете получить 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

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

К сожалению, получение ссылочного счета, как вы нашли, является минным полем, но на самом деле вам не нужно его для 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(); }); } } с 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(); }); } } с 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). В основном, если он становится слишком полным или слишком много результатов истекли, тогда он будет обрезать кеш любых пустых ссылок, чтобы освободить место, и удалять непустые ссылки, если это необходимо для здравомыслия.