PHP 团队于2020年11月26日宣布 PHP 8 正式发布!这意味着将不会有 PHP 7.5 版本。PHP8 目前正处于非常活跃的开发阶段,所以在接下来的几个月里,情况可能会发生很大的变化。我也分享一些研究PHP 8 的心得,希望PHPer大家一起共同进步。首先说一下最受关注的JIT。 

JIT

  由于 PHP 8 是一个新的大版本,因此升级版本,代码被破坏的可能性更高。如果项目始终保持运行 PHP 的最新版本,那么升级相对来说就会轻松很多,因为在 7. * 版本中,大多数重大更改均已弃用。除重大更改外,PHP 8 还带来了一些不错的新功能,比如说 JIT 编译器 , 联合类型 , 属性,以及更多。很多人可能对JIT有很深的误解,觉得引入JIT之后性能就能提高10倍跟V8平起平坐了,事实上不是这样的。JIT技术的水很深,动态语言的JIT尤其困难,V8的诞生几乎可以说是一个技术奇迹。以PHP社区的技术水平,我谨慎地不看好他们解决这个问题的能力,毕竟Facebook的HHVM也没有完全解决,最后是靠Hacklang补全PHP的语法功能之后才基本圆满解决的。

  动态语言的JIT本质要解决的问题之中,生成汇编只是一小部分,对于弱类型和动态类型语言来说,优化内存布局也是重点。例如,对于JavaScript和Python来说,以前对象内部是一个HashMap,这种数据结构的访问效率比较低,导致访问对象的每个属性都很慢,在JIT之后会将它优化成类似C++的平铺式的布局,将属性的值按顺序放在特定的位置上,这就带来一些新的要求:

  1. 没有类型标注的情况下,JIT只能猜测类型而无法肯定,那么使用优化的类型布局之前需要进行额外的检测,判断是否的确为预想的类型;

  2. 属性的类型也需要进一步推测,使用时也需要检验;

  3. JavaScript、Python乃至PHP都支持在对象创建之后为它添加新的属性。之前符合推测的类型后来添加或者删除了属性,要怎么处理?

  除此之外,调用函数时候如何优化调用开销也是一个重点,本质上跟优化对象的内存布局是类似的,可以将传入参数看成是构建一个有多个属性的对象,每个属性的类型不同。局部变量也需要有选择性地优化到寄存器、栈和堆当中。

  PHP在这里的优势是支持类型标注,缺点是所有Hacklang里面修改掉的部分:

  1. 不支持泛型,尤其是array类型不支持泛型。将一个变量类型标注为array几乎没有任何帮助,PHP中的array可以是顺序表也可以是hashmap,还可以混着,value的类型也不确定,这些都对类型优化有很高要求。Hacklang就推荐废掉array改用vector等几个确定类型且支持泛型的数据结构。

  2. reference这个功能,这个功能非常容易成为内存布局优化的障碍,也会阻碍JIT生成高效代码,尤其是数组中可以存储reference这件事,JIT编译器完全无法从字面上判断某条对array元素赋值的语句是否会影响环境中的其它变量的值。这也是为什么Hacklang直接删掉了这个功能。

  3. 其他参考Hacklang的变更

  之前版本(PHP7)抠解释器实现带来的性能优化也会是一个阻碍,JIT的时候这些都得放弃掉,因为内存布局不一样了,这样可能导致最初的时候许多应用JIT反而变慢。所以,PHP8如果解决不了这些问题,最大的可能是许多microbenchmark速度大幅上升,但整体应用性能持平,自娱自乐。

联合类型

  考虑到 PHP 动态语言类型的特性,现在很多情况下,联合类型都是很有用的。联合类型是两个或者多个类型的集合,表示可以使用其中任何一个类型。

public function foo(Foo|Bar $input): int|float;

  联合类型中不包含 void,因为 void 表示的含义是 “根本没有返回值”。 另外,可以使用 |null 或者现有的 ? 表示法来表示包含 nullable 的联合体 :

public function foo(Foo|null $foo): void;

public function bar(?Bar $bar): void;

属性

  属性在其他语言中通常被称为 注解 ,提供一种在无需解析文档块的情况下将元数据添加到类中的方法。

use App\Attributes\ExampleAttribute;

<<ExampleAttribute>>
class Foo
{
<<ExampleAttribute>>
public const FOO = 'foo'; <<ExampleAttribute>>
public $x; <<ExampleAttribute>>
public function foo(<<ExampleAttribute>> $bar) { }
}

新增 static 返回类型

  尽管已经可以返回 self,但是 static 直到 PHP 8 才是有效的返回类型 。考虑到 PHP 具有动态类型的性质,此功能对于许多开发人员将非常有用。

class Foo
{
public function test(): static
{
return new static();
}
}

新增 mixed 类型

  有人可能将其称为必要的邪恶:mixed 类型让许多人感觉十分混乱。然而,有一个很好的论据支持去实现它:缺少类型在 PHP 中会导致很多情况:

  • 函数不返回任何内容或返回空值
  • 我们需要多种类型的一种类型
  • 我们需要的是 PHP 中不能进行类型提示的类型

  因为上述原因,添加 mixed 类型是一件很棒的事儿。mixed 本身代表下列类型中的任一类型:

  • array
  • bool
  • callable
  • int
  • float
  • null
  • object
  • resource
  • string

  请注意,mixed 不仅仅可以用来作为返回类型,还可以用作参数和属性类型。

  另外,还需要注意,因为 mixed 类型已经包括了 null,因此 mixed 类型不可为空。下面的代码会触发致命错误:

// 致命错误:混合类型不能为空,null已经是混合类型的一部分。
function bar(): ?mixed {}

throw 表达式

  将 throw 从一个语句更改为一个表达式,这使得可以在很多新地方抛出异常:

$triggerError = fn () => throw new MyError();

$foo = $bar['offset'] ?? throw new OffsetDoesNotExist('offset');

允许对对象使用 ::class

  一个很小但是很有用的新特性:现在可以在对象上使用 :: class ,而不必在对象上使用 get_class() ,它的工作方式跟 get_class() 相同。

$foo = new Foo();

var_dump($foo::class);

Non-capturing catches

  在 PHP 8 之前,无论何时你想要捕获一个异常,你都需要先将其存储到一个变量中,不管这个变量你是否会用到。通过 Non-capturing catches 你可以忽略变量,所以替换下面的代码:

try {
// 执行错误代码段
} catch (MySpecialException $exception) {
Log::error("错误");
}

  你现在可以这么做:

try {
// 执行错误代码段
} catch (MySpecialException) {
Log::error("错误");
}

  请注意,必须始终指定类型,不允许将 catch 留空,如果你想要捕获所有类型的异常和错误,需要使用 Throwable 作为捕获类型。

新增 str_contains() 函数

  这是早该出现的函数,我们最终不必再依赖 strpos 来知道一个字符串是否包含另一个字符串。

  无需这样做:

if (strpos('string with lots of words', 'words') !== false) { /* … */ }

  现在可以这样了:

if (str_contains('string with lots of words', 'words')) { /* … */ }

新增 str_starts_with() 和 str_ends_with() 函数

  也是一组早该出现的函数,顾名思义:

str_starts_with('haystack', 'hay'); // true
str_ends_with('haystack', 'stack'); // true

重新分类的错误信息

许多以前仅触发警告或通知的错误已转换为适当的错误。以下警告已更改。

  • 变量未定义:Error 异常代替通知
  • 数组索引未定义:警告代替通知
  • 除以零:DivisionByZeroError 异常代替警告
  • 尝试添加 / 移除非对象的属性 '% s' :Error 异常代替警告
  • 尝试修改非对象的属性 '% s' :Error 异常代替警告
  • 尝试分配非对象的属性 '% s' :Error 异常代替警告
  • 从空值创建默认对象:Error 异常代替警告
  • 尝试获取非对象的属性 '% s' :警告代替通知
  • 未定义的属性:% s::$% s:警告代替通知
  • 无法添加元素到数组,因为下一个元素已被占用:Error 异常代替警告
  • 无法在非数组变量中销毁偏移量:Error 异常代替警告
  • 无法将标量值用作数组:Error 异常代替警告
  • 只有数组和 Traversables 可以被解包:TypeError 异常代替警告
  • 为 foreach () 提供了无效的参数:TypeError 异常代替警告
  • 偏移量类型非法:TypeError 异常代替警告
  • isset 或 empty 中的偏移量类型非法:TypeError 异常代替警告
  • unset 中的偏移量类型非法:TypeError 异常代替警告
  • 数组到字符串的转换:警告代替通知
  • 资源 ID#% d 用作偏移量,转换为整数 (% d):警告代替通知
  • 发生字符串偏移量转换:警告代替通知
  • 未初始化的字符串偏移量:% d:警告代替通知
  • 无法将空字符串分配给字符串偏移量:Error 异常代替警告
  • 提供的资源不是有效的流资源:TypeError 异常代替警告

@ 运算符不再使致命错误不提醒

  @符是一个偷懒解决问题的办法,此更改可能会使 PHP 8 之前的版本被 @ 隐藏的错误再次显示出来。请确保在生产服务器上设置了 display_errors=Off !

默认错误报告级别

  现在的默认错误报告级别是 E_ALL 而不是之前的除 E_NOTICE 和 E_DEPRECATED 的所有内容。这意味着可能会弹出许多错误,这些错误以前曾被忽略,尽管在 PHP 8 之前的版本中可能已经存在。

默认 PDO 错误模式

  这个改动很坑,PDO 的默认错误模式改为静默。这意味着当出现 SQL 错误时,除非开发人员实现了自己的错误处理,否则不会发出任何错误或警告,也不会引发任何异常。

串联优先级

  在 PHP 7.4 中已废弃,在8.0开始生效。如果你像这样子书写:

echo "sum: " . $a + $b;

  PHP 以前会如是理解:

echo ("sum: " . $a) + $b;

  PHP 8 :

echo "sum: " . ($a + $b);

暂时就讲这些比较有用的新特性吧,一些不常用的就不浪费大家时间了。

关于万众期待的JIT,我还想说一些,JIT会让我的项目更快吗?

  

  很有可能并不明显。也许不是我们期望的答案:在一般情况下,用PHP编写的应用程序是I/O绑定的,然而JIT在CPU绑定的代码上工作得最好。

关于I/O绑定和CPU绑定最简单的说法是:

  • 如果我们能够改进(减少、优化)它所做的I/O,那么一段I/O绑定的代码将会运行得更快。
  • 如果我们能够改进(减少、优化)CPU正在执行的指令,或者(神奇地)提高CPU的时钟速度,那么一段CPU限制的代码就会运行得更快。
  • 一段代码或一个应用程序可以是I/O绑定、CPU绑定,或者与CPU和I/O同等绑定。
  • 一般来说,PHP应用程序往往是I/O绑定的——减慢它们速度的是它们正在执行的I/O——连接、读取和写入数据库、缓存、文件、套接字等等。

PHP实际上相当快,它是世界上解释速度最快的语言之一。Zend VM调用与I/O无关的函数,和在机器代码中进行相同的调用之间,没有显著的区别。而PHP的瓶颈也从来不是其他的,正是I/O。

所以JIT好像没什么用?

  其实不然,引入JIT总体来讲是一个积极正面的发展:

  1. 目前已经很难通过常规手段提升 PHP 的性能,JIT 基本上是目前性能提升的唯一手段;
  2. JIT 带来的性能提升可以让 PHP 在更多使用场景( CPU 密集)中发挥作用;
  3. 可以使用 PHP 来开发内置函数,而不用担心性能方面的问题。这一方面可以加速语言的发展(更多PHPer可以参与进来),同时也可以减少目前使用 C 开发内置函数,容易出现的内存管理、溢出等问题。

  JIT的引入,对整个语言的使用场景扩展,及语言生态发展有很深远的意义。语言可以有局限,但是人拥有无限可能。许多PHPer把自己局限在web一个角落内里。JIT的引入,现在人人都可以去拥抱PHP带来的转变与生态:Swoole解决了IO密集场景问题,JIT解决了运算密集场景问题,未来PHP的发展更让人期待。

认识PHP8的更多相关文章

  1. php8.0正式版新特性和性能优化学习

    前言 PHP团队宣布PHP8正式GA(链接).php的发展又开启了新的篇章,PHP8.0.0版本引入了一些重大变更及许多新特性和性能优化机制.火速学习下~ JIT(Just in Time Compi ...

  2. PHP8开启PHPStorm + Xdebug3

    下载Xdebug 需要下载对应php版本xdebug 否则对加载xdebug失败 https://xdebug.org/download 我的是PHP版本 为php8.0.3-nts-x64 安装xd ...

  3. ThinkPHP V6.0.12在php8.1下验证码出现问题

    一.问题描述 1.项目需求要求使用PHP8.1.*版本 2.运行程序发现验证码不生效报错如下: 二.错误描述 1.报错信息得出:从浮点(数字)到整数的隐式转换将失去精度 三.解决流程 1.找到报错文件 ...

  4. PHP8中match新语句的操作方法

    PHP8 新出的一个语法很好用,就是 match 语句.match 语句跟原来的 switch 类似,不过比 switch 更加的严格和方便 原来的 switch 语句代码如下: 1 function ...

  5. PHP8年开发经验原创开发文档教程

    订阅微信公众号: gzgwgas 每天为你分享PHP开发经验,坚决不踩坑,坚决不入坑. 微信扫码,关注公众号有惊喜!

  6. Docke 搭建 apache2 + php8 + MySQL8 环境

    Docker 安装 执行 Docker 安装命令 curl -fsSL https://get.docker.com/ | sh 启动 Docker 服务 sudo service docker st ...

  7. uva 1599 ideal path(好题)——yhx

    aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAABGYAAAODCAYAAAD+ZwdMAAAgAElEQVR4nOy9L8/0ypH/Pa8givGiyC

  8. PHP 调用 Go 服务的正确方式 - Unix Domain Sockets

    * { color: #3e3e3e } body { font-family: "Helvetica Neue", Helvetica, "Hiragino Sans ...

  9. SQL注入之重新认识

    i春秋作家:anyedt 原文来自:https://bbs.ichunqiu.com/thread-41701-1-1.html 引言 作为长期占据 OWASP Top 10 首位的注入,认识它掌握它 ...

随机推荐

  1. 老猿学5G扫盲贴:PDU协议数据单元、PDU连接业务和PDU会话的功能详解

    专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt入门学习 老猿Python博文目录 老猿学5G博文目录 一.PDU 关于PDU在百度百科是这样定义的:协议 ...

  2. Python中splitlines方法判断文本中一行结束除了回车换行符是否还有其他字符?

    Python中splitlines([keepends])方法用于返回由原字符串中各行组成的列表,在行边界的位置拆分. 如果keepends=True,结果列表中包含行边界,否则不包含 行边界的字符. ...

  3. 第九章 Python文件操作

    前一阵子写类相关的内容,把老猿写得心都累了,本来准备继续介绍一些类相关的知识的,如闭包.装饰器.描述符.枚举类.异常等,现在实在不想继续,以后再开章节吧.本章弄点开胃的小菜提提神,介绍Python中文 ...

  4. PyQt(Python+Qt)学习随笔:QTreeWidgetItem项中列数据的访问方法

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 树型部件QTreeWidget中的QTreeWidgetItem项中可以有多列数据,每列数据可以根据 ...

  5. 效率神器-MouseInc推荐和使用

    主要功能 鼠标手势 按住右键滑动即可开始使用. 配置细微,可自由修改手势宽度,颜色,识别灵敏度等. 支持黑名单,支持特定软件自定义手势,支持复合动作. 功能非常强大,比如下面的操作: 选中一个网址,画 ...

  6. 哔哩哔哩批量采集器(支持windows和mac)

    链接:https://pan.baidu.com/s/1jW2ea0Cl1xL5xN9DuB8Fcw  密码:klyw

  7. GaussDB(DWS)应用实践丨负载管理与作业排队处理方法

    摘要:本文用来总结一些GaussDB(DWS)在实际应用过程中,可能出现的各种作业排队的情况,以及出现排队时,我们应该怎么去判断是否正常,调整一些参数,让资源分配与负载管理更符合当前的业务:或者在作业 ...

  8. Python 学习笔记 之 02 - 高级特性总结

    切片 语法:  li.[x:y:z]  li为list.tuple等数据类型,x为开始进行切片的位置,y为切片停止的位置(不包含y),z为xy切片后的结果里,每间隔z个元素输出一次结果.  x默认为0 ...

  9. Python开发:一个直播弹幕机器人诞生过程,自动发送弹幕

    前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理. Python爬取B站弹幕视频讲解 https://www.bilibili.com/vide ...

  10. 【震惊】padding-top的百分比值参考对象竟是父级元素的宽度

    引言 书写页面样式与布局是前端工程师Coding 中必不可少的一项工作,在定义页面元素的样式时,padding 属性也是经常被使用到的. padding 属性用于设置元素的内边距,其值可以是lengt ...