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. 第8.11节 Python类中记录实例变量属性的特殊变量__dict__

    一. 语法释义 调用方法:实例. __dict__属性 __dict__属性返回的是实例对象中当前已经定义的所有自定义实例变量的名和值,用字典存储,每个元素为一个"实例变量名:值" ...

  2. Google浏览器PostMan插件版安装步骤

    PostMan插件版安装步骤: 第一步:把下载后的.crx扩展名的离线Chrome插件的文件扩展名改成.zip或者.rar 第二步:右键点击该文件,并使用压缩软件(如winrar.360压缩等)对该压 ...

  3. 总结下flask中的宏、Jinjia2语法

    这几天学的东西比较多,时间又有点不够用,趁着快吃饭了,赶紧总结总结. 00x1 宏: 如果学过C语言的童鞋,可能知道宏在C语言里面是一个定义一个固定参数的变量.在flask里面,宏是相当于一个函数的作 ...

  4. mysql游标cursor与for循环

    delimiter // create procedure p2() begin declare row_id int DEFAULT 0; declare row_num int DEFAULT 0 ...

  5. swagger添加统一认证参数

    import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Co ...

  6. go学习第四天

    昨天通宵加班,暂停了一天学习,今天再偷懒下,学习半个小时

  7. Day6 Scrum 冲刺博客

    一.站立式会议# 1. 会议照片 2. 工作进度+燃尽图  团队成员 昨日完成工作  今日工作计划 遇到的困难  周梓波  将方块旋转变形  添加键盘监听事件  不熟悉监听事件的操作  纪昂学  左右 ...

  8. geoserver的demo使用过程

    先贴一个效果图,使用的geoserver版本2.18.0,需要对应版本插件netcdf插件[Extensions>Coverage Formats>NetCDF],使用tomcat8进行发 ...

  9. 页面上下载canvas中的内容作为图片

    使用如下代码,获得Canvas图像对应的data URI,也就是平常我们所说的base64地址 var dataUrl = document.getElementById("canvasId ...

  10. li = [11,22,33,44,55,66,77,88,99]分类

    方法一: li = [11,22,33,44,55,66,77,88,99]s = []m = []for i in li: if i <= 55: s.append(i) else: m.ap ...