python是解释器还是编译器,python编辑器和python解释器的区别

  python是解释器还是编译器,python编辑器和python解释器的区别

  使用PHP8中新添加的JIT编译器

  发布时间:2020-06-01 22:09:19

  来源:易俗云

  阅读量:751

  作者:鸽子

  php 8的JIT(justintime)编译器使用php集成的Opcache扩展作为扩展,在运行时直接从cpu指令转换一些操作码。

  也就是说,使用JIT时,Zend VM不需要解释具体的操作码,这些指令直接作为CPU级指令执行。

  PHP 8的JIT

  8 PHP Justin Time(JIT)编译器的影响力毋庸置疑。然而,到目前为止,我发现JIT应该做什么还不清楚。

  经过多次研究和放弃,我决定自己去查PHP源代码。结合我的C语言知识和我现在收集的所有零碎信息,我提出了这篇文章。我希望JIT能帮助我们更好地理解PHP。

  简单来说,如果JIT按预期运行,代码就不会在Zend VM上运行,而是直接作为一组CPU级指令运行。

  这是所有的想法。

  然而,为了更好地理解这一点,我们需要考虑php内部是如何工作的。并不复杂,但需要一些介绍。

  如何执行PHP的代码?

  吵架的音频,PHP是解释性语言。这句话本身是什么意思?

  每次执行PHP代码(命令行脚本或web APP应用程序)时,都会经过PHP解释器。最常用的是PHP-FPM和CLI解释器。

  翻译的工作很简单。接收PHP代码,解释它,并返回结果。

  一般的描述性语言就是这个过程。虽然有语言减少了几步,但总体思路是一样的。在PHP中,过程如下:

  读取PHP代码,并将其解释为一组称为令牌的关键字。这个过程会让解释器知道每个程序写的是哪个代码。这一步被称为词法分析或标记化。获得令牌集合后,PHP解释器将尝试解析它们。通过称为解析的过程生成抽象语法树[ast]。其中AST是表示要执行的操作的节点集。比如“echo1”的实际含义是“打印1 ^ 1的结果”,或者更具体地说,“打印一个操作后,这个操作就变成1 ^ 1”。使用AST,您可以更容易地理解操作和优先级。为了将抽象语法树转换成CPU可执行的操作,需要转换表达式(IR)。它在PHP中被称为操作码。将AST转换成操作码的过程称为编译。有了操作码,有趣的部分就来了。执行代码!PHP有一个名为Zend VM的引擎,可以接收并运行一系列操作码。当所有操作码运行时,Zend VM将退出程序。

  这张图可以让你更清楚:

  简化的PHP解释过程概述。

  如你所见。这里有一个问题。即使PHP代码没有改变,您是否希望每次运行该进程时都运行它?

  让我们来看看操作码。没错!这就是Opcache扩展存在的原因。

  Opcache扩展

  Opcache扩展包含在PHP中,所以通常不需要禁用它。要使用PHP,最好打开Opcache。

  它的功能是给操作码增加一个内存共享缓存层。它的工作是从AST中提取新生成的操作码,并缓存它们以供运行时使用。

  您可以跳过词法分析/标记化和解析步骤。

  这是包含Opcache扩展的进程的图像。

  解释使用PHP Opcache的过程。如果文件已经被分析,PHP将检索缓存的操作码,而不是重新分析。

  完美地跳过了词法分析/标记化、解析和编译的步骤。

  边注:这是一个伟大的PHP 7.4预加载功能RFC!您可以指示PHP FPM分析代码库,将其转换为操作码,并在运行之前缓存它。

  想知道JIT是怎么参与到这个讲解过程中的吗?这篇文章的解释。

  准时制编译的效果如何?

  在听了Zeev关于PHP内部消息的PHP和JIT广播后,我清楚了JIT实际上做了什么。

  如果Opcache扩展可以更快地获取操作码,并将它们直接传递给Zend VM,那么JIT完全不需要使用Zend VM就可以运行。

  Zend VM是用c写的程序,作为操作码和CPU之间的一层。由于JIT在运行时直接生成编译的代码,PHP可以

  跳过Zend VM虚拟机,直接在CPU上运行。理论上性能会更好。

  这听起来可能很奇怪。因为在编译成机器码之前,需要为每个结构类型创建一个特定的实现。但其实这也在情理之中。

  PHP的JIT使用Dy这个名字

  NASM(动态汇编程序)库,它将一组特定格式的CPU指令映射成许多不同CPU类型的汇编代码。所以编译器只需要用DynASM把操作码转换成特定结构的机器码。

  但是,有一个问题困扰我很久了。

  如果预加载可以在执行前将PHP代码解析成操作码,DynASM可以将操作码编译成机器码(刚好及时编译),那我们为什么不使用预运行编译(提前编译)立即编译PHP呢?

  通过听Zeev的广播,我发现了一个原因,PHP是一种弱类型语言,这意味着在Zend VM试图执行某个操作码之前,PHP通常不知道某个变量的类型。

  查看Zend_value联合类型,我们可以看到许多指针指向不同类型的变量。每当Zend VM试图从Zend_value中获取一个值时,它都会使用类似ZSTR_VAL的宏来获取一个指向联合类型中的字符串的指针。

  例如,这个Zend VM处理程序处理“小于或等于”(=)表达式。看看它编码了这么多if else分支,仅仅是为了类型推断。

  用机器码执行类型推理逻辑是不可行的,可能会变得更慢。

  编译前进行评估并不是一个好的选择,因为编译成机器码是一个CPU密集型的任务。所以运行时什么都编译不好。

  那么准时制是如何编译的呢?

  现在我们知道,我们不能很好地推断类型来提前编译。我们也知道运行时编译的计算成本非常高。那么JIT对PHP有什么好处呢?

  为了找到平衡,PHP的JIT试图只编译有价值的操作码。所以JIT会分析Zend VM要执行的操作码,检查可能的编译位置。(根据配置文件)

  当一个操作码被编译后,它会把执行交给编译后的代码,而不是Zend VM。看起来是这样的:

  PHP的JIT解释过程。如果编译,操作码将不会通过Zend VM执行。

  因此,在Opcache扩展中,有两条检测指令来决定是否编译Opcode。如果是,编译器将使用DynASM将这个操作码转换成机器码,并执行这个机器码。

  有趣的是,由于当前接口中编译的代码受到MB的限制(这也是可配置的),代码执行必须能够在JIT和解释代码之间无缝切换。

  顺便说一下,Benoit Jacquemont关于the JIT的演讲帮助我理解了整个事情。

  我现在还不确定编译部分什么时候有效,但是我想现在我真的不想知道。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: