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

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

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

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

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

 Jake N21 сент. 2010 г., 23:50
Вы просто храните идентификатор в массиве или во всем объекте?
 Josh21 сент. 2010 г., 23:56
Идентификатор является ключом, весь объект 'reference' является значением: static $ cache = array (1122 => новый клиент (1222), 1864 => новый клиент (1864), ...)

Ответы на вопрос(4)

вы точно можете получить пересчет из PHP. К сожалению, рефконт не так легко получить, потому что у него нет встроенного в 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

 Josh21 сент. 2010 г., 23:59
Это на самом деле невозможно, в основном потому, что объект не может напрямую запрашивать значение - я могу взять ссылку на то, что он ранее возвратил ... в любом случае, PHP хранит для меня счетчик ссылок - если я не смогу просто использовать это?
 Josh22 сент. 2010 г., 00:22
Я знал о возможности использовать debug_zval_dump, хотя это немного сумасшедший. Эта функция выводит тысячи строк данных о моих классах (все их дети и соответствующие данные). Когда я начинаю использовать это, я сталкиваюсь с ошибками времени выполнения - и хуже, чем когда сценарий завершается, он может иногда выгружать часть данных debug_zval пользователю, поскольку они записываются в выходной буфер.
 Sean22 сент. 2010 г., 00:31
Да, он хочет, чтобы вы были близки и рассказывали историю жизни каждого указателя. Кроме этого, у меня нет патронов. Может быть, сделать массив, ключи которого являются чем-то идентифицируемым для каждого объекта Если ключ существует, игнорируйте объект.
 Pacerier07 авг. 2013 г., 23:03
@Sean, ваша функция не работает для массивов, объектов и циклических ссылок .......
 Sean22 сент. 2010 г., 00:13
Да, ты прав. Я хотел избежать PREG, так как вы обрабатываете 90 тыс. Записей. Я поместил время выполнения до простоты кодирования. Я обновил свой ответ, чтобы отразить способ получения пересчета.

как будто она скажет вам счет, но на самом деле этот счет не поможет вам в долгосрочной перспективе.

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

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

 Wez Furlong28 сент. 2010 г., 05:58
Да, это было бы проблемой, но заявленная проблема с наличием 90 КБ объектов в памяти состояла в том, что вы не могли поместить их в память.
 Josh27 сент. 2010 г., 15:27
Это хорошая идея, хотя она не решает основной проблемы. Если на объект в кеше ссылаются в другом месте, назовите его $ o1; затем объект удаляется из кеша с помощью MRU; и затем снова загружается как $ o2. Теперь $ o1! = $ O2, что неудобно, но более того, если я установлю свойство на $ o1, оно не будет передано $ o2; $ o1-> credit + = 50; $ O1-> Save (); $ o2-> credit + = 10; $ O2-> Save (); - если бы это был тот же объект -> кредит был бы увеличен на 60, но вместо этого он перезаписывается.
 Wez Furlong28 сент. 2010 г., 06:00
тьфу, энтер отправляет комментарий, это отстой. В любом случае, цель кэша MRU состоит в том, что вы всегда запрашиваете свой объект через кеш и позволяете ему удалять ненужные элементы. Если вам действительно необходимо иметь в памяти одновременно 90 тыс. Объектов, то вам не помогут ни MRU, ни трюки с перерасчетами - вам нужно купить больше памяти.

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

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

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

 Emil Vikström28 дек. 2017 г., 09:22
Или просто используйтеWeakMap из того же расширения PECL! Он может использоваться как массив напрямую, без переноса каждого элемента в WeakRef.
Решение Вопроса

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

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

 ircmaxell31 янв. 2013 г., 13:52
Просто имейте в виду, что это всегда будет возвращать refcount 1 для ссылочной переменной (из-за копирования при записи).
 Pacerier07 авг. 2013 г., 23:06
@ircmaxell, что вы подразумеваете под"ссылочная переменная"? php.net/manual/en/function.debug-zval-dump.php#example-5193 кажется, говорит, что пересчет 2 очень возможно.
 Sean05 февр. 2014 г., 16:30
Ницца. Я вижу, вы выбрали подход «Если вам не нравится, как работает двигатель, измените его» :-)
 ircmaxell08 авг. 2013 г., 04:19
@Pacerier: это возможно. Для переменной со ссылкой. Связанный код (расширение) принимает переменную по значению. Это означает, что если исходная переменная является ссылкой (is_ref установлен в1), переменная будет разветвлена ​​до вызова функции расширения. Следовательно, если переменная является ссылкой до входа в эту функцию, возвращаемая ссылка будетвсегда быть1Спасибо, копируй-пиши ...

Ваш ответ на вопрос