背景
希望观察graalvm的编译过程输出,以便后续调试和分析问题。
信息导出
主要的入口资料在
https://www.graalvm.org/graalvm-as-a-platform/language-implementation-framework/Optimizing/
完整的详细选项可以参考
https://www.graalvm.org/graalvm-as-a-platform/language-implementation-framework/Options/
graal提供大量的导出命令,部分信息可以在终端上直接查看,核心的导出文件需要使用专用的可视化工具进行分析。
示例的导出命令,非常完整的信息dump出来
1 | ./js --jvm --engine.TraceCompilationAST --engine.BackgroundCompilation=false --experimental-options --engine.CompileImmediately --vm.Dgraal.Dump --vm.XX:+UnlockDiagnosticVMOptions --vm.XX:+PrintAssembly --vm.Dgraal.PrintBackendCFG=true --vm.XX:TieredStopAtLevel=0 hello.js |
选项补充说明:
选项 | 说明 |
---|---|
–jvm | 打印汇编需要该选项,看起来似乎native image的运行时没有提供打印功能,使用openjdk vm时可以打印 |
–vm.Dgraal.Dump | 可以调整为-vm.Dgraal.Dump=:99,以导出各个优化过程后的结果, 是调高后dump的数据太多,可能导致整个运行过程非常缓慢 |
–vm.XX:+PrintAssembly | 该选项可以将最终的汇编打印到终端,但是需要安装插件hsids。可以直接下载,但是需要改名并放置在正确的目录下,如lib/hsdis-amd64.so |
–vm.Dgraal.PrintGraph=Network | 该选项可以直接将dump数据通过本地网络送到已经在本地启动的visualizer中,可以节省查找dump文件的时间 |
-Dgraal.MethodFilter=class.method | For example, -Dgraal.MethodFilter=java.lang.String.*,HashMap.get will produce diagnostic data only for methods in the java.lang.String class as well as methods named get in a class whose non-qualified name is HashMap. |
图形化分析工具
按照oracle的官方介绍,整个编译过程的输出分成了两个部分:
图形式的HIR(graal-ir)使用Ideal Graph Visualizer 展示。
接近机器码的LIR使用c1Visualizer 展示。
HIR部分还有一个开源的实现seafoam可以使用,其界面更简洁一些,但是功能弱于Ideal Graph Visualizer,比较适合入门学习。
Ideal Graph Visualizer
该工具是oracle官方提供的分析工具,需要到oracle网站上注册后才能下载。其基本介绍可参考 https://docs.oracle.com/en/graalvm/enterprise/21/docs/tools/igv/ 处的帮助。
Visualizer 有几个非常重要的功能,包括 基本块切分、节点名称搜索、源代码位置展示等,可以帮助我们快速定位问题。
参考下图。
图中还有四个较为重要的功能没有展示出来。
一是在graph的条目上点右键,选择’difference to current graph’功能,可以对不同的graph进行差异对比。
二是,在主展示区双击一个node,可以仅展示与这个选中节点相关的节点。通过不断双击相关节点(尤其是沿着红色的控制流走),可以以我们关心的node为中心展开graph,较快理清楚前后逻辑。同时,在选中节点的情况下,还可以切换graph的阶段,可以看到各个阶段变换对我们关心node的影响。
三是,可以自定义filter,使用简单的js语言接口编程,具体可以双击Coloring等过滤条目来查看示例代码。
四是,在单个节点上点右键,可以快速找到与其相连的node,由于图中有些节点相距太远,Visualizer不会把连接线画出来,用这个功能可以很快找到当前节点的前后节点。
c1Visualizer
在真正生成二进制代码前,HIR需要被Lower到LIR,在LIR表达形式下还需要做寄存器分配和调度等重要的工作。
c1Visualizer 是展示这些过程的工具。
通常在该工具中,我们最关心的是汇编代码与HIR间的对应关心。如下图所示。
seafoam
参考https://chrisseaton.com/truffleruby/basic-graal-graphs/
https://github.com/Shopify/seafoam
可以认为这是一个开源简化版本的Ideal Graph Visualizer。
使用工具分析实际问题
问题描述
使用如下的测试用例,测试js+c跨语言优化能力,可以发现同样的代码使用graal中的js运行速度很慢,而graal中的node能做到跨语言优化,使得js+c混合运行速度与纯js运行同样逻辑的速度一致。
1 | $ cat inter_c.c |
1 | $ cat inter.js |
使用如下命令编译出所需的动态库。
1 | cd graalvm-ce-java11-21.0.0.2/bin |
再以如下的命令运行测试,可以看到使用node和js分别启动完全相同的程序,性能差了10倍。
1 | $ time ./js --polyglot inter.js |
使用纯js进行基准测试。
1 | $cat inter_pure.js |
得到如下数据
1 | time ./node inter_pure.js |
这里可以看到graal-node的js+c运行速度与纯js运行的速度已经一样了,语言互相调用的瓶颈被消除。
为了进一步标定性能,使用了如下c代码。
1 | $ cat per.c |
得到的性能数据如下。可以看到联合优化后js+c的性能比纯c无优化还略微更快一些。
1 | $ gcc per.c -o per -O0;time ./per |
工具分析过程
打开Ideal Graph Visualize,使用如下命令分别获取两个导出图。
1 | ./js --jvm --polyglot --engine.TraceCompilationAST --engine.BackgroundCompilation=false --experimental-options --engine.CompileImmediately --vm.Dgraal.Dump=:1 --vm.Dgraal.PrintGraph=Network ./inter.js |
循环在graal导出中会被做成一个独立节点,选中js导出文件中的 Truffle::WhileNode$WhileDoRepeatingNode@7dee8838 inter.js:5~。
按照前面介绍的方法,可以查找到c 中的加法节点,并以此为中心来展开循环,观察js和nodejs的差异。
具体操作后,得到下图所示的两张图。
可以看到nodejs的循环非常短,除开55418这个写入数字3的节点不清楚缘由外(现在已经基本清楚了,写入的数字3是在记录stackslot写入数据对应的数据类型,后面读取时会校验,失败时会deoptimize),其余的动作都很容易映射到循环逻辑上。
而js的循环展开后节点非常多,流程很长,很多节点无法与循环逻辑对应。
剩下的问题是找到js循环中多余节点的来源。
可以选中加法后的控制流节点,查看其详细的nodeSourcePosition属性,对比如下图所示。
对比可以看到js中多出了DualNode的节点,退回到AST 的graph中可以非常明显的看到nodejs的节点没有DualNode这一层而js将循环挂到了DualNode下。进一步分析,需要查看graaljs和graal-nodejs在parser部分的差异,这个不属于工具的范畴这里暂时不再深入。
参考 https://github.com/oracle/graaljs/issues/411 ,nodejs和js 执行模型有差异。主要是graal-node会把全局的代码片段放到一个匿名函数中。为了评估这个差异,使用如下所示的测试代码。程序的语义没有变化的情况下,graaljs的运行速度大幅度提升,但是仍然明显比node更慢。
1 | cat ./inter_in_func.js |
1 | $ time ./js --polyglot ./inter_in_func.js |
再次dump出inter_in_func的graphs,与前面graal-nodejs的对比。在AST层级使用图的diff功能,可以看到两者的差异极小,只有调用console.log前的一个节点不同(可以从差异节点中id最小的一个开始看,差异节点被单独列出来了)。
考虑到vm和编译可能会对较短运行时间的程序产生干扰,为了减小干扰,将循环次数改为60次后,再测试。这次node和js的性能就基本一致了。
1 | $ time ./node --jvm --polyglot ./inter.js |
遗留问题
PrintAssembly 会有一些报错,原因未知。
Ideal Graph Visualizer state的作用未知
Ideal Graph Visualizer 中使用node 不加–jvm导出时,其出现了很多单独的graph,看起来是整个程序(有jvm选项时,graph一般从属于一个函数)。并且图中的节点没有Source信息。原因未知。