获取PHP中对象的引用计数?

时间:2010-09-21 21:36:04

标签: php

我意识到对这个问题的下意识反应是“你不喜欢。”,但是听我说。

基本上我在SQL上的活动记录系统上运行,并且为了防止同一数据库行的重复对象,我在工厂中为每个当前加载的对象保留一个“数组”(使用自动增量'id'作为关键)。

问题在于,当我在奇怪的场合试图通过这个系统处理90,000多行时,PHP会遇到内存问题。这很容易通过每隔几百行运行一次垃圾收集来解决,但不幸的是,由于工厂存储了每个对象的副本 - PHP的垃圾收集不会释放任何这些节点。

我能想到的唯一解决方案是检查工厂中存储的对象的引用计数是否等于1(即没有引用该类),如果是这样,则将它们释放。这可以解决我的问题,但PHP没有引用计数方法? (除了debug_zval_dump,但那几乎没用)。

5 个答案:

答案 0 :(得分:6)

Sean的debug_zval_dump函数看起来会告诉你refcount,但实际上,从长远来看,refcount对你没有帮助。

您应该考虑使用有界数组作为缓存;像这样的东西:

<?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()返回您要存储的对象的一些唯一ID。 这依赖于PHP记住插入其哈希表的顺序这一事实;每次添加新元素时,它都会在逻辑上附加到数组中。

get()函数确保您访问的对象保留在数组的末尾,因此数组的前面将是最近最少使用的元素,这是我们要处理的对象当我们决定空间低时; array_shift()为我们做了这个。

此方法也称为最近使用的或MRU缓存,因为它会缓存最近使用的项目。我们的想法是,您更有可能访问最近访问过的项目,因此您可以随身携带它们。

你得到的是控制你保留的最大对象数量的能力,你不必在有意识地难以访问的php实现细节上找到它。

答案 1 :(得分:6)

似乎最好的答案仍然是获取引用计数,尽管debug_zval_dump和ob_start太难看了,我的应用程序中包含了hack。

相反,我编写了一个带有refcount()函数的简单PHP模块,可从以下位置获得:http://github.com/qix/php_refcount

答案 2 :(得分:3)

我知道这是一个非常古老的问题,但它仍然是搜索中的最佳结果,所以我认为我会给你&#34;正确的&#34;回答你的问题。

不幸的是,获得引用计数是一个雷区,但实际上你并不需要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以禁用它)。基本上,如果它太满或太多结果已过期,那么它将修剪任何空引用的缓存以腾出空间,并且只有在必须为了理智时才删除非空引用。

答案 3 :(得分:2)

是的,您绝对可以从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

答案 4 :(得分:1)

PHP 7.4现在具有WeakReference

要知道$obj是否被其他人引用,可以使用:

// 1: create a weak reference to the object
$wr = WeakReference::create($obj);

// 2: unset our reference
unset($obj);

// 3: test if the weak reference is still valid
$res = $wr->get();
if (!is_null($res)) {
    // a handle to the object is still held somewhere else in addition to $obj
    $obj = $res;
    unset($res);
}