2010-09-21 81 views
16

我意识到这个问题的膝盖反应是“你不要。”,但是请听我说。获取PHP中对象的引用计数?

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

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

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

+0

你只是将ID存储在数组或整个对象中? – 2010-09-21 21:50:27

+0

ID是关键,整个对象'reference'是value:static $ cache = array(1122 => new Client(1222),1864 => new Client(1864),...) – Josh 2010-09-21 21:56:49

回答

6

这似乎是最好的答案仍然得到的引用计数,尽管debug_zval_dump和ob_start是太丑陋了一个黑客在我的应用程序包括。

相反,我在编写了一个简单的PHP模块引用计数()函数,可得:http://github.com/qix/php_refcount

+0

只要注意,这将总是返回1引用变量的refcount(由于写时复制)。 – ircmaxell 2013-01-31 12:52:17

+0

@ircmaxell,你是什么意思由一个*“参考变量”*? http://php.net/manual/en/function.debug-zval-dump.php#example-5193似乎说2的refcount是非常可能的。 – Pacerier 2013-08-07 21:06:22

+0

@Prier:这是可能的。用于引用的变量。链接的代码(扩展名)接受一个变量的值。这意味着如果原始变量是一个引用('is_ref'设置为'1'),变量将在调用该扩展的函数之前分叉。因此,如果一个变量是进入该函数之前的参考,则由于写入时复制,返回的计数总是*总是* 1。 – ircmaxell 2013-08-08 02:19:49

2

是的,你绝对可以从PHP获得refcount。不幸的是,这个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

+0

那真的不可能,主要是因为可能不会直接要求对象的值 - 我可能会参考之前返回的内容......无论如何,PHP会为我保留一个引用计数 - 我不应该只使用它吗? – Josh 2010-09-21 21:59:26

+0

是的,你是对的。我想避免PREG,因为你正在处理90K条目。我正在执行时间,以方便编码。我更新了我的答案,以反映如何获取该人数。 – Sean 2010-09-21 22:13:55

+0

我知道使用debug_zval_dump的能力,尽管它有点疯狂。该函数输出我的课程(他们所有的孩子和各自的数据)的数千行数据。当我开始使用时,我遇到了执行时间错误 - 并且比脚本退出时的错误还要严重,因为它正在写入输出缓冲区,所以有时会将一些debug_zval数据转储给用户。 – Josh 2010-09-21 22:22:42

6

肖恩的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()返回一些你想存储的对象的唯一ID。 这依赖于这样一个事实,即PHP会记住插入到其哈希表中的顺序;每次添加一个新元素时,它都会被逻辑地追加到数组中。

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

这种方法也被称为最最近使用,或MRU缓存,因为它缓存最近使用的项目。这个想法是,你更有可能访问你最近访问的项目,所以你把它们放在一边。

什么你在这里是要控制你保持周围物体的最大数量的能力,你没有在PHP的实现细节是故意难以进入闲逛。

+0

这是一个不错的主意,虽然它不能解决核心问题。如果缓存中的某个对象在其他地方被引用,则称其为$ o1;然后用MRU从缓存中删除该对象;然后再次加载为$ o2。现在$ o1!= $ o2,这很不方便 - 但更重要的是,如果我在$ o1上设置属性,它不会传递到$ o2; $ o1-> credit + = 50; $ o1->保存(); $ o2-> credit + = 10; $ O2->保存(); - 如果它是同一个对象 - >信用会增加60,但它会被覆盖。 – Josh 2010-09-27 13:27:31

+0

是的,这将是一个问题,但是在内存中存在90k个对象的问题是您无法将它们放在内存中。 – 2010-09-28 03:58:17

+0

呃,输入提交评论,这很糟糕。无论如何,MRU缓存的目的是你总是通过缓存请求你的对象,并让它处理删除你不需要的项目。如果你真的需要一次在内存中存储90k个对象,那么无论是MRU还是带有重新计数的技巧都不会帮助你 - 你需要购买更多的内存。 – 2010-09-28 04:00:33

2

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

不幸的是,如果您发现的引用计数是雷区,但实际上您并不需要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,以禁用)矫枉过正版本。基本上,如果它变得太满或者过多的结果过期,那么它会修剪任何空引用的缓存来腾出空间,并且如果为了理智而只删除非空引用。

+0

或者只是使用[WeakMap](http://php.net/manual/en/class.weakmap .php)来自相同的PECL扩展!它可以像数组一样直接使用,而不用将每个元素包装在WeakRef中。 – 2017-12-28 08:22:36