[翻译] Graal的方式-Java JVM JVM编译器

哈Ha! 我向您提供了文章“ 理解Graal的工作原理-用Java编写的Java JIT编译器 ”的翻译。


引言


我之所以成为编程语言研究人员的原因之一是,在计算机技术领域的一大批人中,几乎每个人都在使用编程语言,而且许多人都对它们的工作方式感兴趣。 当我小时候第一次接触编程并熟悉编程语言时,我想了解的第一件事是它的工作方式,而我想做的第一件事就是创建自己的语言。


在本演示中,我将向您展示大家都使用的语言-Java的一些工作机制。 独特之处在于,我将使用一个名为Graal的项目,该项目在Java中实现Java的概念。


Graal只是Java工作中的组件之一-它是即时编译器。 这是JVM的一部分,它在程序执行期间将Java字节码转换为机器代码,并且是确保较高平台性能的因素之一。 在我看来,这也是大多数人认为是JVM中最复杂和最模糊的部分之一,这超出了他们的理解。 改变这种观点是本演讲的目的。


如果您知道JVM是什么; 通常了解术语字节码机器码的含义; 并且能够读取用Java编写的代码,那么,我希望这足以理解所介绍的材料。


我将首先讨论为什么我们可能想要一个用Java编写的JVM的新JIT编译器,然后我将通过将任务分解为编译器的组装,使用和演示,说明您可能会想到的,这没有什么特别的地方其代码与任何其他应用程序中的代码相同。


我将对理论进行大量介绍,然后说明如何在从字节码到机器码的整个编译过程中应用该理论。 我还将展示一些细节,最后,我们将讨论此功能的好处,以及为自己的目的在Java中实现Java


我将在Eclipse中使用代码的屏幕快照,而不是在演示过程中启动它们,以避免不可避免的实时编码问题。


什么是JIT编译器?


我敢肯定,你们中的许多人都知道JIT编译器是什么,但是我仍然会讲一些基础知识,以确保没有人坐在这里害怕问这个主要问题。


在IDE中运行javac保存时编译命令时,Java程序将从Java代码编译为JVM字节码,这是程序的二进制表示形式。 它比Java源代码更紧凑,更简单。 但是,笔记本电脑或服务器的常规处理器不能仅执行JVM字节码。


为了程序的运行,JVM会解释该字节码。 解释器通常比处理器上运行的机器代码慢得多。 因此,JVM在程序运行时可以启动另一个编译器,该编译器将您的字节码转换为机器码,处理器已经可以执行该机器码。


该编译器通常比javac复杂得多,它执行复杂的优化以生成高质量的机器代码。


为什么要用Java编写JIT编译器?


迄今为止,称为OpenJDK的JVM实现包括两个主要的JIT编译器。 客户端编译器称为C1 ,旨在提高操作速度,但同时产生的优化代码较少。 服务器编译器称为optoC2 ,需要更多的时间来工作,但会生成更多的优化代码。


这个想法是,客户端编译器更适合于桌面应用程序,因为在桌面应用程序中JIT编译器不宜长时间停顿,而对于长时间播放的服务器应用程序则使用服务器编译器,这可能会花费更多时间进行编译。


如今,可以将它们组合起来,以便首先由C1编译代码,然后,如果继续继续执行该代码,并且有必要花费额外的时间-C2,则可以这样做。 这称为分层编译


让我们关注执行更多优化的服务器编译器C2。


我们可以从GitHub上的镜像克隆OpenJDK,或仅在站点上打开项目树。


 $ git clone https://github.com/dmlloyd/openjdk.git 

C2代码位于openjdk / hotspot / src / share / vm / opto中


源代码C2


首先,值得注意的是C2是用C ++编写的。 当然,这还不错,但是有某些缺点。 C ++是一种不安全的语言。 这意味着C ++中的错误可能会使VM崩溃。 造成这种情况的原因可能是代码的年代久远,但是C2 C ++代码却变得很难维护和开发。


C2编译器背后的关键人物之一,Cliff Click说他永远不会再用C ++编写VM,而且我们听到Twitter JVM团队说C2停滞不前,出于某种原因需要替换进一步发展的困难。


悬崖点击演示


悬崖点击不想再做什么


https://www.youtube.com/watch?v=Hqw57GJSrac


那么,回到问题所在,Java中的什么可以帮助解决这些问题? 使用Java而不是C ++编写程序的过程与此相同。 这可能是安全性(异常而不是崩溃,没有实际内存泄漏或指针悬垂),好帮手 (调试器,探查器和诸如VisualVM之类的工具),良好的IDE支持等。


您可能会想: 如何编写类似Java JIT的编译器? ,并且只有在诸如C ++的低级系统编程语言中才有可能。 在本演讲中,我希望说服您这根本不是! 本质上,JIT编译器应该只接受JVM字节码并给出机器码-您在输入端给它byte[] ,并且还想取回byte[] 。 为此,需要进行许多复杂的工作,但是它不会影响系统级别,因此不需要诸如C或C ++之类的系统语言。


骨灰级设置


我们需要的第一件事是Java9。所使用的Graal接口(称为JVMCI)已作为JEP 243 Java级JVM编译器接口的一部分添加到Java,而包含它的第一个版本是Java9 。我使用9 + 181 。 如果有特殊要求,则有Java 8的端口(反向端口)。


 $ export JAVA_HOME=`pwd`/jdk9 $ export PATH=$JAVA_HOME/bin:$PATH $ java -version java version "9" Java(TM) SE Runtime Environment (build 9+181) Java HotSpot(TM) 64-Bit Server VM (build 9+181, mixed mode) 

我们需要的下一步是一个名为mx的构建系统。 它有点像MavenGradle ,但是很可能您不会为您的应用选择它。 它实现了某些功能来支持某些复杂的用例,但是我们仅将其用于简单的程序集。


您可以使用GitHub克隆mx 。 我正在使用提交#7353064 。 现在,只需将可执行文件添加到路径即可。


 $ git clone https://github.com/graalvm/mx.git $ cd mx; git checkout 7353064 $ export PATH=`pwd`/mx:$PATH 

现在我们需要克隆Graal本身。 我正在使用一个名为GraalVM版本0.28.2的发行版。


 $ git clone https://github.com/graalvm/graal.git --branch vm-enterprise-0.28.2 

该存储库包含我们不感兴趣的其他项目,因此我们转到编译子项目(即Graal JIT编译器),然后使用mx对其进行编译。


 $ cd graal/compiler $ mx build 

要使用Graal代码,我将使用Eclipse IDE 。 我正在使用Eclipse 4.7.1。 mx可以为我们生成Eclipse项目文件。


 $ mx eclipseinit 

要将graal目录作为工作区打开您需要执行File,Import ...,General,Existing项目,然后再次选择graal目录。 如果您未在Java 9上运行Eclipse,则可能还需要附加JDK源代码。


Eclipse中的Graal


好啊 现在一切就绪,让我们看看它是如何工作的。 我们将使用这个非常简单的代码。


 class Demo { public static void main(String[] args) { while (true) { workload(14, 2); } } private static int workload(int a, int b) { return a + b; } } 

首先,我们编译此javac代码,然后运行JVM。 首先,我将向您展示标准C2 JIT编译器的工作方式。 为此,我们-XX:+PrintCompilation指定几个标志: -XX:+PrintCompilation ,这是JVM在编译方法时写入日志所必需的,而-XX:CompileOnly=Demo::workload ,因此仅此方法可以编译。 如果我们不这样做,那么将显示太多信息,并且JVM将比我们需要的聪明,并且将优化我们想要查看的代码。


 $ javac Demo.java $ java \ -XX:+PrintCompilation \ -XX:CompileOnly=Demo::workload \ Demo ... 113 1 3 Demo::workload (4 bytes) ... 

我将不作详细解释,但我只会说这是一个日志输出,显示workload方法已编译。


现在,作为Java 9 JVM的JIT编译器,我们使用新编译的Graal。 为此,添加更多标志。


--module-path=...--upgrade-module-path=...模块path中添加Graal。 让我提醒您, 模块路径作为Jigsaw模块系统的一部分出现在Java 9中,出于我们的目的,我们可以将其与类路径类比。


我们需要-XX:+UnlockExperimentalVMOptions因为该版本中的JVMCI(Graal使用的接口)是实验性功能。


需要使用标志-XX:+EnableJVMCI来表示我们要使用JVMCI,而标志-XX:+EnableJVMCI要启用和安装新的JIT编译器。


为了不使示例复杂化,并且仅使用JVMCI编译器,而不是将C1与JVMCI结合使用,请指定标志-XX:-TieredCompilation ,这将禁用逐步编译。


和以前一样,我们指定标志-XX:+PrintCompilation-XX:CompileOnly=Demo::workload


如上例所示,我们看到已编译了一个方法。 但是,这次,我们仅使用汇编的Graal进行编译。 现在,请相信我。


 $ java \ --module-path=graal/sdk/mxbuild/modules/org.graalvm.graal_sdk.jar:graal/truffle/mxbuild/modules/com.oracle.truffle.truffle_api.jar \ --upgrade-module-path=graal/compiler/mxbuild/modules/jdk.internal.vm.compiler.jar \ -XX:+UnlockExperimentalVMOptions \ -XX:+EnableJVMCI \ -XX:+UseJVMCICompiler \ -XX:-TieredCompilation \ -XX:+PrintCompilation \ -XX:CompileOnly=Demo::workload \ Demo ... 583 25 Demo::workload (4 bytes) ... 

JVM编译器接口


您难道不认为我们所做的事情很不寻常吗? 我们已经安装了JVM,并且在不更改JVM本身的情况下,用刚刚编译的新JIT编译器替换了JIT编译器。 此功能由称为JVMCI的新JVM接口提供, JVM编译器接口是Java 9中的JEP 243 ,如上所述。


这个想法类似于其他一些现有的JVM技术。


也许您曾经遇到过使用Java注释处理APIjavac进行其他源代码处理的情况。 这种机制使得可以识别注释和使用注释的源代码模型,并基于它们创建新文件。


另外,您可能已使用Java代理在JVM中使用了其他字节码处理。 该机制允许您在启动时通过截获Java字节码来对其进行修改。


JVMCI的想法与此类似。 它允许您连接自己的用Java编写的Java JIT编译器。


现在,我想谈一谈我在演示过程中如何显示代码。 首先,为了理解这个想法,我将在幻灯片上以文本形式显示一些简化的标识符和逻辑,然后我将切换到Eclipse屏幕快照并显示实际代码,这可能会稍微复杂一些,但是主要思想将保持不变。 本演示文稿的主要部分旨在显示实际上可以使用真实的项目代码,因此,尽管它可能有些复杂,但我不想隐藏它。


从现在开始,我开始消除关于JIT编译器非常复杂的观点。


JIT编译器接受什么输入? 它接受要编译的方法的字节码。 顾名思义,字节码只是一个字节数组。


JIT编译器产生什么结果? 它给出了该方法的机器代码。 机器代码也只是字节数组。


结果,编写新的JIT编译器以将其嵌入JVM时必须实现的接口将如下所示。


 interface JVMCICompiler { byte[] compileMethod(byte[] bytecode); } 

因此,如果您无法想象Java可以如何像将JIT编译到机器代码中那样做低级的工作,现在您可以看到这不是一个低级的工作。 对不对 这只是从byte[]byte[]的函数。


实际上,一切都有些复杂。 仅仅字节码是不够的-我们还需要更多信息,例如局部变量的数量,所需的堆栈大小以及解释器探查器收集的信息,以了解实际上的代码执行方式。 因此,想象一下CompilationRequest形式的输入,它将告诉我们需要编译的JavaMethod并提供所有必要的信息。


 interface JVMCICompiler { void compileMethod(CompilationRequest request); } interface CompilationRequest { JavaMethod getMethod(); } interface JavaMethod { byte[] getCode(); int getMaxLocals(); int getMaxStackSize(); ProfilingInfo getProfilingInfo(); ... } 

同样,该接口不需要返回已编译的代码。 而是使用另一个API在JVM中安装机器代码。


 HotSpot.installCode(targetCode); 

现在,要为JVM编写新的JIT编译器,只需实现此接口。 我们获得有关需要编译的方法的信息,我们必须将其编译为机器代码并调用installCode


 class GraalCompiler implements JVMCICompiler { void compileMethod(CompilationRequest request) { HotSpot.installCode(...); } } 

让我们使用Graal切换到Eclipse IDE,并看一些实际的接口和类。 如前所述,它们将稍微复杂一些,但不会太多。


JVMCI编译器


HotSpotGraalCompiler


编译方法


现在,我想表明我们可以对Graal进行更改,并立即在Java 9中使用它们。我将添加一条新的日志消息,该消息将在使用Graal编译方法时显示。 将其添加到已实现的接口方法,该方法由JVMCI调用。


 class HotSpotGraalCompiler implements JVMCICompiler { CompilationRequestResult compileMethod(CompilationRequest request) { System.err.println("Going to compile " + request.getMethod().getName()); ... } } 

合编


现在,在HotSpot中禁用编译日志记录。 现在,我们可以从修改后的编译器版本中看到消息。


 $ java \ --module-path=graal/sdk/mxbuild/modules/org.graalvm.graal_sdk.jar:graal/truffle/mxbuild/modules/com.oracle.truffle.truffle_api.jar \ --upgrade-module-path=graal/compiler/mxbuild/modules/jdk.internal.vm.compiler.jar \ -XX:+UnlockExperimentalVMOptions \ -XX:+EnableJVMCI \ -XX:+UseJVMCICompiler \ -XX:-TieredCompilation \ -XX:CompileOnly=Demo::workload \ Demo Going to compile workload 

如果您尝试自己重复一次,您会发现甚至不需要运行我们的构建系统mx build 。 足够了,对于Eclipse来说是正常的,可以在save上编译 。 当然,我们不需要重建JVM本身。 我们只是将修改后的编译器嵌入到现有的JVM中。


伯爵


好吧,我们知道Graal将一个byte[]转换为另一个byte[] 。 现在让我们谈谈他使用的理论和数据结构,因为 即使对于编译器,它们也有点不寻常。


本质上,编译器处理您的程序。 为此,该程序必须以某种数据结构的形式呈现。 一种选择是字节码和类似的指令列表,但它们的表达能力不是很好。


相反,Graal使用图形来表示您的程序。 如果我们使用一个简单的加法运算符来对两个局部变量求和,则该图将包括一个节点,用于加载每个变量,一个节点用于求和,以及两个边,它们表明加载局部变量的结果进入了加法运算符的输入。


有时称为程序依赖图


通过使用x + y形式的表达式x + y我们获得了局部变量xy的节点以及它们之和的节点。


数据流图


该图中的蓝色边缘表示从读取局部变量到求和的数据流向。


同样,我们可以使用边来反映程序的执行顺序。 如果调用方法而不是读取局部变量,那么我们需要记住调用的顺序,并且我们不能重新排列它们(在不了解内部代码的情况下)。 为此,还有定义此顺序的其他边。 它们以红色显示。


控制流程图


因此,Graal图实际上是将两个图组合在一起。 节点是相同的,但是一些边缘指示数据流的方向,而另一些边缘指示它们之间的控制权转移。


要查看Graal图,可以使用一个名为IdealGraphVisualiserIGV的工具。 使用mx igv执行启动。


IdealGraphVisualiser


之后,使用-Dgraal.Dump标志运行JVM。


通过编写一个简单的表达式可以看到一个简单的数据流。


 int average(int a, int b) { return (a + b) / 2; } 

算术预期igv


您可以看到参数0P(0 )和1P(1) )如何进入加法运算的输入,它与常数2C(2) )一起进入除法运算的输入,然后返回此值。


为了查看更复杂的数据和控制流,我们引入了一个循环。


 int average(int[] values) { int sum = 0; for (int n = 0; n < values.length; n++) { sum += values[n]; } return sum / values.length; } 

带周期的算术平均igv


算术中号详细IGV,带循环


在这种情况下,我们具有循环起点和终点的节点,读取数组的元素,并读取数组的长度。 如前所述,蓝线表示数据流的方向,红线表示控制流。


现在您可以了解为什么有时将这种数据结构称为节点节点之


我想说的是C2使用了非常相似的数据结构,实际上,正是C2普及了节点海的编译器的思想,因此这不是Graal的创新。


在演示的下一部分之前,我不会显示构造该图的过程,但是当Graal以这种格式接收程序时,将通过修改此数据结构来执行优化和编译。 这就是为什么用Java编写JIT编译器有意义的原因之一。 Java是一种面向对象的语言,而图形是通过链接以边连接的对象的集合。


从字节码到机器码


让我们看看这些想法在实践中如何看待,并遵循一些编译过程。


获取字节码


编译从字节码开始。 让我们回到小的求和示例。


 int workload(int a, int b) { return a + b; } 

我们将在编译开始之前输出在输入处接收到的字节码。


 class HotSpotGraalCompiler implements JVMCICompiler { CompilationRequestResult compileMethod(CompilationRequest request) { System.err.println(request.getMethod().getName() + " bytecode: " + Arrays.toString(request.getMethod().getCode())); ... } } 

 workload bytecode: [26, 27, 96, -84] 

如您所见,编译器的输入是字节码。


字节码解析器和图形生成器


构建器将这个字节数组视为JVM字节码,然后将其转换为Graal图。 这是一种抽象解释 -生成器解释Java字节码,但不传递值,而是操纵边缘的自由端并将它们彼此逐渐连接。


让我们利用Graal用Java编写的事实,看看它如何使用Eclipse导航工具工作。 我们知道在我们的示例中有一个添加节点,因此让我们找到它的创建位置。


附议


搜索调用AddNode.create


解析器


可以看出,它们是由字节码解析器创建的,这使我们找到了IADD处理IADD96 ,在打印的输入数组中可以看到)。


 private void genArithmeticOp(JavaKind kind, int opcode) { ValueNode y = frameState.pop(kind); ValueNode x = frameState.pop(kind); ValueNode v; switch (opcode) { ... case LADD: v = genIntegerAdd(x, y); break; ... } frameState.push(kind, append(v)); } 

我在上面说过,这是一种抽象解释,因为 所有这些都与字节码解释器非常相似。 如果它是真正的JVM解释器,那么它将从堆栈中取出两个值,执行加法,然后将结果放回去。 在这种情况下,我们从堆栈中删除了两个节点,当程序启动时,这两个节点将进行计算,加和(这是求和的结果),一个新的加法节点,并将其放置在堆栈中。


这样就建立了Graal图。


获取机器代码


要将Graal图转换为机器代码,您需要为其所有节点生成字节。 通过调用每个节点的generate方法,分别为每个节点完成此操作。


 void generate(Generator gen) { gen.emitAdd(a, b); } 

我重复一遍,在这里我们以很高的抽象水平进行工作。 我们有一类可以发布机器代码指令的类,而无需进一步介绍其工作原理。


emitAdd , , , , . .


 int workload(int a) { return a + 1; } 

, .


 void incl(Register dst) { int encode = prefixAndEncode(dst.encoding); emitByte(0xFF); emitByte(0xC0 | encode); } void emitByte(int b) { data.put((byte) (b & 0xFF)); } 

汇编程序中的汇编说明


字节输出


, , ByteBuffer — .



— .


 class HotSpotGraalCompiler implements JVMCICompiler { CompilationResult compileHelper(...) { ... System.err.println(method.getName() + " machine code: " + Arrays.toString(result.getTargetCode())); ... } } 

印刷机代码


. HotSpot. . OpenJDK, , -, JVM, .


 $ cd openjdk/hotspot/src/share/tools/hsdis $ curl -O http://ftp.heanet.ie/mirrors/gnu/binutils/binutils-2.24.tar.gz $ tar -xzf binutils-2.24.tar.gz $ make BINUTILS=binutils-2.24 ARCH=amd64 CFLAGS=-Wno-error $ cp build/macosx-amd64/hsdis-amd64.dylib ../../../../../.. 

: -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly .


 $ java \ --module-path=graal/sdk/mxbuild/modules/org.graalvm.graal_sdk.jar:graal/truffle/mxbuild/modules/com.oracle.truffle.truffle_api.jar \ --upgrade-module-path=graal/compiler/mxbuild/modules/jdk.internal.vm.compiler.jar \ -XX:+UnlockExperimentalVMOptions \ -XX:+EnableJVMCI \ -XX:+UseJVMCICompiler \ -XX:-TieredCompilation \ -XX:+PrintCompilation \ -XX:+UnlockDiagnosticVMOptions \ -XX:+PrintAssembly \ -XX:CompileOnly=Demo::workload \ Demo 

.


 workload machine code: [15, 31, 68, 0, 0, 3, -14, -117, -58, -123, 5, ...] ... 0x000000010f71cda0: nopl 0x0(%rax,%rax,1) 0x000000010f71cda5: add %edx,%esi ;\*iadd {reexecute=0 rethrow=0 return_oop=0} ; - Demo::workload@2 (line 10) 0x000000010f71cda7: mov %esi,%eax ;\*ireturn {reexecute=0 rethrow=0 return_oop=0} ; - Demo::workload@3 (line 10) 0x000000010f71cda9: test %eax,-0xcba8da9(%rip) # 0x0000000102b74006 ; {poll_return} 0x000000010f71cdaf: vzeroupper 0x000000010f71cdb2: retq 

. , . generate , .


 class AddNode { void generate(...) { ... gen.emitSub(op1, op2, false) ... // changed from emitAdd } } 

发出AddNode指令


, , , .


 workload mechine code: [15, 31, 68, 0, 0, 43, -14, -117, -58, -123, 5, ...] 0x0000000107f451a0: nopl 0x0(%rax,%rax,1) 0x0000000107f451a5: sub %edx,%esi ;\*iadd {reexecute=0 rethrow=0 return_oop=0} ; - Demo::workload@2 (line 10) 0x0000000107f451a7: mov %esi,%eax ;\*ireturn {reexecute=0 rethrow=0 return_oop=0} ; - Demo::workload@3 (line 10) 0x0000000107f451a9: test %eax,-0x1db81a9(%rip) # 0x000000010618d006 ; {poll_return} 0x0000000107f451af: vzeroupper 0x0000000107f451b2: retq 

, ? Graal ; ; ; . , Graal.


 [26, 27, 96, -84] → [15, 31, 68, 0, 0, 43, -14, -117, -58, -123, 5, ...] 


, , . Graal , .


— . .


 interface Phase { void run(Graph graph); } 

(canonicalisation)


. , , ( constant folding ) .


canonical .


 interface Node { Node canonical(); } 

, , , . — . -(-x) x .


 class NegateNode implements Node { Node canonical() { if (value instanceof NegateNode) { return ((NegateNode) value).getValue(); } else { return this; } } } 

NegateNode中的findSynonym


Graal . , .


Java, canonical .


Global value numbering


Global value numbering (GVN) — . a + b , — .


 int workload(int a, int b) { return (a + b) * (a + b); } 

Graal . — . GVN . hash map , , .


全局值编号


全局值编号图


, — , , - . , , , , — .


 int workload() { return (getA() + getB()) * (getA() + getB()); } 

不能为全局值编号的图


(lock coarsening)


. . , , , ( inlining ).


 void workload() { synchronized (monitor) { counter++; } synchronized (monitor) { counter++; } } 

, , , , .


 void workload() { monitor.enter(); counter++; monitor.exit(); monitor.enter(); counter++; monitor.exit(); } 

. .


 void workload() { monitor.enter(); counter++; counter++; monitor.exit(); } 

Graal LockEliminationPhase . run , . , , .


 void run(StructuredGraph graph) { for (monitorExitNode monitorExitNode : graph.getNodes(MonitorExitNode.class)) { FixedNode next = monitorExitNode.next(); if (next instanceof monitorEnterNode) { AccessmonitorNode monitorEnterNode = (AccessmonitorNode) next; if (monitorEnterNode.object() ## monitorExitNode.object()) { monitorExitNode.remove(); monitorEnterNode.remove(); } } } } 

简化锁


, , , , 2 .


 void workload() { monitor.enter(); counter += 2; monitor.exit(); } 

IGV . , , \ , , , 2 .


简化锁定


之后简化锁



Graal , , , . , , , .


Graal , , , , , , .



Graal , , . ? , ?


, , . . , , , , . , , , , , .


( register allocation ). Graal , JIT-, — ( linear scan algorithm ).



, , , - , .


, , , , (.. ), . , , .


( graph scheduling ). . , . , , , .


, .


Graal?


, , , Graal — , Oracle . , Graal?


(final-tier compiler)


C JVMCI Graal HotSpot — , . ( HotSpot) Graal , .


Twitter Graal , Java 9 . : -XX:+UseJVMCICompiler .


JVMCI , Graal JVM. (deploy) - JVM, Graal. Java-, Graal, JVM.


OpenJDK Metropolis JVM Java. Graal .


大都会项目


http://cr.openjdk.java.net/\~jrose/metropolis/Metropolis-Proposal.html



Graal . Graal JVM, Graal . , Graal . , - , , JNI.


Charles Nutter JRuby Graal Ruby. , - .


AOT (ahead-of-time)


Graal — Java. JVMCI , Graal, , , Graal . , Graal , JIT-.


JIT- AOT- , Graal . AOT Graal.


Java 9 JIT-, . JVM, .


AOT Java 9 Graal, Linux. , , .


. SubstrateVM — AOT-, Java- JVM . , - (statically linked) . JVM , . SubstrateVM Graal. ( just-in-time ) SubstrateVM, , Graal . Graal AOT- .


 $ javac Hello.java $ graalvm-0.28.2/bin/native-image Hello classlist: 966.44 ms (cap): 804.46 ms setup: 1,514.31 ms (typeflow): 2,580.70 ms (objects): 719.04 ms (features): 16.27 ms analysis: 3,422.58 ms universe: 262.09 ms (parse): 528.44 ms (inline): 1,259.94 ms (compile): 6,716.20 ms compile: 8,817.97 ms image: 1,070.29 ms debuginfo: 672.64 ms write: 1,797.45 ms [total]: 17,907.56 ms $ ls -lh hello -rwxr-xr-x 1 chrisseaton staff 6.6M 4 Oct 18:35 hello $ file ./hello ./hellojava: Mach-O 64-bit executable x86_64 $ time ./hello Hello! real 0m0.010s user 0m0.003s sys 0m0.003s 

Truffle


Graal Truffle . Truffle — JVM.


, JVM, , JIT- (, , , JIT- JVM , ). Truffle — , , Truffle, , ( partial evaluation ).


, ( inlining ) ( constant folding ) . Graal , Truffle .


Graal — Truffle. Ruby, TruffleRuby Truffle , , Graal. TruffleRuby — Ruby, 10 , , , .


https://github.com/graalvm/truffleruby


结论


, , , JIT- Java . JIT- , , , - . , , . JIT- , byte[] JVM byte[] .


, Java. , C++.


Java- Graal - . , , .


. , Eclipse . (definitions), .. .


JIT JIT- JVM, JITWatch , , Graal , . , , - , Graal JVM. IDE, hello-world .


SubstrateVM Truffle, Graal, , Java . , Graal Java. , , - LLVM , , , , .


, , Graal JVM. 因为 JVMCI Java 9, Graal , , Java-.


Graal — . , Graal. , !




More information about TruffleRuby


Low Overhead Polling For Ruby


Top 10 Things To Do With GraalVM


Ruby Objects as C Structs and Vice Versa


Understanding How Graal Works — a Java JIT Compiler Written in Java


Flip-Flops — the 1-in-10-million operator


Deoptimizing Ruby


Very High Performance C Extensions For JRuby+Truffle


Optimising Small Data Structures in JRuby+Truffle


Pushing Pixels with JRuby+Truffle


Tracing With Zero Overhead in JRuby+Truffle


How Method Dispatch Works in JRuby+Truffle


A Truffle/Graal High Performance Backend for JRuby

Source: https://habr.com/ru/post/zh-CN419637/


All Articles