在我的工作中,我们有一个用于指定数学公式的 DSL,我们后来将其应用于很多点(以百万计)。
截至今天,我们构建了公式的 AST,并访问每个节点以生成我们所谓的“评估器”。然后我们将公式的参数传递给该评估器,并针对每个点进行计算。
例如,我们有这个公式:x * (3 + y)
┌────┐
┌─────┤mult├─────┐
│ └────┘ │
│ │
┌──v──┐ ┌──v──┐
│ x │ ┌───┤ add ├──┐
└─────┘ │ └─────┘ │
│ │
┌──v──┐ ┌──v──┐
│ 3 │ │ y │
└─────┘ └─────┘
我们的评估器将为每个步骤发出“评估”对象。
这种方法易于编程,但效率不高。
所以我开始研究方法句柄来建立一个“组合”的方法句柄来加快速度。
沿着这个:我有我的“算术”课:
public class Arithmetics {
public static double add(double a, double b){
return a+b;
}
public static double mult(double a, double b){
return a*b;
}
}
在构建我的 AST 时,我使用 MethodHandles.lookup() 直接获取这些句柄并组合它们。沿着这些思路,但在一棵树上:
Method add = ArithmeticOperator.class.getDeclaredMethod("add", double.class, double.class);
Method mult = ArithmeticOperator.class.getDeclaredMethod("mult", double.class, double.class);
MethodHandle mh_add = lookup.unreflect(add);
MethodHandle mh_mult = lookup.unreflect(mult);
MethodHandle mh_add_3 = MethodHandles.insertArguments(mh_add, 3, plus_arg);
MethodHandle formula = MethodHandles.collectArguments(mh_mult, 1, mh_add_3); // formula is f(x,y) = x * (3 + y)
可悲的是,我对结果感到非常失望。例如,方法句柄的实际构造非常长(由于调用 MethodHandles::insertArguments 和其他此类组合函数),并且为评估增加的加速仅在超过 600k 次迭代后才开始产生影响。
在 1000 万次迭代时,方法句柄开始真正发光,但数百万次迭代还不是(还?)一个典型的用例。我们更多的是在 10k-1M 左右,结果好坏参半。
此外,实际计算速度加快了,但速度不快(约 2-10 倍)。我期待这东西跑得快一点..
所以无论如何,我再次开始搜索 StackOverflow,并看到了像这样的 LambdaMetafactory 线程:https ://stackoverflow.com/a/19563000/389405
我很想开始尝试这个。但在此之前,我希望您能就一些问题提供意见:
我需要能够编写所有这些 lambda。MethodHandles 提供了很多(不可否认的缓慢)方法来做到这一点,但我觉得 lambdas 有一个更严格的“接口”,我还不能解决如何做到这一点。你知不知道怎么?
lambdas 和方法句柄是相互关联的,我不确定我是否会获得显着的加速。我看到了简单 lambda 的这些结果:
direct: 0,02s, lambda: 0,02s, mh: 0,35s, reflection: 0,40
但是组合 lambda 呢?
多谢你们!