ActiveSpecializer Examples

Note: Please keep in mind that ActiveSpecializer is an experimental project, use it cautiously. It doesn't support lambda expressions and may have difficulties with specializing non-trivial instances.

Simple Calculator

We took this Parsec calculator tutorial and adapted it a little bit for ActiveSpecializer. 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);

ActiveSpecializer shows its bests with tree-like data structures. So we will parse 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:

graph TD + --> 3 + --> * * --> 2 * --> 4

Let’s test Specializer out:

public static void main(String[] args) {
	double x = -1;

	// manual code, super fast
	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));
}

ActiveSpecializer transforms the AST to a set of static final classes with baked-in values of the provided equation. JIT heavily optimizes and inlines these classes while runtime. As a result, we receive an optimized expression instance that can be reused in case we calculate 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. ActiveSpecializer sped up AST processing 8 times.