tipi小记

  1. 从类型的维度上来看,编程语言分为三大类

    静态类型语言,比如:C/Java等,在静态语言类型中,类型的检查是在编译期(compile-time)确定的,也就是说在运行时变量的类型是不会变化的

    动态类型语言,比如:PHP,python等各种脚本语言,这类语言中的类型是在运行时确定的,那么也就是说类型通常可以在运行时发生变化

    无类型语言,比如:汇编语言,汇编语言操作的是底层存储,它们对类型毫无感知
     
  2. 和其他编译性静态语言不同,PHP在存储变量时将PHP用户空间的变量类型也保存在同一个结构体中
    typedef struct _zval_struct zval;
    ...
    struct _zval_struct {
        /* Variable information */
        zvalue_value value;     /* value */
        zend_uint refcount__gc;
        zend_uchar type;    /* active type */
        zend_uchar is_ref__gc;
    };
  3. 变量的值使用联合体而不是结构体是出于空间利用率的考虑(同一个变量同时只能属于一种类型)
    typedef union _zvalue_value {
        long lval;                  /* long value */
        double dval;                /* double value */
        struct {
            char *val;
            int len;
        } str;
        HashTable *ht;              /* hash table value */
        zend_object_value obj;
    } zvalue_value;
    联合体的所有成员变量共享内存中的一块内存,在某个时刻只有一个成员使用这块内存
    PHP数组的值存储在zvalue_value.ht字段中,它是一个Hash Table类型的数据
    php对象的值存储在zend_object_value的结构体中
    php的对象只有在运行时才会被创建
    
  4. 哈希表是一种通过哈希函数,将特定的键映射到特定值的一种数据结构,它维护键和值之间一 一对应关系

    哈希表可以理解为数组的扩展或者关联数组,数组使用数字下标来寻址,如果关键字(key)的范围较小且是数字的话,我们可以直接使用数组来完成哈希表,而如果关键字范围很大或者键不是数字,我们使用哈希函数来将key映射到特定的域中
    h(key) -> index
    解决哈希冲突的两种主要方法:链接法和开放寻址法。目前php中Hash Table的哈希冲突解决方法就是链接法
    
    Hash Table结构体用于保存整个哈希表需要的基本信息,而Bucket结构体用于保存具体的数据内容
    
    Zend引擎中的链表是双链表,通过双链表的任意节点都能方便的对链表进行遍历
    
    Zend引擎的哈希表实现是哈希表和双链表的混合实现,这也是为了方便哈希表的遍历
  5. 常量在脚本执行期间该值不能改变。和变量一样,常量默认为大小写敏感。函数的名称是不区分大小写的

    常量的内部结构:
     
    typedef struct _zend_constant {
        zval value; /* zval结构,PHP内部变量的存储结构,在第一小节有说明 */
        int flags;  /* 常量的标记如 CONST_PERSISTENT | CONST_CS */
        char *name; /* 常量名称 */
        uint name_len;  
        int module_number;  /* 模块号 */
    } zend_constant;
    
    常量分为:持久化常量和非持久化常量。在用户空间,也就是用户定义的常量都是非持久化的;通常扩展和内核定义的常量会设置为持久化。非持久常量,会在RSHUTDOWN阶段就将该常量释放,否则只会在MSHUTDOWN阶段将内存释放
    
  6. 静态变量可以分为:

    静态全局变量,PHP中的全局变量也可以理解为静态全局变量,因为除非明确unset释放,在程序运行过程中始终存在

    静态局部变量,也就是在函数内部定义的静态变量,函数在执行时对变量的操作会保持到下一次函数被调用

    静态成员变量,这是在类中定义的静态变量,和实例变量相对应,静态成员变量可以在所有实例中共享
     
  7. (unset)$a和unset($a)这两者没有关系,前者是将变量$a的类型变为NULL,这只是一个类型的变化,而后者是将这个变量释放,释放后当前作用域内该变量就不存在了
     
  8. PHP支持变量函数的类型。这意味着如果一个变量名后面有圆括号,PHP将寻找与变量的值同名的函数,并且将尝试执行它
     
    do_bind_function(EX(opline), EG(function_table), 0);
    语法解析后,编译函数,会将EX(opline)所指向的函数添加到EG(function_table)中,并判断是否存在相同名字的函数,如果存在则报
    
    函数参数处理部分包括两个操作:一个是取参数的个数,一个是解析参数列表
    
    在解析参数的同时会尽可能地转换参数类型,这样就可以确保我们总是得到所期望的类型的变量。任何一种标量类型都可以转换为另一种标量类型,不能在标量类型与复杂类型(比如数组、对象和资源等)之间进行转换。如果成功地解析了参数并且在转换期间也没出现错误,那么这个函数就会返回SUCCESS,否则返回FALSE
    
    每个PHP脚本都有自己专属的全局符号表,而每个用户自定义的函数也有自己的符号表,这个符号表用来存储在这个函数作用域下的属于它自己的变量。当调用每个用户自定义的函数时,都会为这个函数创建一个符号表,当这个函数返回时都会释放这个符号表
    
    函数内部的每个局部变量有三个引用计数分别是:function stack中的引用,function symbol table中引用,原变量$a的引用
    
  9. 在本质上,函数中的php语句与函数外的php语句并无不同。函数体本身最大的区别,在于其执行环境的不同。这个“执行环境”最重要的特征就是变量的作用域。在函数执行的时候,进入函数前的环境信息是必须要保存的。在函数执行完毕后,这些环境信息也会被还原,使整个程序继续地执行下去
     
  10. 闭包是词法闭包的简称,是引用了自由变量(use()括号中的变量)的函数,这个被引用的自由变量将和这个函数一同存在,即使离开了创建它的环境也一样,所以闭包也可认为是有函数和与其相关引用组合而成的实体

    匿名函数是一个闭包实例,use语句定义的变量值赋值给这个匿名函数的静态变量,这样匿名函数就能访问到use的变量了

    如果存在一样函数名的函数,create_function则会生成新的函数名,第一个字符为空字符的定义。我们其实还是可以调用这个函数的,只要我们在函数名前加一个空字符就可以

    create_function创建“匿名函数”的一些缺点:函数的定义是通过字符串动态eval的,这无法进行基本的语法检查;这类函数和普通函数没有本质区别,无法实现闭包的效果
     
  11. 面向对象是一种编程范式,它将对象作为程序的基本单元,将程序和数据封装起来,以此来提高程序的重用性、灵活性和可扩展性

    get_class_vars()函数查找名为class_name的类过程的核心是一个Hash Table的查找函数zend_hash_quick_find,它会查找EG(class_table)。判断类是否存在,如果存在则直接返回。如果不存在,则需要判断是否可以自动加载,如果可以字段加载,则会加载类后再返回

    我们需要调用一个类的静态变量,当然要先找到这个类,然后再获取这个类的变量

    类的成员变量在PHP中本质上是一个变量,只是这些变量都归属于某个类,并且这些变量是有访问控制的。类的成员方法在PHP中本质上是一个函数,只是这个函数以类的方法存在,它可能是一个类方法也可能是一个实例方法,并且在这些方法上都加上了类的访问控制

    在面向对象的语言中一般是通过访问控制来实现封装的特性

    是否有访问权限的检测的实现过程在获取类的方法过程中
     
  12. PHP内核将类的继承实现放在了“编译阶段”

    在继承过程中,除了常规的函数合并后,还有魔法方法的合并。如果子类中没有相关的魔术方法,则继承父类的对应方法。如果子类有自己的魔术方法,并且需要调用父类的魔术方法时,需要在子类的魔术方法中调用父类的魔术方法,PHP不会自动调用

    如果一个成员被指定为private,它将不能被继承。实际上在PHP中这个方法会被继承下来,只是无法访问

    多态即多种形态,相同方法调用实现不同的实现方式,多态关注一个接口或基类。在面向对象的原则中,里氏替换原则和依赖倒转原则等都依赖于多态特性

    类型提示在判断是否为指定类的实例时,会先遍历实例所在类的所有接口和父类,递归调用其本身,判断实例的接口或者父类是否为指定类的实例

    接口和接口之间也可以继承,只能是一个接口继承另一个接口。接口的继承过程中,会合并接口中的常量列表和方法列表

    标记类为抽象类或标记成员方法为抽象方法的确认阶段是语法解析阶段

    在PHP中,抽象类是被abstract关键字修饰的类,或者类没有被声明为abstract,但是在类中存在抽象成员的类

    PHP中魔术方法,运行时执行与否取决的判断根据,最终都是_zend_class_entry结构体中对应的指针是否为空

    后期静态绑定的意思,static::不再被解析为定义当前方法所在的类,而是在实际运行时计算的(static特殊类指向的是called_scope,也就是当前类,触发方法调用的类)
     
    <?php
    var_dump(self); // ->  string(4) "self"
    尝试未定义的常量会把产量本身当成一个字符串
    
  13. 对象拥有方法,对象间的通信是通过方法调用。我们常说的面向对象编程使得对象具有交互能力的主要模型就是消息传递模型,对象是消息传递的主体,它可以接收,也可以拒绝外界发来的消息
     
  14. 命名空间表示标识符(identifier)的上下文(context)。在编程语言中,命名空间是一种特殊的作用域,它包含了处于该作用域内的标识符,且本身也是一个标识符来表示,这样便将一系列在逻辑上相关的标识符用一个标识符组织了起来

    当需要将全局的非命名空间中的代码与命名空间中的代码组合在一起,全局代码必须用一个不带名称的namespace语句加上大括号括起来
     
  15. SPL(Standard PHP Library)是为了解决典型问题而存在,为了实现一些有效的数据访问接口和类。现在它包括对常规数据结构的访问,迭代器,异常处理,文件处理,数组处理和一些设计模式的实现

    SPL在PHP内核中以一个扩展的形式存在,在ext目录下有一个spl目录,这里存放了SPL的所有代码实现

    SPL异常类只是一个壳,它们都是从Exception继承下来的,所有的方法完全继承自Exception类

    对于每条PHP脚本生成的opcode,在编译时都会执行一次初始化操作,在这次初始化操作中,PHP内核会将当前正在编译的行号赋值给opcode的lineno属性

    EG(active_op_array)表示正在执行的opcode列表,EG(opline_ptr)是PHP内核执行的当前opcode,抛出异常时对应的行号即为此对象的lineno属性

    在异常类中,还包括一个非常重要的内容:异常的追踪信息。通过getTrace方法可以获取这些信息。此方法的作用相当于PHP的内置函数debug_backtrace。与getTrace方法对应还有一个返回被串化值的方法getTraceAsString,以字符串替代数组返回异常追踪信息
     
  16. 队列(SplQueue)和栈(SplStack)都是双向链表的子类,栈操作的pop和push方法都是直接继承父类,队列操作除了父类的操作外,增加了属于自己的enqueue和dequeue操作,不过它们只不过是父类的push方法和shift方法的别名

    堆、大头堆、小头堆和优先队列是同一类数据结构,都是基于堆的实现。堆是一颗完全二叉树,常用于管理算法执行过程中的信息,应用场景包括堆排序,优先队列等

    PHP的堆以数组的形式存储数据,默认初始化分配64个元素的内存空间

    PHP实现的优先级队列默认是以大头堆实现,即较大的优先,如果要较小的优先,则需要继承SplPriorityQueue类,并重载compare方法
     
  17. CPU只有有限的寄存器可以用于 存储计算数据,而大部分的数据都是存储在内存中的,程序运行都是在内存中进行的

    内存管理的工作就是编程语言实现程序员的工作

    内存管理的主要工作是尽可能高效的利用内存

    普通应用程序和操作系统的关系有点像老师和学生,老师管理一切,而学生的行为会受到老师或学校规定的限制,例如普通应用程序无法直接访问物理内存或者其他硬件资源

    由于向操作系统申请内存空间会引发系统调用,系统调用会将CPU从用户态切换到内核,因为涉及到物理内存的操作,只有操作系统才能进行,而这种切换的成本是非常大的。Web服务器,编程语言这些对性能有要求的应用通常会自己在用户态进行内存管理,例如第一次申请稍大的内存留着备用,而使用完释放的内存并不是马上归还给操作系统,可以将内存进行复用,这样可以避免多次的内存申请和释放所带来的性能消耗

    memory_get_usage(),这个函数的作用是获取 目前PHP脚本所用的内存大小
    memory_get_peak_usage(),这个函数的作用返回当前脚本到目前为止所占用的内存峰值

    出现循环引用,这个就得靠gc来处理了,内存不会在当时就释放,只有在gc环节才会被释放
     
  18. 内存在ZEND内核中是以宏的形式作为接口提供给外部使用

    PHP的内存管理可以看作是分层的。它分为三层:存储层、堆层和接口层

    存储层通过malloc()、mmap()等函数向系统真正的申请内存,并通过free()函数释放所申请的内存。PHP在存储层有4种内存分配方案:malloc,win32,mmap_anon,mmap_zero,默认使用malloc分配内存。PHP的内存方案可以通过设置环境变量来修改

    PHP中的内存管理主要工作就是维护三个列表:小块内存列表、大块内存列表和剩余内存列表。这些列表(bucket)都包含双向链表的实现
    通过对这三个列表的填充,建立一个类似内存池的管理机制

    在PHP5.2版本以及更早之前,没有进行比较复杂的不相邻地址的空闲内存合并,而是集中再次向系统请求。这样做得好处就是运行速度会更快,缺点是随着程序的运行时间的变长,内存的使用情况会“越来越多”。此时不适合做为守护进程长期运行(当然,可以有其他方法解决,而且在PHP5.3中引入了新的GC机制)

    程序使用的所有内存,将在进程结束时统一交还给系统
     
  19. GC的一些基本规则,如果一个引用计数增加,它将继续被使用,当然就不再在垃圾中。如果引用计数减少到零,所在变量容器将被清除。就是说,仅仅在引用计数减少到非零值时,才会产生垃圾周期

    在一个垃圾周期中,通过检查引用计数是否减1,并且检查哪些变量容器的引用次数是零,才发现哪部分是垃圾

    为避免不得不检查所有引用计数可能减少的垃圾周期,垃圾回收算法把所有可能根(都是zval变量容器),放在根缓冲区,同时确保每个可能的垃圾根在缓冲区中只出现一次。仅仅在根缓冲区满了时,才对缓冲区内部所有不同的变量容器执行垃圾回收操作
    PHP提供了gc_collect_cycles()函数可以在根缓存区还没满时强制执行周期回收

    算法中都是模拟删除、模拟恢复、真的删除,都使用简单的遍历(深度优先搜索)即可。模拟恢复是有条件的,当变量的引用计数大于0时才对其做模拟恢复

    PHP的垃圾回收机制在执行过程中以四种颜色标记状态:GC_WHITE 白色表示垃圾,GC_PURPLE 紫色表示已放入缓冲区,GC_GREY 灰色表示已经进行了一次refcount的减一操作,GC_BLACK 黑色是默认颜色,正常
     
  20. 凡是位于速度相差较大的两种硬件之间的,用于协调两者数据传输速度差异的结构,均可称之为Cache

    从最初始的处理器与内存间的Cache开始,都是为了让数据访问的速度适应CPU的处理速度

    引入缓存,就是为了减少小块内存块的查询次数,为最近访问的数据提供更快的访问方式

    用户释放内存块空间时,如果启用了缓存并且所释放的的是小块内存,并且已分配的缓存大小小于缓存限制大小时,程序会将释放的块放到缓存列表中
     
  21. 写时复制(相同值共享内存的策略),不仅节省了内存,并且,还省去了分配内存和管理内存地址的计算开销

    is_ref标识是不是用户使用&的强制引用
    ref_count是引用计数,用于标识此zval被多少个变量引用,即COW自动引用,为0时会被销毁

    PHP中的变量全部基于zval,所以COW的范围是全部的变量,而对于zval结构体组成的集合(如数组和对象等),在需要复制内存时,将复杂对象分解为最小粒度来处理。这样可以使内存中复杂对象中某一部分做修改时,不必将该对象的所有元素全部“分离复制”出一份内存拷贝
     
    <?php
    $foo['love'] = 1;
    $bar  = &$foo['love'];
    $tipi = $foo;
    $tipi['love'] = '2';
    echo $foo['love'];
    &会污染$foo['love'],使其变成了引用,从而Zend没有对$tipi['love']的修改产生内存的复制分离
     
  22. 内存泄漏指的是在程序运行过程中申请了内存,但是在使用完成后没有及时释放的现象
    对于普通运行时间较短的程序来说可能问题不会那么明显,但是对于长时间运行的程序,比如Web服务器,后台进程就比较明显了,随着系统运行占用的内存会持续上升,可能会因为占用内存过高而崩溃,或被系统杀掉

    PHP属于高级语言,语言级别并没有内存的概念(内存由Zend管理)
    PHP使用C编写的解释器,所以本质上还是一样的,如果你的PHP程序内存泄漏了,肯定不是你的错,而是PHP实现的错

    PHP提供了一个hook,我们可以在启动PHP前指定USE_ZEND_ALLOC环境变量为0,即关闭内存管理功能。这样所有的内存分配都会直接向操作系统申请

    将PHP的内存管理关闭,直接向操作系统申请内存不仅效率变差了,可能会导致很多内存只进行了申请而无法正确释放。简单讲:PHP中的异常执行流依赖内存管理来释放内存。这里说的异常执行流指的是PHP实现级别的异常执行流(C语言并不支持异常),在用户看来指的是出现PHP Fatal error或者内部异常时
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值