本文主要是从JavaScriptJust-in-time(JIT)的工作原理以及编译器如何生成程序集来了解WebAssembly的独有特性。第一,等等,什么是WebAssembly?WebAssembly是除JavaScript之外的另一种可以在浏览器中执行的编程语言。所以当人们说WebAssembly更快时,他们通常是与JavaScript相比。这并不意味着您在开发时只能选择WebAssembly或JavaScript。事实上,我们更希望您在同一个项目中同时使用这两者。有必要将两者进行比较,以便您了解WebAssembly所具有的独特特性。第二,关于性能的一些历史JavaScript是1995年出来的,它的初衷不是为了执行快,而且前10年,它的执行速度确实不快。紧接着,浏览器市场的竞争变得激烈起来。广为人知的“性能大战”始于2008年。许多浏览器引入了即时编译器,也称为JIT。基于JIT模式,JavaScript代码运行速度逐渐加快。正是因为这些JIT的引入,JavaScript的性能达到了一个转折点,JS代码的执行速度快了10倍。随着性能的提升,JavaScript可以应用到以前从未想过的领域,比如用于后端开发的Node.js。性能的提升大大扩展了JavaScript的应用范围。现在有了WebAssembly,我们很可能正处于第二个转折点。那么,JIT是如何工作的呢?让我们来看看。三、JavaScriptJust-in-time(JIT)的工作原理1、JavaScript是如何在浏览器中运行的?如果您是一名开发人员,当您决定在您的页面中使用JavaScript时,有两件事需要考虑:目标和问题。目标:告诉计算机你想做什么。问题:你和电脑说的是不同的语言,无法交流。你说人类语言,而计算机使用机器语言。机器语言也是一种语言,但是JavaScript或者其他高级编程语言机器可以看懂,人类不会用它们来交流。它们是基于人类认知而设计的。所以,JavaScript引擎的工作就是将人类语言转换成机器可以理解的语言。这就像电影《降临》中人类与外星人的互动。在电影中,人类和外星人不仅语言不同,看世界的方式也不同。其实人和机器是相似的(后面会详细介绍)。那么,翻译是如何工作的呢?在代码的世界里,翻译机器语言一般有两种方式:解释器和编译器。如果是通过解释器,翻译是在解释的同时逐行执行的。编译器就是将源代码编译成目标代码。执行时不再需要编译器,直接在支持目标代码的平台上运行。两种翻译方法各有利弊。2.口译员的优缺点口译员的启动和执行速度更快。您无需等待整个编译过程来运行您的代码。从第一行开始翻译,可以继续依次执行。正是出于这个原因,解释器似乎更适合JavaScript。对于Web开发人员来说,能够快速执行代码并查看结果非常重要。这就是为什么第一个浏览器使用JavaScript解释器的原因。但是当你多次运行相同的代码时,解释器的缺点就会显现出来。比如你执行一个循环,解释器要一遍又一遍地翻译,这是一种低效的表现。3.编译器的优缺点编译器的问题恰恰相反。编译整个源代码然后生成要在机器上执行的目标文件需要一些时间。带有循环的代码执行速度非常快,因为它不需要重复翻译每个循环。另一个区别是编译器可以花更多的时间优化代码,从而使代码执行得更快。解释器在运行时执行此步骤,这决定了它不能在翻译过程中花费大量时间进行优化。4、即时编译器:结合两者的优点为了解决解释器效率低下的问题,后来的浏览器也引入了编译器,形成混合模式。不同的浏览器实现这个功能的方式不同,但基本思想是一样的。向JavaScript引擎添加监视器(也称为分析器)。监视器监控代码的运行状态,记录代码运行了多少次、运行情况等信息。最初,监视器监视所有通过解释器的代码。如果同一行代码运行多次,则一段代码被标记为“温”,如果运行多次,则标记为“热”。5.BaselineCompiler如果一段代码变得“暖”了,那么JIT就把它送到编译器去编译,并存储编译结果。代码段的每一行都会被编译成一个“存根”,存根被赋予一个“行号+变量类型”的索引。如果monitor监听到相同代码和相同变量类型的执行,则直接将编译后的版本推送给浏览器。这样可以加快执行速度,但正如我前面所说,编译器也可以想办法更有效地执行代码,也就是优化。基线编译器可以做一些这样的优化(下面我会举个例子),但是基线编译器优化的时间不能太长,因为它会让程序的执行卡在这里。但如果代码真的很“热”(即几乎所有的执行时间都花在这里),那么花一些时间优化是值得的。6.优化编译器如果代码段变得“非常热”,监视器将它发送给优化编译器。生成更快、更高效的代码版本并将其存储。为了生成更快的代码版本,优化编译器必须做出一些假设。例如,它可以通过假设由同一个构造函数生成的实例都具有相同的形状来优化此模式——也就是说,所有实例都具有相同的属性名称并以相同的顺序初始化。整个优化器工作的链条是这样的。监视器从它监视的代码的执行情况中做出自己的判断,然后将它整理的信息传递给优化器进行优化。如果一个循环的前一次迭代中的对象具有相同的形状,则可以认为以后迭代的对象将具有相同的形状。但是JavaScript永远无法保证,前99个对象保持其形状,也许第100个对象缺少某个属性。正因为如此,编译后的代码在运行之前需要检查其假设是否合理。如果合理,那么优化后的编译代码就会运行,如果不合理,那么JIT就会认为自己做出了错误的假设,并丢弃优化后的代码。此时(在优化代码丢弃的情况下)执行将回退到解释器或基线编译器,这个过程称为去优化。通常优化编译器会使代码更快,但有些情况也会导致一些意想不到的性能问题。如果您的代码不断陷入优化<->反优化循环,程序执行将比基线编译器慢。大多数浏览器都有限制,试图在优化/取消优化循环发生时跳出。例如,如果JIT已经做了10次以上的优化和丢弃操作,那么它就不会继续尝试优化这个代码堆。7.一个优化的例子:类型特化有很多不同类型的优化方法。这里我介绍一个,让大家明白怎么优化。优化编译器最成功的功能之一称为类型专门化,下面将对此进行详细说明。JavaScript使用的动态类型系统需要在运行时进行额外的解释,例如下面的代码:functionarraySum(arr){varsum=0;for(vari=0;i
