php多线程抓取网页(什么是虚拟机?“语言级虚拟机”可能是不一样的)

优采云 发布时间: 2022-01-17 17:03

  php多线程抓取网页(什么是虚拟机?“语言级虚拟机”可能是不一样的)

  来源:

  什么是虚拟机?

  “虚拟机”是一个很大的概念。从字面上看,“虚拟机”的意思是“虚拟计算机”。我们在学习服务器端编程的时候,相信大部分同学都接触过虚拟机。有这样一个场景,因为我们日常使用的电脑大部分都是Windows操作系统,而服务器软件大多运行在Linux系统上。如果我们在 Windows 上编程,我们不能直接在 Windows 上进行编程。测试非常不方便。基于这个场景,有一个虚拟机。它的作用是在windows系统的基础上运行Linux系统,然后我们可以很方便的在windows系统上测试Linux系统的程序。这个Linux操作系统是通过一些技术手段虚拟化的,

  今天要讲的虚拟机和上面提到的虚拟机略有不同,但是它们解决的问题是一样的。上面提到的虚拟机虚拟化了一个完整的操作系统,我称之为“操作系统级虚拟机”。我们今天要讲的虚拟机是针对编程语言的。它可以达到的效果是相同的代码在不同的操作系统上运行,输出相同的结果。它可以编写一次,到处运行。我称之为“语言级虚拟机”。我们非常熟悉的编程语言,比如Java、PHP、Python等,其实都是基于虚拟机的语言,它们都是跨平台的。我们只需要编写一次代码,就可以在不同的操作系统上运行,

  学过系统编程的同学应该都知道,不同的操作系统为同一个功能提供的“系统API”可能是不同的。例如,Windows 和 Linux 系统都提供了用于网络监控的 API,但它们对应的 SOCKET API 不同。假设我们使用的是平台相关的编程语言(如C、C++),我们在编程的时候一定要注意这个区别。并且针对不同的操作系统做相应的兼容性处理,否则程序在Linux系统上可以正常运行,但是Windows会报错。类似的区别还有很多,具体细节只能从相应的系统编程手册中了解。有些系统API完全不同,而有些只是个别参数不同,并且方法名称完全相同。程序员在写代码的时候需要时刻注意这些,这样才能写出健壮的跨平台代码,这对于新手来说是非常困难的。,而这样一来,程序员就需要将很大一部分精力花在兼容性问题上,而不是专注于实际功能的开发。

  有了虚拟机,上面的问题就不存在了。虚拟机的角色只是一个中介代理。例如,我们是大城市的新手,想租房子。北京、上海、广州等大城市的房东那么多,如果没有房产中介(虚拟机),需要连接N个房东,然后才能租到合适的房子;有了房产中介(虚拟机),我们只需要告诉房产中介(虚拟机)我们要租什么样的房子,房产中介(虚拟机)会协调各个房东,我们就可以租到合适的房子,过程不同,结果是一样的。同理,以Socket API调用为例,我们把写好的代码交给虚拟机,然后虚拟机负责调用系统API,相当于在中间加了一层中介代理,虚拟机会根据操作系统选择正确的。Soekct API,帮助我们完成最终的功能。这样做的好处是程序员不再需要关注底层 API 的细节,可以专注于真实函数的编写。虚拟机帮助我们屏蔽了底层系统API的细节,编程的门槛也大大降低,代码的健壮性也大大提高。帮助我们完成最终的功能。这样做的好处是程序员不再需要关注底层 API 的细节,可以专注于真实函数的编写。虚拟机帮助我们屏蔽了底层系统API的细节,编程的门槛也大大降低,代码的健壮性也大大提高。帮助我们完成最终的功能。这样做的好处是程序员不再需要关注底层 API 的细节,可以专注于真实函数的编写。虚拟机帮助我们屏蔽了底层系统API的细节,编程的门槛也大大降低,代码的健壮性也大大提高。

  PHP执行流程 PHP解释执行流程

  了解PHP的同学都知道PHP是一种解释型语言,也称为脚本语言。它的特点是重量轻,使用方便。传统的编程语言需要经过编译链接后才能执行并输出结果。脚本语言(PHP)省略了这个过程,可以直接通过shell命令执行并输出相应的结果,非常轻量级、直观、易用。说实话,我进坑编程的时候也学过Java。为什么我终于进了PHP的坑?也许这些特点吸引了我。

  刚才我们只讲了PHP的优点,但大多数时候,有得有失。我认为编程语言也是如此。PHP非常轻量级和好用,所以一定是以牺牲一些优势为代价的,否则为什么呢?其他编程语言不这样做。接下来说说PHP的执行过程。我想了解PHP的执行过程,然后才能了解PHP语言设计的选择。

  以下是启用 Opcache 后 PHP 运行的主要流程。

  

  图1

  从图1可以看出,加载PHP代码文件后,首先通过词法分析器(re2c/lex),从代码中提取单词符号(token),再通过语法分析器(yacc/bison) , 从token中找到语法结构后,生成抽象语法树(AST),然后由静态编译器生成Opcode。最后,解释器模拟机器指令来执行每个 Opcode。

  另外,当 PHP 打开 Opcache 时,ZendVM 会缓存 Opcode 并缓存在共享内存中。不仅如此,ZendVM 还会优化​​编译后的 Opcode。编译的优化技术包括方法内联、常量传播和重复代码删除。有了Opcache,不仅可以省去词法分析、语法分析、静态编译等步骤,而且还额外优化了Opcode,程序的执行效率比第一次执行要快。

  以上就是PHP解释和执行的过程。虽然解释和执行对程序员非常友好,省略了静态编译这一步,但实际上这个过程并没有省略。它只是由虚拟机为我们完成,以牺牲一些性能为代价。重量轻、易于使用和灵活。其中,词法分析、语法分析、静态编译、解释执行等过程都是在执行过程中完成的。

  编译语言执行过程

  在了解了解释型语言的执行过程之后,我们再来看看编译型语言的执行过程作为对比,看看它与解释型语言有什么不同。

  

  图 2

  从图2可以看出,虚线框内的执行过程包括:词法分析、语法分析、编译。这三个步骤也收录在 PHP 的解释和执行中。唯一不同的是,C/C++的这三个步骤都是在编译过程中由编译器提前完成的,这样在运行时节省了大量的时间和开销。汇编代码生成后,第四步,链接汇编文件,生成可执行文件。这里的可执行文件是指二进制机器码,可以直接由CPU执行,无需额外翻译。这四个步骤统称为静态编译。可以清楚地看出,与解释型语言相比,编译型语言在前期需要做更多的工作,而是换取更高的性能和执行效率。因此,一般在大型项目中,由于性能要求比较高,代码量较大,如果使用解释型语言,执行效率会大大降低。使用静态编译可以达到更好的执行效率,降低服务器采购成本。

  什么是 JIT?

  JIT可以说是虚拟机中技术含量最高的技术。刚才我们讲了解释型语言和编译型语言的执行过程,分析了各自的优缺点。我们可以思考一下,是否有一种技术,它不仅在解释语言中具有轻量级和易于使用的优点,而且还具有编译语言的高性能。结论是 JIT。下面我们要介绍的是编程语言中的JIT技术。它的全称是“即时编译”。这是什么意思?我们先来看看维基百科对即时编译的定义。

  在计算机技术中,即时编译(英文:just-in-time compilation,缩写为JIT;也译作即时编译、实时编译),又称动态翻译或运行时编译,是一种执行计算机代码的方法,其中一种方法涉及在程序执行期间(在运行时)而不是在执行之前进行编译。通常,这涉及将源代码或更常见的字节码转换为机器代码,然后直接执行。实现 JIT 编译器的系统通常会不断分析正在执行的代码,并确定代码的某些部分在哪里编译或重新编译。

  正如我们刚才所说,JIT不仅具有解释语言的轻量级易用性,而且具有高性能,那么它是如何实现的呢?以PHP8新增的JIT特性为例,下图描述了PHP开启JIT特性后的执行过程。PHP8-JIT基于对Opcache的优化,在编译前对保存在Opcache中的Opcode进行优化。将Opcode编译成CPU可识别的可执行文件,即二进制文件,相当于C++编译后的可执行文件,但是这个过程不需要在运行前完成,而是在运行时虚拟机启动一个后台线程, 将Opcode转换成二进制文件,二进制文件缓存后,下次执行逻辑时,CPU直接执行即可,无需多说,理论性能与C++相同。这样做的好处是在实现高性能的同时,保留了 PHP 语言的易用性和灵活性。

  

  图 3

  JIT的触发条件

  JIT实际上是将运行时的一部分代码转换成可执行文件并缓存起来,以加快下一段代码的执行速度。那么程序启动后会触发JIT吗?

  程序第一次启动时,JIT 不起作用。可以理解为,PHP/Java代码第一次执行时,依然是以解释的形式运行。程序运行一段时间后需要触发JIT。说到这里,你有没有和我一样的疑问,为什么JIT在程序启动的时候会把所有的代码都转换成可执行文件并缓存起来,就像C++一样,这样不是更高效吗?Java 语言中确实有少量这样的应用,但都不是主流。主要有以下几个原因

  全部编译成二进制文件需要很多时间,而且程序启动会很慢,这对于大型项目来说是无法接受的。并不是所有的代码都是性能优化所必需的,大部分代码在实际场景中用的不多。编译成二进制会占用大量容量。提前编译相当于静态编译。与静态编译相比,JIT 编译具有许多不可替代的优势。

  JIT的触发条件主要基于“计数器的热点检测”。虚拟机为每个方法(或代码块)建立一个计数器。如果执行次数超过某个阈值,则认为是“热方法”。达到阈值后,虚拟机启动后台线程,将代码块编译成可执行文件,缓存在内存中,加快下一次执行的速度。以上只是对热代码的触发规则的简单说明。实际虚拟机采用的规则会比这个复杂。

  JIT和提前编译的优缺点

  JIT 编译器是在运行时执行的,我们可以很容易地看出,与提前编译相比,它有几个明显的缺点。首先,JIT 编译需要消耗运行时计算资源。最初,这些资源可用于执行程序。不管JIT编译器怎么优化(比如分层编译),这都是一个无法回避的问题,而最耗费资源的一步就是“过程间分析”,比如分析方法是否永远不能被调用,抽象是否方法只会调用单个版本的结论,这些信息对于生成高质量代码非常有价值,但是要准确获取这些信息必须经过大量耗时的计算,消耗大量的运行时计算资源. 反过来,

  说了这么多,JIT编译和提前编译在性能优化上真的没有优势吗?结论是否定的,JIT 编译具有提前编译的许多不可替代的优点。正是因为JIT编译器是在运行时进行的,所以JIT编译器才能获取程序的真实数据。通过在程序运行时不断的采集监控信息,分析数据,JIT编译器可以对程序做一些事情。提前静态编译器无法进行的激进优化。

  一是性能分析指导优化。比如JIT编译器在运行的时候,通过程序运行的监控数据,如果发现一些代码块执行的非常频繁,可以重点优化这块代码,例如:分配更好的寄存器、缓存等到此代码。

  然后是积极的预测优化。比如有一个接口有3个实现类,但在实际运行过程中,实现类A的运行时间超过95%。通过数据分析,可以从根本上进行预测。每次执行 A。如果多次发现预测错误,可以返回解释状态再次执行,但这只是小概率事件,不影响程序执行结果。

  最后是链接时优化。传统的编译步骤是将编译优化和链接分开。这意味着什么?添加一个程序需要用到A、B、C三个库。编译器首先编译这3个类库,并通过各种手段进行优化,转换成汇编代码保存在一个文件中。最后一步是转换这 3 个库。汇编文件链接在一起,最后转换成可执行文件。这里有一个问题。三个库 A、B 和 C 在编译时分别进行优化。不可能假设 A 和 B 中的某些方法被重复执行,或者可以通过方法内联进行优化。但是JIT编译器的不同之处在于它在运行时是动态链接的,可以优化整个程序的调用栈,

  总结

  写这篇博​​客的主要目的是总结这段时间学到的虚拟机相关技术。google了一下PHP虚拟机相关的文章,发现可以参考的文章很少。由于Java和PHP的执行原理非常相似,所以我想通过学习Java虚拟机可以理解ZendVM的工作原理。Java虚拟机非常成熟,可以说是虚拟机的鼻祖。有很多关于 JVM 世界的优秀书籍。它给我带来了一个新的世界,让我对虚拟机有了新的认识,JIT技术更让我惊喜。

  最后,PHP 是世界上最好的语言!

  参考

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线