一、快速了解
1.问题 实现同样功能的Java代码与AviatorScript代码,两者均会将代码编译成JVM可以执行的字节码,但是两者执行代码所消耗的时间,却有几十倍甚至上百倍的差距。在AS(AviatorScript简称)中,实现相同的功能,更换一种写法,其执行耗时可能也存在极大的差距。 2.原因 ? AS将表达式编译生成的字节码与Java代码编译生成的字节码有较大差异,AS生成的字节码在执行时存在大量的类型转换、条件判断以及为了统一输入输出所做的额外操作,这些都是AS生成的字节码执行速度慢的原因,并且一个表达式并非只生成一个字节码,例如一个单纯的 if-else 逻辑的表达式,AS会生成四份字节码,而执行时使用的是最后一份,其会引用前三份字节码。此处为其性能比不上Java生成的字节码的核心原因。 ? AS编译表达式生成的字节码其继承了 ClassExpression 类并重写了 public abstract Object execute0(Env env)方法,AS最终是通过执行 execute0(env) 方法获取表达式的结果,但AS提供给用户获取表达式执行结果的方法是 execute() ,对 execute0() 方法做了包装,其主要作用是:将用户传入的HashMap转换成Env并对Env内部的一些参数进行配置;添加了执行 execute0() 方法时的异常捕获等方法。此处虽然进行了前期执行的准备工作,但并非其性能问题的核心点; ? 经过测试:1千万次执行,执行execute()方法会比执行execute0()方法多耗时150ms;
二、AS字节码反编译结果
获取AS动态生成的字节码方式:AS获取动态生成的字节码
1.字节码反编译结果举例
1.1 if-else获取两个数的最大值: 表达式:
if (num1 > num2) {
return num1;
} else {
return num2;
}
动态生成字节码反编译结果:
public class Script_1633877984542_59 extends ClassExpression {
private final AviatorJavaType f0;
private final AviatorFunction f1;
public Script_1633877984542_59(AviatorEvaluatorInstance var1, List var2, SymbolTable var3) {
super(var1, var2, var3);
this.f0 = new AviatorJavaType("num1", var3);
this.f1 = var1.getFunction("__reducer_return", var3);
}
public final Object execute0(Env var1) {
return RuntimeUtils.assertNotNull(this.f1.call(var1, this.f0)).deref(var1);
}
}
public class Script_1633878073270_60 extends ClassExpression {
private final AviatorJavaType f0;
private final AviatorFunction f1;
public Script_1633878073270_60(AviatorEvaluatorInstance var1, List var2, SymbolTable var3) {
super(var1, var2, var3);
this.f0 = new AviatorJavaType("num2", var3);
this.f1 = var1.getFunction("__reducer_return", var3);
}
public final Object execute0(Env var1) {
return RuntimeUtils.assertNotNull(this.f1.call(var1, this.f0)).deref(var1);
}
}
public class Script_1633878131216_61 extends ClassExpression {
private final AviatorJavaType f0;
public Script_1633878131216_61(AviatorEvaluatorInstance var1, List var2, SymbolTable var3) {
super(var1, var2, var3);
this.f0 = new AviatorJavaType("__reducer_empty", var3);
}
public final Object execute0(Env var1) {
return this.f0.deref(var1);
}
}
public class Script_1633877984542_58 extends ClassExpression {
private final AviatorJavaType f0;
private final AviatorJavaType f1;
private final AviatorFunction f2;
public Script_1633877984542_58(AviatorEvaluatorInstance var1, List var2, SymbolTable var3) {
super(var1, var2, var3);
this.f0 = new AviatorJavaType("num1", var3);
this.f1 = new AviatorJavaType("num2", var3);
this.f2 = var1.getFunction("__if_callcc", var3);
}
public final Object execute0(Env var1) {
return RuntimeUtils.assertNotNull(this.f2.call(var1, (this.f0.compare(this.f1, var1) > 0 ? AviatorBoolean.TRUE : AviatorBoolean.FALSE).booleanValue(var1) ? RuntimeUtils.assertNotNull(RuntimeUtils.getFunction(this.newLambda(var1, "Lambda_1633877984542_57"), var1).call(var1)) : RuntimeUtils.assertNotNull(RuntimeUtils.getFunction(this.newLambda(var1, "Lambda_1633878073270_58"), var1).call(var1)), this.newLambda(var1, "Lambda_1633878131216_59"))).getValue(var1);
}
}
1.2 Math函数获取两个数的最大值 表达式:
return Math.max(num1, num2);
动态生成字节码反编译结果:
public class subClassExpression extends ClassExpression {
private final AviatorJavaType f0;
private final AviatorJavaType f1;
private final AviatorFunction f2;
public subClassExpression(AviatorEvaluatorInstance var1, List var2, SymbolTable var3) {
super(var1, var2, var3);
this.f0 = new AviatorJavaType("num1", var3);
this.f1 = new AviatorJavaType("num2", var3);
this.f2 = var1.getFunction("Math.max", var3);
}
public final Object execute0(Env var1) {
return RuntimeUtils.assertNotNull(this.f2.call(var1, this.f0, this.f1)).getValue(var1);
}
}
1.3 三元运算获取两个数最大值 表达式:
return num1 > num2 ? num1 : num2;
动态生成字节码反编译结果:
public class subClassExpression extends ClassExpression {
private final AviatorJavaType f0;
private final AviatorJavaType f1;
public subClassExpression(AviatorEvaluatorInstance var1, List var2, SymbolTable var3) {
super(var1, var2, var3);
this.f0 = new AviatorJavaType("num1", var3);
this.f1 = new AviatorJavaType("num2", var3);
}
public final Object execute0(Env env) {
return ((this.f0.compare(this.f1, env) > 0 ? AviatorBoolean.TRUE : AviatorBoolean.FALSE).booleanValue(env) ? this.f0 : this.f1).getValue(env);
}
}
2. 分析
根据上述例子可以知道,求两个数的最大值,不同的写法,经过AS转换后生成的字节码反编译后的结果也完全不一样,而不一样的字节码其执行耗时也不一样,在不修改源码的基础上,想要AS执行速度快,就得在表达式的编写上下功夫,让其生成最优的字节码。 因为AS编译生成的字节码的变量类型是Aviator自己定义的,里面封装了许多方法,方法参数类型几乎都是AviatorObject类型,相当于Java的Object类,而AviatorObject类一般不参与计算,最终需要转换成基本类型进行计算,所以每次调用方法进行运算,基本上都伴随着类型校验、类型转换,直到AS认为该类型符合自己的要求后,才开始进行计算。 类型转换大致流程: AviatorJavaType (初始类型)-> AviatorObject (经过方法变为父类类型)-> AviatorJavaType (方法内转换成具体实现类)-> Object (通过env获取的类型)-> AviatorNumber (判断Object类型并转换)-> AviatorObject (调用方法变成父类)-> AviatorNumber (判断为这个类型后强转)->判断其具体为 AviatorType.Long 、 AviatorType.Double 等,获取其对应类型的值,进行计算。为了对两个数字类型的值进行比较大小,其进行了最少7次以上的类型校验以及转换,这些都是因为AviatorScript身为一个弱类型的语言,参数的具体类型只有在计算的时候从env中获取并判断后才知道具体类型,同时为了保证方法的通用型,其可以接收一个通用类型,然后方法内进行判断具体类型应该走什么逻辑分支,而这也导致类型转换变成一个耗费性能点。
|