Java高级——Graal编译器介绍

Graal编译器介绍

Graal编译器属于后端编译器,由Java实现,支持提前编译和即时编译

JDK9推出Java虚拟机编译器接口(Java-Level JVM CompilerInterface,JVMCI),将Graal从HotSpot代码中独立出来(即可外部挂载)

构建编译调试环境

这里大坑,试了很多个版本都报错,文件放置在自己的Home下,不要放在奇奇怪怪的目录,保持同一个用户,否则可能会有权限问题!!!

下载构建工具mx

git clone https://github.com/graalvm/mx.git
git checkout 5.247.4

下载Graal编译器代码

git clone https://github.com/oracle/graal.git
git checkout release/graal-vm/19.3

使用带JVMCI的JDK 1.8,可在如下网站下载

https://github.com/graalvm/openjdk8-jvmci-builder/releases

Python版本如下

在这里插入图片描述

配置编译环境,将mx添加到环境变量,指定编译使用的JDK

export PATH=$PATH:/home/song/mx
export JAVA_HOME=/home/song/Downloads/jdk1.8

编译,无报错即编译成功

cd graal/compiler
mx build

编译完成后构建为Eclipse项目

cd graal/compiler
mx eclipseinit

下载Eclipse安装,修改eclipse.ini设置最大堆为2G

在这里插入图片描述

启动Eclipse,将编译graal的JDK设置为默认,Window-Java-installed JREs,删除原来的,点击add-Standard VM-Diectory选择存放目录会自动识别

在这里插入图片描述

在installed JREs下面的Execution Enviroments选择对应JDK并打勾

在这里插入图片描述

在Java-Compiler选择对应JDK

在这里插入图片描述

File-Import-General-Existing Projects into Workspace选择graal根目录导入项目

在这里插入图片描述

项目有报错但不影响下面步骤

JVMCI编译器接口

JVMCICompiler接口如下,除了接收字节码之外,还有方法的局部变量槽个数、操作数栈深度等信息

interface JVMCICompiler {
	void compileMethod(CompilationRequest request);
}

interface CompilationRequest {
	JavaMethod getMethod();
}

interface JavaMethod {
	byte[] getCode();
	int getMaxLocals();
	int getMaxStackSize();
	ProfilingInfo getProfilingInfo();
	... // 省略其他方法
}

通过继承关系,可看到HotSpotGraalCompiler实现了JVMCI

在这里插入图片描述

对于如下程序

public class Demo {

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

先使用分层编译,添加虚拟机参数

-XX:+PrintCompilation -XX:CompileOnly=Demo::workload

可看到wordload()方法被分层编译了多次,“made not entrant”的输出就表示了方法的某个已编译版本被丢弃过

在这里插入图片描述

若只使用graal编译器运行,添加如下参数

-Djvmci.class.path.append=/home/song/graal/compiler/mxbuild/dists/jdk1.8/graal.jar:/home/song/graal/sdk/mxbuild/dists/jdk1.8/graal-sdk.jar
-XX:+UnlockExperimentalVMOptions
-XX:+EnableJVMCI
-XX:+UseJVMCICompiler
-XX:-TieredCompilation
-XX:+PrintCompilation
-XX:CompileOnly=Demo::workload

为了验证结果,修改HotSpotGraalCompiler类的compileMethod()方法,输出编译的方法名称和编译耗时

public CompilationRequestResult compileMethod(CompilationRequest request) {
	long time = System.currentTimeMillis();
	CompilationRequestResult result = compileMethod(request, true, graalRuntime.getOptions());
	System.out.println("compile method:" + request.getMethod().getName());
	System.out.println("time used:" + (System.currentTimeMillis() - time));
	return result;
}

运行代码,可看到对应输出

在这里插入图片描述

代码中间表示

Graal采用理想图作为中间表示,其是有向图,节点表示程序的元素,边表示数据或控制流,对如下程序

public class Demo {

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

添加参数-Dgraal.Dump输出理想图

-Djvmci.class.path.append=/home/song/graal/compiler/mxbuild/dists/jdk1.8/graal.jar:/home/song/graal/sdk/mxbuild/dists/jdk1.8/graal-sdk.jar
-XX:+UnlockExperimentalVMOptions
-XX:+EnableJVMCI
-XX:+UseJVMCICompiler
-XX:-TieredCompilation
-XX:+PrintCompilation
-XX:CompileOnly=Demo::workload
-Dgraal.Dump

可看到类似输出

在这里插入图片描述

在如下地址下载Ideal Graph Visualizer

https://www.oracle.com/technetwork/graalvm/downloads/index.html

修改idealgraphvisualizer/etc/idealgraphvisualizer.conf配置JDK

在这里插入图片描述

通过./idealgraphvisualizer/bin/idealgraphvisualizer打开软件,打开上面生成的文件

在这里插入图片描述
如上,参数的加法操作只进行了一次,然后流出两条数据流到乘法操作的输入中,说明产生了公共子表达式消除,而对于如下程序

public class Demo {
    private static int A;
    private static int B;

    public static void main(String[] args) {
        while (true) {
            workload();
        }
    }

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

    public static int getA() {
        return A;
    }

    public static int getB() {
        return B;
    }

}

getA()和getB()方法内部逻辑不确定的,只能在内联之后才能考虑进一步的优化措施,函数调用时无法实现公共子表达式消除,如下进行了4次调用方法、2次加法、1次乘法

在这里插入图片描述

代码优化

生成理想图的函数为greateGraph(),其数据结构为ValueNode子类节点集合,转换到理想图过程被封装在BytecodeParser类,

以BytecodeParser::genArithmeticOp()中的iadd操作为例,先出栈2个操作数,通过genIntegerAdd()相加再入栈

在这里插入图片描述

过程由AddNode节点表示,代码如下

protected ValueNode genIntegerAdd(ValueNode x, ValueNode y) {
	return AddNode.create(x, y, NodeView.DEFAULT);
}

如下,AddNode在创建节点时调用canonical(),在其中优化缩减理想图(即代码优化),如进行了

  • 算术聚合(如将(a+1)+2聚合为a+3)
  • 符号合并(如将(a-b)+b合并为a)等操作
public static ValueNode create(ValueNode x, ValueNode y, NodeView view) {
	BinaryOp<Add> op = ArithmeticOpTable.forStamp(x.stamp(view)).getAdd();
	Stamp stamp = op.foldStamp(x.stamp(view), y.stamp(view));
	ConstantNode tryConstantFold = tryConstantFold(op, x, y, stamp, view);
	if (tryConstantFold != null) {
		return tryConstantFold;
	}
	if (x.isConstant() && !y.isConstant()) {
		return canonical(null, op, y, x, view);
	} else {
		return canonical(null, op, x, y, view);
	}
}

而公众子表达式消除实现在tryGlobalValueNumbering()方法中,若发现了可以进行消除的算术子表达式,则找出重复的节点替换、删除,如下

public boolean tryGlobalValueNumbering(Node node, NodeClass<?> nodeClass) {
	if (nodeClass.valueNumberable()) {
		Node newNode = node.graph().findDuplicate(node);
		if (newNode != null) {
			assert !(node instanceof FixedNode || newNode instanceof FixedNode);
			node.replaceAtUsagesAndDelete(newNode);
			COUNTER_GLOBAL_VALUE_NUMBERING_HITS.increment(debug);
			debug.log("GVN applied and new node is %1s", newNode);
			return true;
		}
	}
	return false;
}

机器码生成

先生成低级中间表示(LIR,与具体机器指令集相关的中间表示),再由HotSpot产生机器码,对如下程序

public class Demo {

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

添加虚拟机参数

-Djvmci.class.path.append=/home/song/graal/compiler/mxbuild/dists/jdk1.8/graal.jar:/home/song/graal/sdk/mxbuild/dists/jdk1.8/graal-sdk.jar
-XX:+UnlockExperimentalVMOptions
-XX:+EnableJVMCI
-XX:+UseJVMCICompiler
-XX:-TieredCompilation
-XX:+PrintAssembly
-XX:CompileOnly=Demo::workload
-XX:+DebugNonSafepoints

从下面地址下载hsdis-amd64.so放到$JAVA_HOME/lib/amd64

https://github.com/cmuramoto/hsdis

编译生成汇编如下

在这里插入图片描述

加法操作在AddNode::generate()中调用ArithmeticLIRGeneratorTool的emitAdd()方法生成机器码,将其改为emitSub

在这里插入图片描述

可看到对应汇编变成了sub

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值