问世 20 多年的 PHP 还是最好的编程语言吗?

发表时间:2018-02-28

作者简介:Panda,一个热爱技术,喜欢刨根问底,热爱分享, 热爱开源的程序猿,活跃于开源社区,乐于分享交流。PHP 开源项目贡献者,对网络安全、PHP 内核、Nginx 内核、MySQL 有一些研究。

本文来自作者在 GitChat 上分享 「2017 年 PHP 社区总结,2018 PHP 发展展望」主题内容。

一、2017 PHP 社区总结:回顾 PHP 语言本身的升级和变化

首先一起回顾下 PHP 的发展史:

  1. PHP 作为 Web 开发领域性价比最高的语言,已经问世 20 多年了。 注:一个 Web 站点可以会使用多种语言作为它的开发语言,本文含有不少从鸟哥 PPT 里的截图,图片版权归鸟哥所有。
  2. PHP 开始于 1994 年,最初产生于 Rasmus Lerdorf 的一个简单的想法,当时 Rasmus 用 C 语言写了一个应用程序,这个程序就是用来追踪和维护自己的个人主页的。
  3. 并且 Rasmus 对其又进行了扩展,使其可以应用于 web 表单还可以和数据库进行交互。就这样 PHP 的第一个版本就诞生了。Rasmus 称其为 “Personal Home Page/Forms Interpreter” 简称 PHP/FI。
  4. 用 Rasmus 自己的话说:起初并不想开发一门新的编程语言,但是随着 PHP/FI 的发展,渐渐的就不再受他的控制了。就这样一个开发团队行程了,并且在 1997 年的 11 月发布了第二个版本 PHP/FI 2。
  5. 再往后,Zeev Suraski 和 Andi Gutmans 两个人的出现,更是使得 PHP 的发展走向了一个新的里程碑。 1997 年,两人重新写了 PHP 的解释器,形成了 PHP 的第三个版本 PHP3,也就是在此时正式名字由 PHP/FI 改为 PHP(Hypertext Preprocessor 超文本预处理器)。 时隔一年,两人在 1998 年又重新写了 PHP 的核心代码,用了将近一年的时间,Zend 引擎在 1999 年诞生了。接着在 2000 年 5 月,带有第一代 zend 引擎的 PHP4 正式发布了。 随后其发展进入了一个平缓的阶段,带有第一代 Zend 引擎的 PHP4 在 2008 年 8 月达到 4.4.9 以后就再没有进行后续开发,也没有任何的安全更新。 我用的最早的一个 PHP4 的程序应该是 DEDECMS 了。这时 PHP 还是面向过程的编程方式。
  6. 在 2004 年 6 月份的时候,PHP 的发展到达了第二个里程碑。带有 Zend Engine II 的 PHP5 正式发布,在这 PHP5 中开始支持面向对象,而且性能明显增强。 直到 2008 年很多程序都不再支持 PHP4 版本了,取而代之的是 PHP5。 接着,下一个人物该出场了,他的出现使 PHP 从 5 又上升了一格成为了 PHP6。他的名字叫 Andrei Zmievski。 当时 PHP5 发布以后,PHP 收到了各种各样的反馈,反馈的内容就是在 PHP 中缺少编码转换的支持。所以在 2005 年的时候,由 Andrei 领导在 PHP 中嵌入了 ICU 库。并且使文本字符串以 unicode-16 的方式呈现。 这一举动,对于 PHP 本身以及用户的编码方式都产生了大的改变,所以 PHP6 应运而生了。 虽说这一改变跨越很大,但是由于开发人员不能很好的理解所做的这些改变,并且向 unicode-16 编码(这一编码方式在 web 环境中很少被用到)转换会导致性能的下降,种种原因导致这一工程停滞下来。 而且在 2009 年发布的 PHP5.3 还有 2010 年发布的 5.4 几乎涵盖了所有从 PHP6 移植来的功能。因此在 2010 年这项工程停止了,直到 2014 年也没有被人们所接受。 在 2014-2015 年期间,PHP7 正式发布了。最初对于 PHP7 的这个版本是存在一些争议的,因为先前的 PHP6 并没有正式发布,就夭折了,所以直接到 7 这个版本并不是很合适。 但是在一些学术论文还有书籍中已经引用了 PHP6 这个名称,所以说最终人们将其定位 7。 对于 PHP7 其主要的目标就是通过重构 Zend 引擎,使 PHP 的性能更加的优化,同时保留语言的兼容性。由于是对其引擎的重构,因此 PHP7 的引擎目前已是第三代 Zend Engine 3。

今天 PHP7 已经正式发布,纵观其从诞生到发展壮大,有成功也有失败,而今天的成功又仅仅源于昨天的一个简单的想法。

作为一名程序员,如果自己在现在的一个想法,多少年后也能产生如此大的成就,那岂是一个 “自豪” 所能表达的。类似的情况也发生在另一个人的身上,linux 的奠基者 linus。

不管怎么说,作为一名 PHP 程序员,看到 PHP 今天的成绩自然感到高兴,自己也会在 PHP 的路上一直走下去,希望 PHP 的发展越来越好。

1.PHP 7.0 的优化

  • 标量类型和返回类型声明(Scalar Type Declarations & Scalar Type Declarations);
  • 更多的 Error 变为可捕获的 Exception;
  • AST(Abstract Syntax Tree,抽象语法树);
  • Native TLS(Native Thread local storage,原生线程本地存储);
  • Zval 的改变。

PHP 的各种类型的变量,其实,真正存储的载体就是 Zval,它特点是海纳百川,有容乃大。从本质上看,它是 C 语言实现的一个结构体(struct)。

对于写 PHP 的同学,可以将它粗略理解为是一个类似 array 数组的东西。

PHP5 的 Zval,内存占据 24 个字节:

PHP7 的 Zval,内存占据 16 个字节:

Zval 从 24 个字节下降到 16 个字节,为什么会下降呢?

这里需要补一点点的 C 语言基础,辅助不熟悉 C 的同学理解。struct 和 union(联合体)有点不同,Struct 的每一个成员变量要各自占据一块独立的内存空间,而 union 里的成员变量是共用一块内存空间。

也就是说修改其中一个成员变量,公有空间就被修改了,其他成员变量的记录也就没有了。因此,虽然成员变量看起来多了不少,但是实际占据的内存空间却下降了。

除此之外,还有被明显改变的特性,部分简单类型不再使用引用。

Zval 结构图:

图中 Zval 的由 2 个 64bits(1 字节 =8bit,bit 是 “位”)组成,如果变量类型是 long、bealoon 这些长度不超过 64bit 的,则直接存储到 value 中,就没有下面的引用了。

当变量类型是 array、objec、string 等超过 64bit 的,value 存储的就是一个指针,指向真实的存储结构地址。

对于简单的变量类型来说,Zval 的存储变得非常简单和高效。

不需要引用的类型:NULL、Boolean、Long、Double;需要引用的类型:String、Array、Object、Resource、Reference。

(1)内部类型 zend_string

Zend_string 是实际存储字符串的结构体,实际的内容会存储在 val(char,字符型)中,而 val 是一个 char 数组,长度为 1,方便成员变量占位)。

结构体最后一个成员变量采用 char 数组,而不是使用 char*,这里有一个小优化技巧,可以降低 CPU 的 cache miss。

如果使用 char 数组,当 malloc 申请上述结构体内存,是申请在同一片区域的,通常是长度是 sizeof(_zend_string) + 实际 char 存储空间。

但是,如果使用 char*,那个这个位置存储的只是一个指针,真实的存储又在另外一片独立的内存区域内。

使用 char[1] 和 char* 的内存分配对比:

从逻辑实现的角度来看,两者其实也没有多大区别,效果很类似。

而实际上,当这些内存块被载入到 CPU 的中,就显得非常不一样。前者因为是连续分配在一起的同一块内存,在 CPU 读取时,通常都可以一同获得(因为会在同一级缓存中)。

而后者,因为是两块内存的数据,CPU 读取第一块内存的时候,很可能第二块内存数据不在同一级缓存中,使 CPU 不得不往 L2(二级缓存)以下寻找,甚至到内存区域查到想要的第二块内存数据。

这里就会引起 CPU Cache Miss,而两者的耗时最高可以相差 100 倍。

另外,在字符串复制的时候,采用引用赋值,zend_string 可以避免的内存拷贝。

(2)PHP 数组的变化(HashTable 和 Zend Array)

在编写 PHP 程序过程中,使用最频繁的类型莫过于数组,PHP5 的数组采用 HashTable 实现。

如果用比较粗略的概括方式来说,它算是一个支持双向链表的 HashTable,不仅支持通过数组的 key 来做 hash 映射访问元素,也能通过 foreach 以访问双向链表的方式遍历数组元素。

这个图看起来很复杂,各种指针跳来跳去,当我们通过 key 值访问一个元素内容的时候,有时需要 3 次的指针跳跃才能找对需要的内容。

而最重要的一点,就在于这些数组元素存储,都是分散在各个不同的内存区域的。

同理可得,在 CPU 读取的时候,因为它们就很可能不在同一级缓存中,会导致 CPU 不得不到下级缓存甚至内存区域查找,也就是引起 CPU 缓存命中下降,进而增加更多的耗时。

PHP7 的 Zend Array:

新版本的数组结构,非常简洁,让人眼前一亮。最大的特点是,整块的数组元素和 hash 映射表全部连接在一起,被分配在同一块内存内。

如果是遍历一个整型的简单类型数组,效率会非常快,因为,数组元素(Bucket)本身是连续分配在同一块内存里,并且,数组元素的 zval 会把整型元素存储在内部,也不再有指针外链,全部数据都存储在当前内存区域内。

当然,最重要的是,它能够避免 CPU Cache Miss(CPU 缓存命中率下降)。

Zend Array 的变化:

  • 数组的 value 默认为 zval。
  • HashTable 的大小从 72 下降到 56 字节,减少 22%。
  • Buckets 的大小从 72 下降到 32 字节,减少 50%。
  • 数组元素的 Buckets 的内存空间是一同分配的。
  • 数组元素的 key(Bucket.key)指向 zend_string。
  • 数组元素的 value 被嵌入到 Bucket 中。
  • 降低 CPU Cache Miss。

(3)函数调用机制(Function Calling Convention)

PHP7 改进了函数的调用机制,通过优化参数传递的环节,减少了一些指令,提高执行效率。

PHP5 的函数调用机制:

图中,在 vm 栈中的指令 send_val 和 recv 参数的指令是相同,PHP7 通过减少这两条重复,来达到对函数调用机制的底层优化。

PHP7 的函数调用机制:

通过宏定义和内联函数(inline),让编译器提前完成部分工作 C 语言的宏定义会被在预处理阶段(编译阶段)执行,提前将部分工作完成,无需在程序运行时分配内存。

能够实现类似函数的功能,却没有函数调用的压栈、弹栈开销,效率会比较高。

内联函数也类似,在预处理阶段,将程序中的函数替换为函数体,真实运行的程序执行到这里,就不会产生函数调用的开销。

PHP7 在这方面做了不少的优化,将不少需要在运行阶段要执行的工作,放到了编译阶段。例如参数类型的判断(Parameters Parsing),因为这里涉及的都是固定的字符常量。

因此,可以放到到编译阶段来完成,进而提升后续的执行效率。例如下图中处理传递参数类型的方式,从左边的写法,优化为右边宏的写法。

2. PHP7.1 的优化

  • 可空类型:主要用于参数类型声明和函数返回值声明;
  • list 的方括号简写: 我们知道在 PHP5.4 之前只能通过 array() 来定义数组,5.4 之后添加了 [] 的简化写法;
  • 允许在 list 中指定 key;
  • void 返回类型:PHP7.0 添加了指定函数返回类型的特性,但是返回类型却不能指定为 void,7.1 的这个特性算是一个补充;
  • 类常量属性设定;
  • 多条件 catch;
  • 详见 RFC 地址。

3. PHP7.2 的变化

  • 语法、函数方面更严格了,性能也有提升。详见 PHP 7 ChangeLog: https://wiki.php.net/rfc/deprecations_php_7_2。
  • PHP 的性能演进 (从 PHP5.0 到 PHP7.1 的性能全评测): http://www.laruence.com/2016/12/18/3137.html。

PHP 周边优秀开源项目

1. 框架篇

  • 目前最火热的 Laravel 类似 ROR 的语法,具有丰富的组件、活跃的社区、更多的语法糖;
  • 然后就是由华人开发的 Yii 框架,Gii 生成代码;
  • 还有国产的 ThinkPHP 快速上手;
  • 还有 codeigniter 简洁方便,还有 Phalcon 是用 C 语言开发的框架,以 PHP 拓展的形式,性能强悍、功能强大。

2. 组件篇

  • 国产的 Swoole 重新定义 PHP,异步、高性能;
  • 国产的 SeasLog C 拓展高性能的 Log 组件;
  • 在此强烈推荐大家看看 Symfony 这个框架解耦做到极致 , 每个模块都是一个组件 , 都可以单独拿出来使用;
  • 大家应该会和微信开发打交道 强烈推荐 EasyWeChat 封装适度、简洁的 API ;
  • PHP-ml 是 PHP 的机器学习库,同时包含算法、交叉验证、神经网络、预处理、特征提取等。

3. 工具篇

  • 推荐 Composer,优秀的 PHP 包管理工具 , 有了它 , 你就可以把社区优秀的开源组件为自己所用;
  • Psysh,这是我经常使用的,调试验证小段代码信手拈来 !
  • Xdebug 断点调试利器 , 让 BUG 无处可逃;
  • Xhprof 专注于性能分析 , 找出你代码慢在哪里 , 持续优化 持续提高性能;
  • Deployer 项目部署工具 简单 快捷 ;
  • Piplint 新鲜出炉的 项目部署系统 国产 中文文档支持 ;
  • 更多有趣的 PHP 组件可以关注这个项目 awesome-php。

4. 项目篇

  • Fecshop :基于 PHP Yii2 框架开发的一款优秀的开源电商系统,可持续发展的电商系统;
  • zentaopms :国内市场市场占有率最高的项目管理 BUG 管理软件;
  • OpenCart :国外电商适用的开源电商系统。

PHP 在 Web 生态中的变化

一句话总结 PHP 在 Web 生态中的变化:更快更高更强。

  • 更快: 从 php5.6 到 php7.0 从 7.0 到 7.1 从 7.1 到 7.2 持续的让内核性能提高 压榨每一个字节 优化每一行代码;
  • 更高:从 DedeCMS 帝国 CMS Discuz 老牌的 Web 系统 到现在的组件化 抽象程度更高了;
  • 更强:从经典的 Web 开发 到现在 PHP-ML 机器学习 UI 桌面软件开发 PHP 更强了。

国内的 PHP 社区

  • Laravel-Chain:不仅仅是 Laravel;
  • SF:有技术问题可以去提问;
  • V2EX:一个小而美的社区。

二、2018PHP 发展展望

PHP 周边生态的发力

编程语言最重要的生态,社区活跃则语言繁荣,社区衰落则语言式微,就好比水与鱼的关系。

正在开发中的 libpkd => 拓展语言原生能力,构建运行时标准库,目前已经基于 Swoole 生态涌出很多优秀的框架。

  • swoft-cloud 崭露头角的 PHP 微服务框架,可能是 PHP 社区的 Spring-Cloud;
  • SwooleDistributedSwooleDistributed 是一款 PHP 结合 swoole 扩展开发的开源高性能的服务器框架。 Swoole 打造底层基石 ,SD 构建上层应用 完美结合。

PHP 程序猿的努力

随着开源的流行 越来越多的 PHPer 在贡献自己的一份力量 GitHub 上 PHP 的开源项目越来越多,例如 packagist 上的组件越来越丰富,而且 PHP 语言本身的性能也在不断提升,让 PHP 语言换发出新的生命力。

参考资料

  • PHP:https://secure.php.net/manual/zh/index.php
  • 技术行者:http://hansionxu.blog.163.com/
  • 风雪之隅:http://www.laruence.com/

声明:封面图为付费下载自视觉中国。