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

时间:2022-06-08 16:57:58

I realize the knee-jerk response to this question is that "you dont.", but hear me out.

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

Basically I am running on an active-record system on a SQL, and in order to prevent duplicate objects for the same database row I keep an 'array' in the factory with each currently loaded object (using an autoincrement 'id' as the key).

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

The problem is that when I try to process 90,000+ rows through this system on the odd occasion, PHP hits memory issues. This would very easily be solved by running a garbage collect every few hundred rows, but unfortunately since the factory stores a copy of each object - PHP's garbage collection won't free any of these nodes.

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

The only solution I can think of, is to check if the reference count of the objects stored in the factory is equal to one (i.e. nothing is referencing that class), and if so free them. This would solve my issue, however PHP doesn't have a reference count method? (besides debug_zval_dump, but thats barely usable).

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

4 个解决方案

#1


6  

Sean's debug_zval_dump function looks like it will do the job of telling you the refcount, but really, the refcount doesn't help you in the long run.

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

You should consider using a bounded array to act as a cache; something like this:

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

<?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;
   }
}

Here, getKey() returns some unique id for the object that you want to store. This relies on the fact that PHP remembers the order of insertion into its hash tables; each time you add a new element, it is logically appended to the array.

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

The get() function makes sure that the objects you access are kept at the end of the array, so the front of the array is going to be least recently used element, and this is the one that we want to dispose of when we decide that space is low; array_shift() does this for us.

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

This approach is also known as a most-recently-used, or MRU cache, because it caches the most recently used items. The idea is that you are more likely to access the items that you have accessed most recently, so you keep them around.

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

What you get here is the ability to control the maximum number of objects that you keep around, and you don't have to poke around at the php implementation details that are deliberately difficult to access.

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

#2


6  

It seems like the best answer was still getting the reference count, although debug_zval_dump and ob_start was too ugly a hack to include in my application.

看起来最好的答案仍然是获取引用计数,虽然debug_zval_dump和ob_start太难看了,但是要包含在我的应用程序中。

Instead I coded up a simple PHP module with a refcount() function, available at: http://github.com/qix/php_refcount

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

#3


2  

Yes, you can definitely get the refcount from PHP. Unfortunately, the refcount isn't easily gotten for it doesn't have an accessor built into PHP. That's ok, because we have PREG!

是的,你绝对可以从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;
}
?>

Source: PHP.net

#4


2  

I know this is a very old issue, but it still came up as a top result in a search so I thought I'd give you the "correct" answer to your problem.

我知道这是一个非常古老的问题,但它仍然是搜索的最佳结果,所以我想我会给你“问题的正确”答案。

Unfortunately getting the reference count as you've found is a minefield, but in reality you don't need it for 99% of problems that might want it.

不幸的是,你发现引用计数是一个雷区,但实际上你并不需要99%的问题可能需要它。

What you really want to use is the WeakRef class, quite simply it holds a weak reference to an object, which will expire if there are no other references to the object, allowing it to be cleaned up by the garbage collector. It needs to be installed via PECL, but it really is something you want in every PHP installation.

你真正想要使用的是WeakRef类,它非常简单地拥有一个对象的弱引用,如果没有对该对象的其他引用,它将过期,允许它被垃圾收集器清理。它需要通过PECL安装,但它确实是你想要的每个PHP安装。

You would use it like so (please forgive any typos):

你会这样使用它(请原谅任何错别字):

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();
        });
    }
}

This is the overkill version that uses both weak references and a max size (set it to -1 to disable that). Basically if it gets too full or too many results were expired, then it will prune the cache of any empty references to make space, and only drop non-empty references if it has to for sanity.

这是使用弱引用和最大大小的过度杀伤版本(将其设置为-1以禁用它)。基本上,如果它太满或太多结果已过期,那么它将修剪任何空引用的缓存以腾出空间,并且如果必须为了理智,则仅删除非空引用。

#1


6  

Sean's debug_zval_dump function looks like it will do the job of telling you the refcount, but really, the refcount doesn't help you in the long run.

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

You should consider using a bounded array to act as a cache; something like this:

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

<?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;
   }
}

Here, getKey() returns some unique id for the object that you want to store. This relies on the fact that PHP remembers the order of insertion into its hash tables; each time you add a new element, it is logically appended to the array.

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

The get() function makes sure that the objects you access are kept at the end of the array, so the front of the array is going to be least recently used element, and this is the one that we want to dispose of when we decide that space is low; array_shift() does this for us.

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

This approach is also known as a most-recently-used, or MRU cache, because it caches the most recently used items. The idea is that you are more likely to access the items that you have accessed most recently, so you keep them around.

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

What you get here is the ability to control the maximum number of objects that you keep around, and you don't have to poke around at the php implementation details that are deliberately difficult to access.

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

#2


6  

It seems like the best answer was still getting the reference count, although debug_zval_dump and ob_start was too ugly a hack to include in my application.

看起来最好的答案仍然是获取引用计数,虽然debug_zval_dump和ob_start太难看了,但是要包含在我的应用程序中。

Instead I coded up a simple PHP module with a refcount() function, available at: http://github.com/qix/php_refcount

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

#3


2  

Yes, you can definitely get the refcount from PHP. Unfortunately, the refcount isn't easily gotten for it doesn't have an accessor built into PHP. That's ok, because we have PREG!

是的,你绝对可以从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;
}
?>

Source: PHP.net

#4


2  

I know this is a very old issue, but it still came up as a top result in a search so I thought I'd give you the "correct" answer to your problem.

我知道这是一个非常古老的问题,但它仍然是搜索的最佳结果,所以我想我会给你“问题的正确”答案。

Unfortunately getting the reference count as you've found is a minefield, but in reality you don't need it for 99% of problems that might want it.

不幸的是,你发现引用计数是一个雷区,但实际上你并不需要99%的问题可能需要它。

What you really want to use is the WeakRef class, quite simply it holds a weak reference to an object, which will expire if there are no other references to the object, allowing it to be cleaned up by the garbage collector. It needs to be installed via PECL, but it really is something you want in every PHP installation.

你真正想要使用的是WeakRef类,它非常简单地拥有一个对象的弱引用,如果没有对该对象的其他引用,它将过期,允许它被垃圾收集器清理。它需要通过PECL安装,但它确实是你想要的每个PHP安装。

You would use it like so (please forgive any typos):

你会这样使用它(请原谅任何错别字):

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();
        });
    }
}

This is the overkill version that uses both weak references and a max size (set it to -1 to disable that). Basically if it gets too full or too many results were expired, then it will prune the cache of any empty references to make space, and only drop non-empty references if it has to for sanity.

这是使用弱引用和最大大小的过度杀伤版本(将其设置为-1以禁用它)。基本上,如果它太满或太多结果已过期,那么它将修剪任何空引用的缓存以腾出空间,并且如果必须为了理智,则仅删除非空引用。