php跳转方式带rere_PHP 内部新闻 第 48 期:PHP 8、JIT和复杂性

51226a8a898f245638ce1b6001684342.png

Derick Rethans 0: 16

嗨,我是Derick。这里是PHP内部新闻,每周一次的播客,致力于解开PHP语言的开发之谜。这是第48集。今天我将和Sara Golemon谈论PHP 8和JIT。Sara,请你介绍一下自己好吗?

Sara Golemon 0:33

嗨,大家好。大家好,正在收听PHP内部播客。我是Sara。我以前上过这个播客。但如果你是第一次来这里,欢迎来到播客。你有一个很好的积压去通过。我是一个失效的Web开发者,白天来数据库安全工程师,晚上也是一个有主见的开源开发斜线PHP7.2发布经理。我已经断断续续参与这个项目20年了。不知道为什么,我总是回来接受更多的惩罚。

Derick Rethans 1:03

我们正在迎接PHP 8,增加了很多新特性。但 PHP 8 中最重要的一件事,我在播客上早在去年的第 7 集就说过了,那就是 PHP 8 将会有一个 JIT 引擎。你能不能再解释一下JIT引擎是做什么的?

Sara Golemon 1:20

好吧,我要给你简短的,你可以在维基百科上查一下,在两秒钟内对JIT的定义,意思是及时编译。这其实并不能告诉你什么,除非你听上那种另一半的AOT,或者超前编译。AOT是你对像GCC这样的应用程序的期望,你知道,你只是做了一个应用程序,你已经得到了C或C++类型的源代码,这是提前的。JIT是说,好吧,让我们把源码应用起来。让我们用它来运行。让我们以最快的速度开始执行它。最终我们会得到一些编译后的代码。这将会比最初的东西运行得更快一些。PHP已经有了一个很好的虚拟机,我们称它为Zend引擎。我们称它为 Zend 引擎。它接受了你的脚本,并立即说:"好吧,好吧,这个是什么?好吧,好吧,用计算机术语来说,这是什么意思?好吧,计算机可读的术语是一系列这样的操作码,在其他语言中它们也被称为字节码,它们给你的指令是:在这个时候运行这种类型的指令并完成一些事情。PHP运行时解释器一次解释那一条条指令,基本上就是假装成一个CPU。这个效果相当好,运行效率相当高。但是中间还是有这样的瓶颈,就是一个程序假装成CPU运行在CPU上面,以便运行其他代码。JIT 的想法是,坐在中间的这个东西会逐渐弄清楚你的程序到底想做什么,以及它打算如何运行,然后它将把这些 PHP 指令转化为 CPU 指令,这样它就可以摆脱障碍,让 CPU 运行你的代码,就像它是用 AOT 编译语言写的一样。在 PHP 8 中,这对 PHP 代码的执行意味着什么,这仍然是一个,你知道,一个有待回答的问题。我听了你对Zeev的采访。第7集,是一集很好的获得一些好的信息。我们肯定同意PHP内部的JIT是什么状况,现在我们可以。是这样的主观事实,主要是Dmitri做了多少工作,我们可以有点期望看到最好的收益来自哪里。我个人认为,从我们得到的实际性能影响来看,我可能比他更悲观一些。我想我们都认识到,我们不会看到从五到七的那种二比一的改善。没有人现实地期待着这一点,但是如果你看看Zeev几个月前运行的演示,他展示了在两个不同的PHP请求中生成Mandelbrot集,然后将WebSocket输出到一个漂亮的显示器上,这是一个非常直观的反应,因为你可以看到一个Mandelbrot集的计算速度比另一个快得多。而且他承认虽然这不是现实的PHP代码,但是没有人用PHP来写曼德尔布罗特计算。我们可以看到,在一定的工作负载下,它肯定会越来越快。但是对于PHP的核心任务,也就是网络服务,我的意思是,我们都知道它不会有很大的速度。我认为它的速度几乎是难以察觉的。

Derick Rethans 4:41

我的网站有一个问题,曼德尔布罗特集,它的实现都是在一个特定的函数里,对吧?而且都是重CPU的代码,不是IO。

Sara Golemon 4:51

是的,是的。

Derick Rethans 4:52

而且都是在同一个函数里。

Sara Golemon 4:54

是的。

Derick Rethans 4:55

现在,我有一天在想的是,这如何与调用标准库函数互动,因为JIT引擎将不得不走出基本上在CPU上运行的东西,并调用的东西,然后在C开始实现。

Sara Golemon 5:10

所以你问这个问题,是因为你已经知道了JIT的一些陷阱,而你却把我引向了这个问题。这很好。当一个JIT发射器在采取它所发射的语言,所以PHP。只要它仍然在PHP的范围内,它就可以有点跟踪它的位置。它就像,好吧,我知道这个变量的init,你的,因为我看到它被设置了。我知道这里正在进行。我知道这是在那里发生的。它可以带着这些假设,因为它在接纳代码。并发出非常高效的代码,不需要一大堆的双重检查防护,比如,等等,这还是一个整数吗?等等,这还是一个整数吗?等等,这还是一个字符串吗?等等,这还是一个字符串吗?所有这些类似于出错时的逃生舱口。任何时候,当你跨入,我要说的是C-land,或者是internals land,或者是提前编译的土地。它基本上是在调用一个它认为是黑盒子的东西。它只是说, 好吧,这里有一些数据,我知道类型,有乐趣。然后空气中发生了一些引号,这些代码就发生了,黑盒子就会吐出一个答案。好吧,当黑盒吐出答案的时候,JIT已经接受了那段PHP代码,不再知道它的任何假设是否为真。它只是不得不说。好吧,是时候从头开始了,是时候跟踪我们在哪里了,建立一套新的假设。于是我们在执行代码的道路上得到了这个速度的提升。而事实证明,大多数PHP应用都在使用大量的内部API,因为它们相当有用。PHP中有一个厨房水槽,它可以做一些事情。所以,你有这些反复命中的这个路障发生,这不是很好。如果我们想把这个和其他的JIT语言进行比较。我可能会建议我们把这个和HHVM进行比较,因为当然,HHVM,至少在一开始实现了和PHP的方言相当接近的亲戚表兄弟。后来它分化得多了,变成了hacklang。但它做的是同样的事情,把PHP代码,在CPU上原生运行,偶尔要让这个跨到这个自己的内部版本,或者它是运行C++代码。减少这些跳转次数的方法之一是,他们把很多内部函数,那些实际上不需要做任何事情的函数,特别是内部函数,用PHP代码重写。如果你看一下现在的HHVM源代码,有一个很大的目录叫systemlib,那是一大堆hacklang代码,把它理解成PHP代码,就是实现了很多这些很常见的quote unquote内部函数。我们刚刚有一个RFC函数叫做str_contains(),这个函数本来可以百分之百的作为PHP代码来写。有些东西可能会把它扔进packagist里。郑重声明,我投了反对票,正是因为这个原因。我认为你应该把它写在packagist里,然后放在你的composer.json里就可以了。反正会通过的,它的票数很多。撇开这些不谈,如果我们把它放在 8.X 版本的 PHP 中,我们有自己的 systemlib 类型,我们可能会说,让我们把它写成 PHP 代码。这样,当JIT进入该函数时,就可以保持所有这些假设不变,甚至可以内联一些指令,完全避免函数调用。这基本上是把本例中str_contains()函数的所有指令,在调用它的函数范围内实现。所以你跳过了整个函数调用的开销,很多人都知道这仍然是PHP的一个比较薄弱的地方,就像Zeev在第七集里说的那样,我们仍然有一些PHP的部分是有点慢的,不管是JIT。

Derick Rethans 8:50

其实现在有一些函数已经内联成了运码,strlen()就是一个例子,它现在不是一个函数调用,其实直接就是一个运码。因为它是一个使用频率很高的函数,实际上获得了一点性能有。

Sara Golemon 9:05

对,我觉得这些函数也都只是一个单一的操作码进行类型检查。是的,是的。

Derick Rethans 9:10

肯定有一大堆。我看到今天早上早些时候,Dmitri制作了,或者说提出了另一个分支,他在其中实现了跟踪JIT,而不是我们已有的JIT,我不知道普通的JIT引擎和跟踪JIT引擎有什么区别。

Sara Golemon 9:25

最终,这个区别对最终用户来说并不是那么重要,它的功能是要一样的,但它是一种内部实现细节。HVVM的顺便说一下,是一个跟踪JIT。它基本上看任何一个给定的工作单元,它需要翻译,比方说一个函数,它说,有这些排序的非分支部分连接在一起的部分是什么?让我看看每一个非分支部分。让我根据我期望进入的类型,创建一个翻译版本。如果类型失败,我就必须创建一个新的版本。但是那块可以插入到这种tracelets链中,创建一个完整的功能。大多数时候,特别是如果你写的代码有很好的类型提示,你已经打开了,你知道,严格的类型,你已经在函数参数上设置了所有的类型。JIT很容易从你的函数中推断出类型。你只需要为任何给定的部分创建一个跟踪链,你的完整跟踪链将是一个单一的、不间断的链条:做这个,做这个,也许做一个跳转到另一个地方,只是继续做这个,做这个,做这个。如果你有,比方说,稍微杂乱一点的代码,也许你没有使用任何类型提示,那就很难推断出任何类型,因为有很多不同的调用点,在做很多不同的事情。我们可能最终会有一些函数,每个主体部分都有多个tracelet,这些tracelet会被内置到巨大的互连边缘丛中,这在最大化性能方面不太理想,但它至少还能发挥作用。

Derick Rethans 11:06

我们已经讲了一下什么是JIT引擎,也算是它的工作原理。听起来挺复杂的,很复杂。

Sara Golemon 11:14

它肯定是复杂的。而且我感觉这又是一个线索。所以我就顺着它跑。

Derick Rethans 11:19

我还得说我的下一个引导性问题... ... 也许我真的应该问这个问题?

Sara Golemon 11:24

好吧,其实我们先从JIT退一步。让我们看看引擎现在在哪里。所以引擎基本上是两个非常大的部分。那就是所有运行时函数的扩展库的排序。所有你看到的暴露在用户空间的东西,以及实际的脚本引擎。还有其他一些小的部分,但那是两个,两个真正大的部分。有一大堆人非常关注扩展部分,因为那是最炫的部分。那是给你一些以前没有的束缚的部分,或者是一些功能,可以作为那个厨房水槽的一部分开箱交付。而这绝对需要关注。我很高兴这一点继续发展。但脚本引擎是那块定义语法和代码如何实际运行的。

Derick Rethans 12:09

阅读extension的代码要比阅读引擎代码容易很多。

Sara Golemon 12:13

这就是我想说的,是的,如果你看一下ext下的代码,你甚至可以在完全不懂C语言的情况下进入这些代码。实际上你可以很好地理解其中的很多内容,因为 a) PHP 使用了大量的宏。所以每个函数的定义都有一个宏,上面写着 PHP_FUNCTION,就像这里,PHP函数,每个类的方法,PHP_METHOD,这里是类名,这里是方法名。这里是方法名。这些东西做的是非常清晰的API。大多数情况下,它们都是非常小的片段。那些涉及到定义一个类和如何管理内存的部分,会稍微复杂一些,但我认为总体上扩展代码更容易理解。如果你去看看引擎,特别是引擎的运行时部分,虽然编译器也很复杂。你必须做很多的挖掘,然后才可以看到这些部件或许开始如何结合在一起。你和我已经在引擎代码中花费了足够多的时间,我们知道该在哪里寻找一个特定的东西。比如说你提到的那个实现strlen()的操作码。我们知道,哦,zend_vm_def.h里有这个定义。我们也知道那个文件不是真正的代码。它是一个预处理版本的代码,会在以后被构建。有人盲目地来到那个地方,是不会看到很多这些片段的。所以已经有了这个大的坡度,只是为了进入这些引擎,因为它现在存在于7.4中。让我们在这个基础上再加上JIT。你的代码要做调用前向图,和单静态分析,找到这些tracelets,从更高的层次去理解代码,而不是一次一个指令,然后把它提炼成CPU要识别的指令。而CPU指令就是这些包装好的复杂的东西,它处理的是直接的,还有间接的,间接的,还有寄存器。而x86调用ABI是荒谬的东西,没有人应该去看。所以你把所有这些复杂的东西都加进去,顺便说一下,这些东西放在ext/opcache里。这一切都被隔离在这个扩展中,它可以进入引擎,并在周围的事情上做手脚,使所有这些JIT魔法发生。你要把你减少的懂得如何在Zend引擎上工作的开发者集合起来,然后你要进一步减少。我认为目前,真正了解PHP的JIT是如何组合在一起的人仍然只有三四个,他们可以在上面做任何有效的工作。这让我很担心。我不认为这是一座不可逾越的山,尤其是如果我们能开始写一些关于它的文档,至少从高层次的角度来看。嘿,你知道,看这里找到这个东西。看这里可以找到这些东西。一些东西开始。所以那些至少对Zend引擎的虚拟机部分如何工作有基本了解的人,可以升级他们的知识,进入到JIT。我认为这才是值得的。如果我们真的能从JIT中得到真正的性能提升。如果我们真的开启了JIT,我们看到对于PHP的核心工作负载,也就是web服务,我们只看到了1到2%的提升。对我来说,这还不够。对其他人来说,这可能是足够的。但对我来说,我认为这个实验,在这一点上不是失败,而是不成功。当然,有一些人还是会想要使用它,因为他们是你在做命令行应用,他们在做复杂的数学。我不是说我们不能用它。我只是说它需要比前进阶段少的那一点。

Derick Rethans 15:43

之前有人在聊天室里提到。这也是另一组潜在的bug,对吗?

Sara Golemon 15:48

这绝对是另一个潜在的bug。

Derick Rethans 15:51

这几乎是PHP语法位的另一个实现。

Sara Golemon 15:57

所以,如果你运行一个应用程序,你得到了你不期望的行为,这种行为到底来自哪里?你可以花很多时间在Zend引擎中寻找,因为你会想:"哦,好吧,这就是执行操作码的东西。哦,好吧,这是执行操作码的东西。而当我在单命令行中运行时,它肯定是通过这段代码,但在单命令行运行时是有效的。但是在我的web服务器上的第20次请求时,它就不工作了。为什么会出现这种情况呢?嗯,事实证明,它的发生,因为这时JIT终于启动了,因为它有足够的信息。然后它就通过这个有点错误的链路来运行。糟了 几个月前,我们在迈阿密谈话时,你曾经提到过,当Xdebug运行时,你必须把JIT完全关闭。

Derick Rethans 16:41

就像我已经会把OPCache优化关闭一样,因为那里太让人困惑了。

Sara Golemon 16:46

这是混乱和复杂的,但它也是它甚至可能不是100%的可能,因为我们就在那里运行CPU指令的裸机。没有太多的机会去说,哦,等一下CPU先生,让我看看你现在的寄存器。好的,这个没问题,我们现在继续往前走。我们现在在Zend中的虚拟机100%适合这些活动,CPU不适合。这意味着我们在Xdebug运行的开发模式下体验到的东西和我们在真实运行时的代码是不一样的。而且我不知道我们是否有解决方案。

Derick Rethans 17:23

据我所知,根本就没有解决方案。

Sara Golemon 17:26

我是想把它关在笼子里,希望也许有一天我们能有解决方案。

Derick Rethans 17:30

这将是可爱的,但说实话,我看不到这种情况发生。我认为这将是重要的,找出这实际上有多少好处,真正的实时代码。它对你的Laravel项目或Symfony项目或类似的东西有什么好处?我想现在很难有理由不将 PHP 8 与 JIT 一起发布。我认为这有点不公平。但另一方面,如果它像你说的那样,只给你带来一个或 2%的收益,这是否值得有额外的复杂性。额外的维护负担,以及另一个机会,有很多难以重现的bug,但它实际上值得拥有它吗?

Sara Golemon 18:11

我绝对不想在JIT的努力上拉屎。

Derick Rethans 18:14

哦,不,绝对不是。

Sara Golemon 18:15

我认为这是一个重要的实验。而且我认为如果8.0作为一个整体,最终成为它的一种公开测试版实验,那肯定会给我们很多好的信息。我超级希望我们能看到更好的百分比,我们能看到5-10甚至15%的比例。

Derick Rethans 18:31

绝对是。

Sara Golemon 18:32

我想在这样的播客上谨慎地谈论它,因为我不希望任何人说。因为我不希望有人说:"哦,8会很棒的。我们的代码会运行10倍的速度,因为它是运行前 不,这是不可能发生的,两个X是不可能发生的。我们说的是比这更低的数字。要警惕,要充满希望,但8.0将是,就像我说的,它将是那种公共测试版的实验。

Derick Rethans 18:55

我认为这很好。我觉得再跑这个实验,是因为ta类似的实验,当然是在PHP 5.6的时候,PHP 7出来的时候跑的。最初的PHP 7,是采用JIT引擎的PHP。然后Dmitri和其他人发现,它是如此多的其他事情,可以做,使PHP运行几乎两倍的速度。

Sara Golemon 19:16

是的,有很多真正的低垂的果实。

Derick Rethans 19: 19

是的。这一点很不错。我很担心人们认为PHP八中的JIT引擎会有类似的性能提升。

Sara Golemon 19:29

我们将拭目以待。没什么好说的,但是呢:我们会看到的。

Derick Rethans 19:32

但我建议,如果你有兴趣看看这能为你的项目做些什么,你应该去尝试一下。下载 PHP 的主分支,启用它,然后看看它的效果如何。

Sara Golemon 19:41

当然,要确保你是在x86硬件上运行。我很怀疑他是否费心在这上面放了不止一个后端。

Derick Rethans 19:48

其实我也不知道。

Sara Golemon 19:49

我还没看。他可能会用一些辅助库来做。所以有可能我们打的是多个后端。但这可能会是一个x86专用的东西,也可能是一个Linux的东西。我应该找出这个问题的答案。

Derick Rethans 20:00

我也应该这样做。好的,Sara,感谢你今天早上抽出时间来和我聊聊 PHP 8 的 JIT 工作。

Sara Golemon 20:08

一如既往的有趣,我总是喜欢和你Derick说话。你给我的一天带来了明亮的日冕。

最后,别忘了点个在看。


作者:爱业星辰

图片:The Internet

欢迎大家点个在看,分享至朋友圈

看鸡汤不如看科普

074fc22933ab87ff5b8d3c8336f8d78a.png

 怪兽的实验室

天才想法?,全在这里

来自【怪兽的实验室】的推荐阅读:

 PHP 最新版本一览 重新设计 PHP
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值