At my job we have a DSL for specfying mathematical formulas, that we later apply to a lot of points (in the millions).
As of today, we build an AST of the formula, and visit each node to produce what we call an "Evaluator". We then pass that evaluator the arguments of the formula, and for each point it does the computing.
For instance, we have that formula: x * (3 + y)
┌────┐
┌─────┤mult├─────┐
│ └────┘ │
│ │
┌──v──┐ ┌──v──┐
│ x │ ┌───┤ add ├──┐
└─────┘ │ └─────┘ │
│ │
┌──v──┐ ┌──v──┐
│ 3 │ │ y │
└─────┘ └─────┘
Our evaluator will emit "Evaluate" objects for each step.
This method is easy to program, but not very efficient.
So I started looking into method handles to build up a "composed" method handle to speed things up lately.
Something along this: I have my "Arithmetic" class with :
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;
}
}
And when building my AST I use MethodHandles.lookup() to directly get a handle on those and compose them. Something along these lines, but in a tree:
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)
Sadly, I'm quite disapointed by the results. For instance, the actual construction of the method handle is very long (due to calls to MethodHandles::insertArguments and other such compositions functions), and the added speedup for evaluation only starts to make a difference after over 600k iterations.
At 10M iterations, the Method handle starts to really shine, but millions of iterations is not (yet?) a typical use case. We are more around 10k-1M, where the result is mixed.
Also, the actual computation is sped up, but by not so much (~2-10 times). I was expecting the thing to run a bit faster..
So anyway, I started scouring StackOverflow again, and saw the LambdaMetafactory threads like these: https://stackoverflow.com/a/19563000/389405
And I'm itching to start trying this. But before that, I'd like your input on some questions:
I need to be able to compose all those lambdas. MethodHandles provides lots of (slowish, admitedly) ways to do it, but I feel like lambdas have a stricter "interface", and I can't yet wrap my head on how to do that. Do you know how?
lambdas and method handles are quite interconnected, and I'm not sure that I will get a significant speedup. I see these results for simple lambdas:
direct: 0,02s, lambda: 0,02s, mh: 0,35s, reflection: 0,40
but what about composed lambdas?
Thanks guys!