PHP 7.4 ZVALS的参考计数的详细信息

发布于 2025-01-31 02:43:32 字数 2581 浏览 1 评论 0原文

我有一些澄清点,基于阅读此处描述的ZVALS的内部实现 PHP 7中的内部值表示形式 - 第1部分 php 7-第2部分2 中的内部值表示形式。

在详细解释我的混乱之前,我认为可以通过:

(i)我看不明白为什么对象引用计数与数组和字符串的计数不同

(ii)为什么引用对字符串的参考计数与数组的参考数不同。

据我了解,对于“复杂”数据类型(例如字符串,数组和对象),这些实体是参考计数类型(在PHP 7.4中)。

  • 在_zval_struct类中,有成员可以跟踪其类型和类型zend_value的成员,即Zend_value。
  • 指针
  • 该工会的成员包括Zend_Array,Zend_object,Zend_string,Zend_Reference Zend_Array和Likes内部存储一个参考计数和功能1所需的其他内部设备的

,因此,我想像数量,字符串和字符串,和字符串和弦,以及PHP中的对象是相同的,但事实并非如此。有人可以解释这(显然)过于简单的观点在哪里分解吗?

我使用“ debug_zval_dump”函数来计数参考(我没有特权来安装xdebug来使用'xdebug_debug_zval')。在后续分析中,我假设该函数自己的参数可以影响显示的参考计数。

2对于物体,以下计数对我来说很有意义,但是有确认会很棒。

class Foo{}
$a = new Foo();
debug_zval_dump($a); // #1: $a refcount = 2 -- and assume this is run after each line of code for each variable
$b = $a; // #2: $a, $b refcount = 3
$c = &$a; // #3: $a, $b, and $c refcount = 3
$a = new Foo(); // #4 $a, $b, and $c refcount = 2

需要清楚:每次定义或更新变量时,我都会将所有现有变量传递给PHP 7.4引擎中的debug_zval_dump。这就是重新数所指的。我只是节省空间。

#1:指向对象的两个_zval_structs(一个是函数参数)。

#2:指向对象(计数函数参数)

#3:函数参数,$ b和zend_Reference($ a和$ c共享)指向对象的

三个_zval_structs #3:对象#4:$ a和$ a和$ a C通过通用Zend_Reference(加上函数参数)和$ b参考同一对象,并且函数参数参考了同一对象。

这是错误的吗?如果是这样,请纠正我。否则,我们转到更令人困惑的项目:

3个数组:

$a=[]; // #1: $a refcount = 1
$b=$a; // #2: $a, $b refcount = 1
$c=&$a; // #3: $a, $b, and $c refcount = 1
$a[]=0; // #4: $a and $c refcount = 2, $b refcount = 1 

我期望的数字与2的数字相同。#1-#4,这不是我们得到的。这似乎与链接到AS的PHP文章不一致,我希望在#4:

$ a,$ c - > 中更像以下内容。 zend_reference1(refcount = 2) - > zend_array2(refcount = 2,value = [0])

$参数-----------------------------------------------------------------------

$ b,$参数 - > zend_array1(refcount = 2,value = [])

4尚有不同的字符串计数。

$a=''; // #1: $a refcount = 1
$b=$a; // #2: $a, $b refcount = 1
$c=&$a; // #3: $a, $b, and $c refcount = 1
$a='foo'; // #4: $a, $b, and $c refcount = 1

我在这里有与3的图表相同的图。

我忽略了此参考计数的哪些细节?

5作为奖励,现在提到一个数字时会发生什么?例如

$a=0;
$b=&$a; // $a, b -> zend_reference(refcount=2) -> zend_value(value=0)

,假设zend_value是基于堆栈的(因为数值不计数)是正确的吗?

I have some points of clarification based on reading the internal implementations for zvals described here Internal value representation in PHP 7 - Part 1 and Internal value representation in PHP 7 - Part 2.

Before explaining my confusion in detail, I think it can be summed up by:

(i) I do not see why object reference counting differs from that of arrays and strings

(ii) why reference counting for strings differs from that of arrays.

From what I understand, for "complex" data types like strings, arrays, and objects, those entities are reference counted types (in PHP 7.4).

  • Within the _zval_struct class, there is member to track its type and a member of type zend_value, which is a union.
  • Members of this union include pointers to zend_array, zend_object, zend_string, zend_reference
  • The zend_array and the likes internally store a reference count and other internals necessary for functionality

1 So based on this simplistic view, I would imagine the reference counting for arrays, strings, and objects in PHP are the same, but it appears not to be the case. Can someone explain where this (apparently) overly simplistic view falls apart?

I used the 'debug_zval_dump' function to count references (I do not have privileges to install xdebug to use 'xdebug_debug_zval'). In my subsequent analysis, I am assuming that the function's own parameter can influence the reference counts displayed.

2 For objects, the following counts make sense to me, but it would be great to have confirmation.

class Foo{}
$a = new Foo();
debug_zval_dump($a); // #1: $a refcount = 2 -- and assume this is run after each line of code for each variable
$b = $a; // #2: $a, $b refcount = 3
$c = &$a; // #3: $a, $b, and $c refcount = 3
$a = new Foo(); // #4 $a, $b, and $c refcount = 2

Just to be clear: every time a variable is defined or updated, I pass all existing variables to debug_zval_dump in a PHP 7.4 engine. That's what the refcounts refer to. I'm just saving space.

#1: There are two _zval_structs pointing to the object (one is the function parameter).

#2: There are three _zval_structs pointing to the object (counting the function parameter)

#3: The function parameter, $b, and a zend_reference (shared by $a and $c) point to the object

#4: $a and $c refer to the same object through a common zend_reference (plus the function parameter) and $b and the function parameter refer to the same object.

Is this counting wrong? Please correct me if so. Otherwise, we move to the more confusing items:

3 Arrays:

$a=[]; // #1: $a refcount = 1
$b=$a; // #2: $a, $b refcount = 1
$c=&$a; // #3: $a, $b, and $c refcount = 1
$a[]=0; // #4: $a and $c refcount = 2, $b refcount = 1 

I would expect the same numbers as for 2.#1-#4 and that's not what what we get. This appears to be at odds with the PHP article linked to as, I would expect something more like the following at #4:

$a, $c -> zend_reference1(refcount=2) -> zend_array2(refcount=2,value=[0])

$parameter -------------------------------^

$b, $parameter -> zend_array1(refcount=2,value=[])

4 There is then yet different counting for strings.

$a=''; // #1: $a refcount = 1
$b=$a; // #2: $a, $b refcount = 1
$c=&$a; // #3: $a, $b, and $c refcount = 1
$a='foo'; // #4: $a, $b, and $c refcount = 1

I would have the same diagram here as for 3.

What details am I overlooking for this reference counting?

5 As a bonus, what happens when a reference is made to a number now? For example

$a=0;
$b=&$a; // $a, b -> zend_reference(refcount=2) -> zend_value(value=0)

Is the comment diagram correct, assuming that zend_value is stack based (since numeric values are not reference counted)?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

月牙弯弯 2025-02-07 02:43:32

因此,基于这种简单的观点,我想像PHP中数组,字符串和对象的参考计数是相同的,但事实并非如此。有人可以解释这(显然)过于简单的视图散落的地方吗?

它崩溃了,因为变量的表示方式不仅基于其 type ,还基于如何定义和使用。尤其是,抄写并不总是处理简单值的最有效方法,并且编译器可以执行其他优化。

这很难用debug_zval_dump看到,因为将变量传递给该函数会更改其表示形式,并且由于没有显示的详细信息。使用 xdebug_debug_debug_debug_zval更多信息...

对于对象情况,它相当简单:

class Foo{}
$a = new Foo();
// a: (refcount=1, is_ref=0)=class Foo {  }
$b = $a;
// a: (refcount=2, is_ref=0)=class Foo {  }                                                                                                                                                                         // b: (refcount=2, is_ref=0)=class Foo {  }
$c = &$a;
// a: (refcount=2, is_ref=1)=class Foo {  }
// b: (refcount=2, is_ref=0)=class Foo {  }
// c: (refcount=2, is_ref=1)=class Foo {  }
$a = new Foo();
// a: (refcount=2, is_ref=1)=class Foo {  }
// b: (refcount=1, is_ref=0)=class Foo {  }
// c: (refcount=2, is_ref=1)=class Foo {  }

$ a$ c点相同is_Reference zval(refcount) = 2); Zval和$ b都指向同一对象(refcount = 2)。


现在,让我们看一下阵列情况:

$a=[];
// a: (immutable, is_ref=0)=array ()
$b=$a;
// a: (immutable, is_ref=0)=array ()
// b: (immutable, is_ref=0)=array ()
$c=&$a;
// a: (refcount=2, is_ref=1)=array ()
// b: (immutable, is_ref=0)=array ()
// c: (refcount=2, is_ref=1)=array ()
$a[]=0;
// a: (refcount=2, is_ref=1)=array (0 => (refcount=0, is_ref=0)=0)
// b: (immutable, is_ref=0)=array ()
// c: (refcount=2, is_ref=1)=array (0 => (refcount=0, is_ref=0)=0)

空数组根本不显示重新数,它们显示“不变”。空数组是常见的,并且可以互换,因此特殊情况避免了分配许多具有相同内容的单独的Zvals。

如果我们将数组更改为“不空”,我们会得到一些不同的东西:

$a=[42];
// a: (refcount=2, is_ref=0)=array (0 => (refcount=0, is_ref=0)=42)
$b=$a;
// a: (refcount=3, is_ref=0)=array (0 => (refcount=0, is_ref=0)=42)
// b: (refcount=3, is_ref=0)=array (0 => (refcount=0, is_ref=0)=42)
$c=&$a;
// a: (refcount=2, is_ref=1)=array (0 => (refcount=0, is_ref=0)=42)
// b: (refcount=3, is_ref=0)=array (0 => (refcount=0, is_ref=0)=42)
// c: (refcount=2, is_ref=1)=array (0 => (refcount=0, is_ref=0)=42)
$a[]=0;
// a: (refcount=2, is_ref=1)=array (0 => (refcount=0, is_ref=0)=42, 1 => (refcount=0, is_ref=0)=0)
// b: (refcount=2, is_ref=0)=array (0 => (refcount=0, is_ref=0)=42)
// c: (refcount=2, is_ref=1)=array (0 => (refcount=0, is_ref=0)=42, 1 => (refcount=0, is_ref=0)=0)

“不变”已经消失了,但是有些奇怪:重新汇票即使只分配一个变量,重新数也开始于2。这是“编译变量”的影响:编译器已将包含[42]的Zval预先分配,因此需要为其管理内存。为了避免正常的内存管理太早释放它,它会增加对Zval的额外计数引用。

要打败 优化,让我们创建一个只能在运行时创建的数组:

$a=[rand()];
// a: (refcount=1, is_ref=0)=array (0 => (refcount=0, is_ref=0)=713417292)
$b=$a;
// a: (refcount=2, is_ref=0)=array (0 => (refcount=0, is_ref=0)=713417292)
// b: (refcount=2, is_ref=0)=array (0 => (refcount=0, is_ref=0)=713417292)
$c=&$a;
// a: (refcount=2, is_ref=1)=array (0 => (refcount=0, is_ref=0)=713417292)
// b: (refcount=2, is_ref=0)=array (0 => (refcount=0, is_ref=0)=713417292)
// c: (refcount=2, is_ref=1)=array (0 => (refcount=0, is_ref=0)=713417292)
$a[]=0;
// a: (refcount=2, is_ref=1)=array (0 => (refcount=0, is_ref=0)=713417292, 1 => (refcount=0, is_ref=0)=0)
// b: (refcount=1, is_ref=0)=array (0 => (refcount=0, is_ref=0)=713417292)
// c: (refcount=2, is_ref=1)=array (0 => (refcount=0, is_ref=0)=713417292, 1 => (refcount=0, is_ref=0)=0)

最后,事情看起来更像是对象情况!


在字符串上...

$a='';
// a: (interned, is_ref=0)=''
$b=$a;
// a: (interned, is_ref=0)=''
// b: (interned, is_ref=0)=''
$c=&$a;
// a: (refcount=2, is_ref=1)=''
// b: (interned, is_ref=0)=''
// c: (refcount=2, is_ref=1)=''
$a='foo';
// a: (refcount=2, is_ref=1)='foo'
// b: (interned, is_ref=0)=''
// c: (refcount=2, is_ref=1)='foo'

像空数组一样,空字符串没有显示重新数,它显示“实习”。同样,编译器已决定不分配新的Zval,而是使用一些共享内存。 实施的,但是Xdebug向我们展示了is_Reference zval指向它

'foo'可能也是

$a='hello';
// a: (interned, is_ref=0)='hello'
$b=$a;
// a: (interned, is_ref=0)='hello'
// b: (interned, is_ref=0)='hello'
$c=&$a;
// a: (refcount=2, is_ref=1)='hello'
// b: (interned, is_ref=0)='hello'
// c: (refcount=2, is_ref=1)='hello'
$a='foo';
// a: (refcount=2, is_ref=1)='foo'
// b: (interned, is_ref=0)='hello'
// c: (refcount=2, is_ref=1)='foo'

。 ,这并没有任何不同,编译器可以“实习”其在源代码中看到的任何常数字符串。

因此,我们需要再次击败优化:

$a=(string)rand();
// a: (refcount=1, is_ref=0)='522057011'
$b=$a;
// a: (refcount=2, is_ref=0)='522057011'
// b: (refcount=2, is_ref=0)='522057011'
$c=&$a;
// a: (refcount=2, is_ref=1)='522057011'
// b: (refcount=2, is_ref=0)='522057011'
// c: (refcount=2, is_ref=1)='522057011'
$a='foo';
// a: (refcount=2, is_ref=1)='foo'
// b: (refcount=1, is_ref=0)='522057011'
// c: (refcount=2, is_ref=1)='foo'

再次与对象示例匹配!


这只是当前引擎中涉及的优化的样本,将来可能会添加更多版本(以上版本已在PHP 7.4、8.0和8.1上进行了测试)。

So based on this simplistic view, I would imagine the reference counting for arrays, strings, and objects in PHP are the same, but it appears not to be the case. Can someone explain where this (apparently) overly simplistic view falls apart?

It falls apart because the way variables are represented is based not just on their type, but how they're defined and used. In particular, copy-on-write isn't always the most efficient way to handle a simple value, and the compiler can perform other optimisations instead.

This is hard to see with debug_zval_dump, because passing a variable to the function changes its representation, and because there are details it doesn't show. Using the xdebug_debug_zval function provided by Xdebug instead, we get a bit more information...

For the object case, it's fairly straight-forward:

class Foo{}
$a = new Foo();
// a: (refcount=1, is_ref=0)=class Foo {  }
$b = $a;
// a: (refcount=2, is_ref=0)=class Foo {  }                                                                                                                                                                         // b: (refcount=2, is_ref=0)=class Foo {  }
$c = &$a;
// a: (refcount=2, is_ref=1)=class Foo {  }
// b: (refcount=2, is_ref=0)=class Foo {  }
// c: (refcount=2, is_ref=1)=class Foo {  }
$a = new Foo();
// a: (refcount=2, is_ref=1)=class Foo {  }
// b: (refcount=1, is_ref=0)=class Foo {  }
// c: (refcount=2, is_ref=1)=class Foo {  }

$a and $c point at the same IS_REFERENCE zval (refcount=2); that zval and $b both point at the same object (refcount=2).


Now let's look at the array case:

$a=[];
// a: (immutable, is_ref=0)=array ()
$b=$a;
// a: (immutable, is_ref=0)=array ()
// b: (immutable, is_ref=0)=array ()
$c=&$a;
// a: (refcount=2, is_ref=1)=array ()
// b: (immutable, is_ref=0)=array ()
// c: (refcount=2, is_ref=1)=array ()
$a[]=0;
// a: (refcount=2, is_ref=1)=array (0 => (refcount=0, is_ref=0)=0)
// b: (immutable, is_ref=0)=array ()
// c: (refcount=2, is_ref=1)=array (0 => (refcount=0, is_ref=0)=0)

The empty arrays don't show a refcount at all, they show "immutable". Empty arrays are common, and interchangeable, so a special case avoids allocating lots of separate zvals with the same content.

If we change the array to not be empty, we get something different:

$a=[42];
// a: (refcount=2, is_ref=0)=array (0 => (refcount=0, is_ref=0)=42)
$b=$a;
// a: (refcount=3, is_ref=0)=array (0 => (refcount=0, is_ref=0)=42)
// b: (refcount=3, is_ref=0)=array (0 => (refcount=0, is_ref=0)=42)
$c=&$a;
// a: (refcount=2, is_ref=1)=array (0 => (refcount=0, is_ref=0)=42)
// b: (refcount=3, is_ref=0)=array (0 => (refcount=0, is_ref=0)=42)
// c: (refcount=2, is_ref=1)=array (0 => (refcount=0, is_ref=0)=42)
$a[]=0;
// a: (refcount=2, is_ref=1)=array (0 => (refcount=0, is_ref=0)=42, 1 => (refcount=0, is_ref=0)=0)
// b: (refcount=2, is_ref=0)=array (0 => (refcount=0, is_ref=0)=42)
// c: (refcount=2, is_ref=1)=array (0 => (refcount=0, is_ref=0)=42, 1 => (refcount=0, is_ref=0)=0)

The "immutable" has gone, but something's odd: the refcount starts at 2 even when we only assign one variable. This is the influence of a "Compiled Variable": the compiler has pre-allocated the zval containing [42], so needs to manage the memory for it. To avoid the normal memory management freeing it too soon, it adds an extra counted reference to the zval.

To defeat that optimization, let's create an array that can only be created at run-time:

$a=[rand()];
// a: (refcount=1, is_ref=0)=array (0 => (refcount=0, is_ref=0)=713417292)
$b=$a;
// a: (refcount=2, is_ref=0)=array (0 => (refcount=0, is_ref=0)=713417292)
// b: (refcount=2, is_ref=0)=array (0 => (refcount=0, is_ref=0)=713417292)
$c=&$a;
// a: (refcount=2, is_ref=1)=array (0 => (refcount=0, is_ref=0)=713417292)
// b: (refcount=2, is_ref=0)=array (0 => (refcount=0, is_ref=0)=713417292)
// c: (refcount=2, is_ref=1)=array (0 => (refcount=0, is_ref=0)=713417292)
$a[]=0;
// a: (refcount=2, is_ref=1)=array (0 => (refcount=0, is_ref=0)=713417292, 1 => (refcount=0, is_ref=0)=0)
// b: (refcount=1, is_ref=0)=array (0 => (refcount=0, is_ref=0)=713417292)
// c: (refcount=2, is_ref=1)=array (0 => (refcount=0, is_ref=0)=713417292, 1 => (refcount=0, is_ref=0)=0)

Finally, things look more like the object case!


On to strings...

$a='';
// a: (interned, is_ref=0)=''
$b=$a;
// a: (interned, is_ref=0)=''
// b: (interned, is_ref=0)=''
$c=&$a;
// a: (refcount=2, is_ref=1)=''
// b: (interned, is_ref=0)=''
// c: (refcount=2, is_ref=1)=''
$a='foo';
// a: (refcount=2, is_ref=1)='foo'
// b: (interned, is_ref=0)=''
// c: (refcount=2, is_ref=1)='foo'

Like the empty array, the empty string isn't showing a refcount, it's showing "interned". Again, the compiler has decided not to allocate a new zval and to use some shared memory instead. ('foo' is probably also interned, but Xdebug is showing us the IS_REFERENCE zval pointing to it.)

Let's pick something non-empty instead:

$a='hello';
// a: (interned, is_ref=0)='hello'
$b=$a;
// a: (interned, is_ref=0)='hello'
// b: (interned, is_ref=0)='hello'
$c=&$a;
// a: (refcount=2, is_ref=1)='hello'
// b: (interned, is_ref=0)='hello'
// c: (refcount=2, is_ref=1)='hello'
$a='foo';
// a: (refcount=2, is_ref=1)='foo'
// b: (interned, is_ref=0)='hello'
// c: (refcount=2, is_ref=1)='foo'

Unlike the non-empty array, this hasn't made any difference, the compiler can "intern" any constant string it sees in the source code.

So we need to defeat the optimization again:

$a=(string)rand();
// a: (refcount=1, is_ref=0)='522057011'
$b=$a;
// a: (refcount=2, is_ref=0)='522057011'
// b: (refcount=2, is_ref=0)='522057011'
$c=&$a;
// a: (refcount=2, is_ref=1)='522057011'
// b: (refcount=2, is_ref=0)='522057011'
// c: (refcount=2, is_ref=1)='522057011'
$a='foo';
// a: (refcount=2, is_ref=1)='foo'
// b: (refcount=1, is_ref=0)='522057011'
// c: (refcount=2, is_ref=1)='foo'

Once again, it matches the object example!


This is just a sampling of the optimizations involved in the current engine, and more will probably be added in future versions (the above was tested on PHP 7.4, 8.0, and 8.1).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文