深入理解PHP内核(一)_php实例

PHP作为一门简单而强大的语言,能够提供很多Web适用的语言特性。从实践出发,继弱类型变量原理探究后,本文继续带领大家深入理解php内核。

 最近,和一个网友交流的时候,给我提了一个非常奇怪的问题。那就是,在一个运算中,加了一个引用之后,发现性能慢了一万倍。在我的脑海里面,引用是一个非常容易出错的问题,特别是PHP里面的引用,有非常多的陷阱。因为,以前专门研究过这一块PHP的源代码,所以,我可以比较清晰的解析引用到底是怎么一回事,希望,读了我这篇文章,能彻底理解这个问题。如果,有任何疑问,或者有一些你想了解的问题,可以给我留言。

先来看一段代码:

class RefferTest
{
 private $data;
 private $testKey;
 function __construct()
 {
  $key = "hello";
  $this->data[$key] = range(0, 10000);
  $this->testKey = $key;
 }
 function reffer($key)
 {
  $reffer = &$this->data[$key];
  return count($reffer);
 }
 function noreffer($key)
 {
  return count($this->data[$key]);
 }
 function test()
 {
  $t1 = microtime(true);
  for ($i = 0; $i < 5000; $i++)
  {
   $this->reffer($this->testKey);
  }
  $t2 = microtime(true) - $t1;
  var_dump("reffer: " . round($t2, 4));
  $t1 = microtime(true);
  for ($i = 0; $i < 5000; $i++)
  {
   $this->noreffer($this->testKey);
  }
  $t2 = microtime(true) - $t1;
  var_dump("noreffer: " . round($t2, 4));
 }
}
$test = new RefferTest();
$test->test();

 如果你完这个代码,能说出,为了reffer 和 noreffer会差一万倍的性能,那下面的也就没有必要往下看了。这篇博客针对的是,PHP的新手。你可以运行一下这个代码试试看,的确差了一万倍。当然,那个网友遇到的问题的代码要比上面的复杂,上面的代码是我为了说明问题,特意简化的。或许你已经从代码里面看出问题了,但是,至于为什么会这样。我想,还是有必要分析一下。这样,以后,在使用PHP的时候,才不会犯相同的错误。

PHP为了减少复制,采用了一种copy on writer的机制。我想,这是一种非常常见的机制,你也一定听说过。比如,gcc 的 stl string 的实现,就是采用这样的机制,字符串赋值,不是真正的复制,而且,在修改的时候才会进行复制。我们先来举个最简单的例子:

 $a = str_repeat("", );
  $b = $a;
  $a[] = "";

$a 是一个非常大的字符串,如果 $b = $a 的时候,进行复制,那要耗费很多内存 和 cpu,这样非常的不划算,万一,下面的代码并不修改$a 和 $b 那复制根本没有必要。当然,$a 在后面又被修改了,这个时候,必须进行复制了,否则就不符合逻辑了。但是,现在问题来了,怎么知道,$a 在修改的时候,要进行复制呢,必须要有这样一个标记。方法就是采用引用计数。引用计数还被用来进行内存的管理。

基本的流程是这样的:

1: 创建一个变量,可以保存 10000 个 0 的这样一个字符串。

2: 创建一个变量符号 a ,这个变量符号引用 这个变量。注意,变量符号 和 变量不是一回事情,这两者是分离的。

如果从C语言的角度来说,PHP大概完成这样一件事情:

  char *varname = "a";
  size_t varname_len = strlen(varname);
  zend_hash_add(EG(active_symbol_table), varname, varname_len + , &var, sizeof(zval*), NULL);

active_symbol_table 是PHP的一个符号表,所有能访问到的变量都在这个里面,他是一个哈希表。var 这个变量,保存了 10000 个 0 这个字符串。而且是zval的结构,zval的结构如下:

typedef struct _zval_struct {
 zvalue_value value;
 zend_uint refcount;
 zend_uchar type;
 zend_uchar is_ref;
} zval;
typedef union _zvalue_value {
 long lval;
 double dval;
 struct {
  char *val;
  int len;
 } str;
 HashTable *ht;
 zend_object_value obj;
} zvalue_value;

 zvalue_value 是一个联合,可以保存 long, double, 字符串,哈希表(PHP Array),还有就是 对象。也就是所有的PHP的类型。 zval 其实 就是 对 zvalue_value ,加入了类型type 和 引用is_ref,引用计数refcount三个功能。这就是PHP中的普通变量。要是用PHP做比较大型的东西,就会发现,内存占用非常厉害。就是因为,他一个变量 不是 传统C语言的那个变量了,它加了很多东西。

好了,第一句完成了,下面是第二句。第二句很简单,会产生一个新的变量符号b,把他加入 active_symbol_table ,但是不会增加新的一个变量,而只是,refcount++。赋值就完成了。如图:

首先我们要注意的是,a ,b 只是一个符号,他是active_symbol_table 表里面的一个key,都有一个指针指向一个zval,所以,a 和b 在 C语言层面上是完全一致的。我们就得出PHP变量第一定律:

PHP变量第一定律:如果两个变量指向同一个zval,那么这两个变量是无差别的。也就是说,任何对a 的操作 相对b 都是对称的。这里的对称,是这样理解的。就是镜子中的你,而不是等同。比如,对 a 进行 赋值,a 就会产生 copy。同样的,如果对b进行赋值,也会进行相同的操作,那就是b产生一个copy。也就是说,a 和b的行为是一样的。

第三句,当writer发生的时候,PHP会判断一下refcount 是否大于2,如果大于2,那么就复制一下zval,然后,把原来那个zval refcount--。这就是copy on writer 的全部了,你一定觉得,这一切你都是非常的熟悉,你都懂。

但是,PHP不仅仅是copy on writer 这样简单,它还有一个引用的问题。引入引用的概念,这样,问题就变的有些复杂了。因为,引用这个标记,意思就是说,writer 的时候,你也不需要复制。这样,会修改原来的那个变量。从我们在学校里面以前经常学习的哲学上来说,这是一对矛盾。他们是对立的,又是统一的,各有各的用处。所谓,存在的就是合理的。

好,下面我们来看看这对矛盾,我们只考虑两种组合的情况。多种组合都是类似的。两种组合的话,就是赋值在前,引用在后。

或者  引用在前,赋值在后。我们会分别讨论,先来看:就是赋值在前,引用在后的情况。

  $a = ;
   $b = $a;
   $c = &$a;

$b = $a, 是copy on writer 行为的 赋值。而 $c 和 $a 是引用赋值。我们假设在上面这样的情况下,我们可以用一个zval表示,也就是不需要复制,那么情况是这样的:

根据我们的PHP变量第一定律,那,就是说,a,b,c的操作是对称的,但是非常明显,对 b 操作要产生复制行为,而对a操作不会产生复制,操作行为不相同,和第一定律矛盾。也就是说,要使得上面的操作没有矛盾,必须,进行分离。分离的原则就是,谁制造矛盾,谁复制。显然是 第三句话,$c = &$a; 在制造矛盾。所以,内部变量的复制过程如下图:

上面情况是赋值在前,引用在后的情况。还有一种情况是,引用在前赋值在后:

 $a = ;
   $b = &$a;
   $c = $a;

按照PHP变量的第一定律,a,b,c 必须进行分离,才能保证定律的正确。可以发现,b 和 a 明显是一伙人,就是说,b 和 a 的操作是对称的,他们可以指向同一个zval ,而c 的行为和 a,b 不一样,改变c 需要进行复制。看到这里,我想,如果你看懂了的话,为什么刚开始,贴出来的那段代码的,那个两个count差异如此之大,你也应该明白了。当我和那个网友讨论的时候,它最后说,那这样的话,PHP设计的不好,我完全可以,$c先不进行复制,等c被write 了,再进行复制。看来要说懂一个东西,还是一件很难的事情,好好想想那个PHP第一定律吧。你可以假设不进行分离,c指向同一个zval,所以,c 和 a,b的行为是一样的,是is_ref = 1,所以,c 不会进行复制。最后一种内部执行情况可以用下图表示:

我以前也进行搞混这个引用,现在,你可以用那个第一定律来分析所有的情况了。PHP内核分析的文章,以后我还会写一些,如果你想深入了解PHP的某些方面,可以给我留言。

最后再补充一点,也是一个隐性的错误。

function count_bigarray()
{
 global $bigarray;
 return count($bigarray);
}

这里,没有显示的引用,但是这里隐藏了一个引用。PHP会自动创建一个引用全局变量 $bigarray 的代码,如果你在这里使用count,那么这个效率会非常的慢。最好直接通过$GLOBAL 数组进行引用。

下面文章将给大家介绍深入理解php内核二之SAPI探究,希望大家继续关注哦。

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索深入php内核
深入理解php内核
深入理解php内核、深入理解php内核 pdf、深入理解linux内核、深入理解linux内核pdf、深入理解unix系统内核,以便于您获取更多的相关知识。

时间: 2024-11-05 18:30:46

深入理解PHP内核(一)_php实例的相关文章

浅谈PHP拦截器之__set()与__get()的理解与使用方法_php实例

"一般来说,总是把类的属性定义为private,这更符合现实的逻辑. 但是,对属性的读取和赋值操作是非常频繁的,因此在PHP5中,预定义了两个函数"__get()"和"__set()"来获取和赋值其属性,以及检查属性的"__isset()"和删除属性的方法"__unset()". 我们为每个属性做了设置和获取的方法,在PHP5中给我们提供了专门为属性设置值和获取值的方法,"__set()"和&qu

PHP内核学习教程之php opcode内核实现_php实例

opcode是计算机指令中的一部分,用于指定要执行的操作, 指令的格式和规范由处理器的指令规范指定. 除了指令本身以外通常还有指令所需要的操作数,可能有的指令不需要显式的操作数. 这些操作数可能是寄存器中的值,堆栈中的值,某块内存的值或者IO端口中的值等等. 通常opcode还有另一种称谓:字节码(byte codes). 例如Java虚拟机(JVM),.NET的通用中间语言(CIL: Common Intermeditate Language)等等. 1. Opcode简介 opcode是计算

深入理解PHP内核(二)之SAPI探究_php实例

在上篇文章给大家介绍了深入了解PHP内核(一),相信大家通过本文多多少少都学到些知识吧,关于php内核知识继续关注本篇文章. SAPI是Server Application Programming Interface(服务器应用编程接口)的缩写.PHP通过SAPI提供了一组接口,供应用和PHP内核之间进行数据交互. 简单的讲,就像函数的输入和输出一样,我们通过Linux命令行执行一段PHP代码,本质是Linux的Shell通过PHP的SAPI传入一组参数,Zend引擎执行后,返回给shell,由

深入理解PHP类的自动载入机制_php实例

php的自动加载: 在php5以前,我们要用某个类或类的方法,那必须include或者require,之后才能使用,每次用一个类,都需要写一条include,麻烦 php作者想简单点,最好能引用一个类时,如果当前没有include进来,系统能自动去找到该类,自动引进~ 于是:__autoload()函数应运而生. 通常放在应用程序入口类里面,比如discuz中,放在class_core.php中. 先讲浅显的例子: 第一种情况:文件A.php中内容如下 <?php class A{ public

深入理解PHP变量的值类型和引用类型_php实例

在PHP中,大部分变量类型,如字符串,整型,浮点,数组等都是值类型的,而类和对象是引用类型,在使用的时候,需要注意这一点. 看到网友在讨论PHP的&符号,要彻底理解它的用法,就有必要讨论一下变量的两种形式. PHP的变量在内存中是这样存储的,变量保存的并不直接是值的内容,而是地址.例如: $a = 1; 我们看起来,似乎变量$a直接存储了 1 这个值.而实际情况是,PHP解释器创建了变量$a,将值:1 存入内存中的某个地方,再将值的地址存到变量$a中. 需要取值时,先找到变量$a中的地址,再根据

深入理解PHP JSON数组与对象_php实例

在PHP后端和客户端数据交互的过程中,JSON数据中有时格式不定,一会儿是数组,一会儿是对象,弄得客户端开发人员要崩溃的感觉. 因此,前后端相关人员先对PHP的json_encode函数原理有必要的了解是最重要的一个环节. PHP中的array是个万能的数据结构,并不像其它语言根据需要的场景会定义很多约束性的数据类型来描述结构,所以PHP程序员是很难给客户端人员讲清楚返回的是什么结构的数据的. 也就变成明明是通过PHP的数组数据编码成json的,但输出值有时是数组.有时是对象的情况. <?php

深入理解linux内核之(二)进程

                                      深入理解linux内核之(二)进程       程序是静态的,进程是正在执行的程序的一个实例,一个程序可以由多个进程组成.进程是资源分配的实体.在进程被创建出来之后,该子进程几乎和父进程一样.子进程复制了父进程的地址空间,从fork()之后的第一条指令开始执行,和父进程有同样的程序可执行代码(exec调用除外).尽管子进程和父进程具有同样的程序执行代码,但是子进程拥有自己的stack和heap,因此,子进程对数据的修改对

php开发留言板的CRUD(增,删,改,查)操作_php实例

项目结构: 开发留言板的CRUD(增,删,改,查)操作_php实例-angularjs crud实例"> 添加页面:                                说明:这里只注重操作,对界面的美工没有下工夫,希望大家理解...... 列表页面: 修改页面: 项目中所需的sql: 复制代码 代码如下: create database form; use form; CREATE TABLE `message` ( `id` tinyint(1) NOT NULL auto_

Windows7下PHP开发环境安装配置图文方法_php实例

      操作系统:Windows 7 Ultimate       WEB服务器:IIS 6.1(内部版本7600).       数据库:MySql5.0.67       PHP版本:5.2.13       我还担心Win7下可能会不兼容,结果是一点问题都没有.    一.安装MySql数据库       MySql数据库在这里下载:http://www.mysql.com/downloads/ 客户端工具Navicat(导航猫)在这里下载:http://www.navicat.com