Examples
Please keep in mind that ActiveJ Specializer is an experimental project, use it with caution. It does not support lambda expressions and may have difficulty specializing non-trivial instances.
Simple Calculator
We took this Parsec calculator tutorial and adapted it slightly for ActiveJ Specializer. In the original tutorial, Parsec returns parsed expressions as double values:
Parser<Double> parser = new OperatorTable<Double>()
.infixl(op("+", (l, r) -> l + r), 10)
.infixl(op("-", (l, r) -> l - r), 10)
.infixl(Parsers.or(term("*"), WHITESPACE_MUL).retn((l, r) -> l * r), 20)
.infixl(op("/", (l, r) -> l / r), 20)
.prefix(op("-", v -> -v), 30)
.build(unit);
ActiveJ Specializer shows its bests qualities when dealing with tree-like data structures. That is why we will be parsing expressions to AST:
private static final Parser<CalculatorExpression> EXPRESSION = new OperatorTable<CalculatorExpression>()
.infixl(DELIMITERS.token("+").retn(Sum::new), 10)
.infixl(DELIMITERS.token("-").retn(Sub::new), 10)
.infixl(DELIMITERS.token("*").retn(Mul::new), 20)
.infixl(DELIMITERS.token("/").retn(Div::new), 20)
.infixl(DELIMITERS.token("%").retn(Mod::new), 20)
.prefix(DELIMITERS.token("-").retn(Neg::new), 30)
.infixr(DELIMITERS.token("^").retn(Pow::new), 40)
.build(ATOM);
Assume we have a simple equation 3 + 2 * 4
. According to the parser, the following AST will be created:
Let's test Specializer out:
public static void main(String[] args) {
double x = -1;
// manual code, superfast
System.out.println(((2.0 + 2.0 * 2.0) * -x) + 5.0 + 1024.0 / (100.0 + 58.0) * 50.0 * 37.0 - 100.0 + 2.0 * (Math.pow(x, 2.0)) % 3.0);
CalculatorExpression expression = PARSER.parse("((2 + 2 * 2) * -x) + 5 + 1024 / (100 + 58) * 50 * 37 - 100 + 2 * x ^ 2 % 3");
System.out.println(expression);
// tree-walking evaluation, super slow
System.out.println(expression.evaluate(x));
// specialized instance evaluation, about as fast as manual code
CalculatorExpression specialized = SPECIALIZER.specialize(expression);
System.out.println(specialized.evaluate(x));
}
ActiveJ Specializer transforms the AST into a set of static final classes with baked-in values of the given equation. JIT heavily optimizes and inlines these classes at runtime. The result is an optimized instance of an expression that can be reused in case we compute an equation with an unknown value several times.
It's time for some benchmarks. Let's try to process an equation
((2 + 2 * 2) * -x) + 5 + 1024 / (100 + 58) * 50 * 37 - 100 + 2 * x ^ 2 % 3
in three different ways and compare the performance:
- manually enter the equation
- parse the equation to an AST and evaluate it without specialization
- parse the equation to an AST and evaluate it with specialization
The results of the benchmark are very illustrative:
Benchmark Mode Cnt Score Error Units
CalculatorBenchmark.ast avgt 10 828.924 ± 8.369 ns/op
CalculatorBenchmark.manual avgt 10 115.985 ± 1.009 ns/op
CalculatorBenchmark.specialized avgt 10 117.635 ± 1.500 ns/op
As you can see, a manually typed equations and specialized AST were processed equally fast. ActiveJ Specializer sped up AST processing 8 times.