2021SC@SDUSC
目录
一、前文承接
二、Specialization方法分析
transformMethod()
使用方式
方法内变量
insn指令描述
其他的opcode
三、结语
四、参考
五、往期回顾
一、前文承接
在第十篇博客中,我们分析了ActiveJ中Specializer类的部分内容,了解了Specializer中如何为外部提供自己的功能——嵌套内部类Specialization。Specializer的几乎全部功能,都是通过其内部的Specialization类来实现的,后者可以为一个动作实例(可以是一个expression,比如ActiveJ官网中给出的一个针对于parsec calculator tutorial的优化例子)来进行JVM优化。
二、Specialization方法分析
上一篇博客中主要是对于Specialization类的构造功能,分析了如何把一个需要被Specializer优化的实例的属性和方法装载到Specialization内部类中去。ActiveJ在把一个实例装载到Specialization中后,还需要做对属性和方法进行处理的操作。
transformMethod()
在最初我们对Specializer的功能的进行描述时,我们提到ActiveJ使用Specializer进行优化的思路是将其转化为static的静态方法从而加速JVM的执行效率,因为静态方法是属于类方法,无需跟随对象的创建而创建,跟随对象的消失而消失。
在编程的角度,考虑到封装性以及编写的要求,在编程方面并不推荐使用过多的静态方法。不仅会导致调用混乱,也会破坏对象之间的独立性。但是ActiveJ考虑到static方法具有优化的性能,所以会将其以静态的方式来执行,从而在运行中来加快程序构建和运行速度。
transformMethod显然就是在编译器的角度来优化Specializer接受的实例的方法。
使用方式
首先是查看transformMethod的使用方式:
transformMethod( classNode.methods.stream() .filter(methodNode -> true && methodNode.name.equals(javaMethod.getName()) && methodNode.desc.equals(methodDesc)) .findFirst() .get(), new GeneratorAdapter(ACC_PUBLIC | ACC_STATIC | ACC_FINAL, new Method(specializedMethodName, methodDesc), null, null, cw));
在上一篇博客分析加入方法的过程中,便使用到了transformMethod方法,可以注意到代码中的filter的使用,这使得对需要被tranformMethod执行的方法进行了筛选。即必须为方法标识符为true、名称必须与MethodFields中读取的Key(这个在上一篇博客中提到过,Key代表一个通过反射机制获得的实例拥有的方法)一致、以及签名必须一致三个条件。然后从实例转化而来的Stream中读取首个元素(推测即为method的具体文件块)。
当得到对应的方法后,再通过GeneratorAdapter来为此方法生成相应的访问修饰符和空间。
方法内变量
在这里我们可以看到transformMethod方法的参数个数是2个,一个是被优化的方法,一个我们很熟悉的方法适配器GeneratorAdapter。
AnalyzerAdapter analyzerAdapter = new AnalyzerAdapter(getType(instanceClass).getInternalName(), ACC_PUBLIC | ACC_FINAL, methodNode.name, methodNode.desc, null); Type[] methodParameters = new Method(methodNode.name, methodNode.desc).getArgumentTypes(); final MaplocalsRemapping = new HashMap<>(); Map > localRemappingsByLabel = new HashMap<>(); AbstractInsnNode insn;
在transformMethod中,创建了一个analyzerAdapter,其是属于org.objectweb.asm.commons包的一个类,作者的描述如下:
A {@link MethodVisitor} that keeps track of stack map frame changes between {@link
* #visitframe(int, int, Object[], int, Object[])} calls. This adapter must be used with the {@link
* org.objectweb.asm.ClassReader#EXPAND_frameS} option. Each visitX instruction delegates(代表) to
* the next visitor in the chain, if any, and then simulates the effect of this instruction on the
* stack map frame, represented by {@link #locals} and {@link #stack}. The next visitor in the chain
* can get the state of the stack map frame before each instruction by reading the value of
* these fields in its visitX methods (this requires a reference to the AnalyzerAdapter that
* is before it in the chain). If this adapter is used with a class that does not contain stack map
* table attributes (i.e., pre Java 6 classes) then this adapter may not be able to compute the
* stack map frame for each instruction. In this case no exception is thrown but the {@link #locals}
* and {@link #stack} fields will be null for these instructions.
简答来讲,analyzerAdapter是一个追踪堆栈操作指令(instruction)的工具,在类中
接下来是创建出一些哈希表和本地映射,其作用我们推迟到之后的具体使用处再来分析。
for (int i = 0; i < methodNode.instructions.size(); i++, insn.accept(analyzerAdapter)) {
insn = methodNode.instructions.get(i);
int opcode = insn.getOpcode();
if (insn instanceof JumpInsnNode) {
JumpInsnNode insnJump = (JumpInsnNode) insn;
localRemappingsByLabel.put(insnJump.label, new HashMap<>(localsRemapping));
}
}
随后进入一个for循环,其中methodNode就是我们传入的、需要被优化的方法,这里取到了
int i = 0; i < methodNode.instructions.size(); i++, insn.accept(analyzerAdapter)
这里需要注意for循环的判定条件,在我们所学的知识中,方法就是代表一个功能的最低尺度,这里却分析到了methodNode.instructions.size()这个变量,这是我所不懂的,字面意思来看,是代表方法包含的指令。继续分析,在for循环的判定条件中的第三部分,不仅是i++,还有一个insn.accept(analyzerAdapter) 擦走操作,这里的insn是之前创建的一个AbstractInsnNode,我们在定义中查看其内容,可以看到其和之前的analyzerAdapter同属于org.objectweb.asm包中。
因此Specializer是把一个Method里的每一个instruction都取了出来,然后对instruction来进行操作的。
insn指令描述
insn的描述是这样的
A node that represents a bytecode instruction. An instruction can appear at most once in at most one InsnList at a time.
作者:
Eric Bruneton
其代表了一个最基本的字节指令操作。并且在一个指令链表中最多出现一次。
回到transformMethod中来,每次for循环,都取出一个当前MethodNode中的一个指令Instrucation,然后返回其opcode(寄存器的操作码,以16进制表示,可以理解为计算机组成与结构中学到的汇编指令,如PUSH、INC等),到这里我们就可以理解ActiveJ是进入到了更深层的内容——机器指令去加速代码执行速度,opcode即是作用在JVM上的保护和加载机制。
insn = methodNode.instructions.get(i);
int opcode = insn.getOpcode();
if (insn instanceof JumpInsnNode) {
JumpInsnNode insnJump = (JumpInsnNode) insn;
localRemappingsByLabel.put(insnJump.label, new HashMap<>(localsRemapping));
}
紧接着,如果insn是一个转移指令(JUMP)的实例(insn是多态的父指针,通过反射来得到实际类型),则强制转为JUMP指令,然后标记。
这里的JumpInsnNode也是和opcode同属于org.objectweb.asm包的内容,其描述如下:
类似的,如果是一个LabelNode,即概括标签指令,则执行同样的操作:
if (insn instanceof LabelNode) {
LabelNode insnLabel = (LabelNode) insn;
methodNode.tryCatchBlocks.stream()
.filter(block -> block.end == insnLabel)
.findFirst()
.ifPresent(block ->
localRemappingsByLabel.put(block.handler, new HashMap<>(localsRemapping)));
}
如果是一个frameNode,也需要进行对应的操作。由于frameNode是引导下一个指令入口的操作,因此frameNode的作用在引导指令这一步就可以结束,因此会被remove出去。
if (insn instanceof frameNode) {
frameNode insnframe = (frameNode) insn;
for (Integer k : new ArrayList<>(localsRemapping.keySet())) {
if (k >= insnframe.local.size()) {
localsRemapping.remove(k);
}
}
}
其他的opcode
对于opcode的处理,ActiveJ使用了switch语句来进行一一对应,不过从代码的大致情况观测其实并没有很多的有效的switch-case语句,而是把大多数对应的case依次顺延到下一个case,也许是为了代码可读性所以不做合并操作。
switch (opcode) {
case ACONST_NULL:
case ICONST_M1:
case ICONST_0:
case ICONST_1:
case ICONST_2:
case ICONST_3:
case ICONST_4:
case ICONST_5:
case LCONST_0:
case LCONST_1:
case FCONST_0:
case FCONST_1:
case FCONST_2:
case DCONST_0:
case DCONST_1:
g.visitInsn(opcode);
break;
...
}
在Specialization中定义了许多的case,根据代码中case的写法可以大致分为以下几类:
(1)CONST常量定义指令,
(2)PUSH指令
(3)LOAD指令
case ALOAD: {
VarInsnNode insnVar = (VarInsnNode) insn;
if (insnVar.var == 0) {
g.getStatic(specializedType, THIS, getType(instanceClass));
break;
}
if (insnVar.var - 1 < methodParameters.length) {
g.loadArg(insnVar.var - 1);
break;
}
g.loadLocal(localsRemapping.get(insnVar.var));
break;
}
以LOAD指令为例,如果参数个数为0的话,则装载静态域的属性,即类属性。
如果参数个数不为0的话,则将参数一一装载完毕。
最后是装载指令,即g.loadlocal操作:
public void loadLocal(final int local) {
loadInsn(getLocalType(local), local);
}
在Specialization的switch语句中,每一些功能上相似或者相关联性比较大的指令,都被放在一起来组织,这样可以提高代码可读性,所以在分析关于switch中操作opcode的部分时,可以很容易的找到需要查找和引申的内容,这也许也是ActiveJ项目构建的优点之一。
(4)STORE指令
与load指令相对的,就是store指令:
case ASTORE: {
VarInsnNode insnVar = (VarInsnNode) insn;
int var = insnVar.var;
if (var - 1 < methodParameters.length) {
g.storeArg(var - 1);
break;
}
if (localsRemapping.containsKey(var)) {
g.storeLocal(localsRemapping.get(var));
} else {
Object top = analyzerAdapter.stack.get(analyzerAdapter.stack.size() - 1);
Type type = null;
if (top == Opcodes.INTEGER) type = Type.INT_TYPE;
if (top == Opcodes.FLOAT) type = Type.FLOAT_TYPE;
if (top == Opcodes.DOUBLE) type = Type.DOUBLE_TYPE;
if (top == Opcodes.LONG) type = Type.LONG_TYPE;
if (top == Opcodes.NULL) type = getType(Object.class);
if (top instanceof String) type = Type.getType(internalizeClassName((String) top));
int newLocal = g.newLocal(type);
localsRemapping.put(var, newLocal);
g.storeLocal(newLocal);
}
break;
}
在代码的中间部分进行了java object中的Integer与Type类型的转换,转换完毕后就可以把对应的数据类型装入到localRemapping,即记载所有instruction的Map中。
(5)DUP指令(JVM堆栈复制指令)
(6)ADD、SUB、MUL、DIV加减乘除指令
(7)OR、XOR、SHR、SHL位运算指令
(8)其他的方法调用指令
这里说明一下,第8种指令范围很大,基本上就是我们在上一篇博客中所提到过的关于方法调用和属性加载的指令等,包括INVOKESTATIC、PUTFIELD指令等。这些指令所执行的操作都和我们之前分析的一样,例如INVOKESTATIC中,即传入方法名称,方法参数,方法访问修饰符等,从而构建出一个能够调用静态方法的实例操作。
对于不同的opcode,Specializer都通过switch语句找到对应的操作,然后去执行与此指令相关的JVM操作。可以分析出,其普通方法转静态方法,就是通过使用底层的机器指令来与JVM功能一起来完成的。
三、结语
本篇博客继续分析Specializer中的Specialization所使用到的功能,继上一篇博客分析了Speciali zation的创建和初始化操作后,这篇集中于分析其如何优化一个传入Specializer的方法的。可以看到,Specializer通过底层的机器指令,opcode层面来进行优化,从而使用JVM的功能来加速代码执行速度。
分析到这里,觉得ActiveJ还是蛮厉害的,虽然我没有学习过编译原理等计算机结构课程,只学习过基本的计算机组成原理,但是每次上课都有老师对汇编侃侃而谈,因此对于编译方面的内容,深感佩服。所以本次分析ActiveJ到这里,已经看到一点关于Specializer如何进行优化的操作,也是对其使用到了深层的汇编层面而感到佩服,也许这就是为什么ActiveJ敢于说成为最快JVM串行化技术之一的底气。
四、参考
OPCode 详解 - Sunshine - 博客园 (cnblogs.com)
java虚拟机指令dup的理解_Gabriel576282253的专栏-CSDN博客_dup指令
五、往期回顾
ActiveJ框架学习(十)——Specializer_m0_56367233的博客-CSDN博客
ActiveJ框架学习(九)——Specializer[承上启下]_m0_56367233的博客-CSDN博客
ActiveJ框架学习(八)——Record类功能解析(II)_m0_56367233的博客-CSDN博客
ActiveJ框架学习(七)——Record类功能解析_m0_56367233的博客-CSDN博客
ActiveJ框架学习(六)——Util其他散工具分析_m0_56367233的博客-CSDN博客
ActiveJ框架学习(五)——ClassBuilder类分析_m0_56367233的博客-CSDN博客
ActiveJ框架学习(四)——Context功能分析_m0_56367233的博客-CSDN博客
ActiveJ框架学习(三)——expressionTest类千行源码分析_m0_56367233的博客-CSDN博客
ActiveJ框架学习(二)——Codegen的初步认识_m0_56367233的博客-CSDN博客
ActiveJ框架学习(一)——起步_m0_56367233的博客-CSDN博客



