Skip to content

Super fast expression evaluator/parser written in Java

License

Notifications You must be signed in to change notification settings

PolyhedralDev/Paralithic

Repository files navigation

Paralithic

Build Status GitHub Tag Chat

Super fast expression evaluator/parser written in Java.

About

Paralithic is a library for parsing and evaluating mathematical expressions. It uses ASM to dynamically generate implementations of the Expression interface representing the expression, with expressions converted directly into Java bytecode for maximum performance, and maximum room for JIT optimization. Paralithic also uses several AOT optimization techniques, such as:

  • Evaluating stateless functions with constant arguments at parse time, replacing them with constants.
  • Evaluating binary operations with constant operands at parse time, replacing them with constants.
  • Combining constants in nested commutative binary operations.

By directly generating bytecode to be executed by the JVM, and with the above optimizations, Paralithic expressions are able to achieve about the same speed as hard-coded ones.

Paralithic uses Seismic as it's backing math library, which provides many of the math functions built into Paralithic, and provides more optimized versions for many functions built into Java's Math class.

Usage

Parser parser = new Parser(); // Create parser instance.
Scope scope = new Scope(); // Create variable scope. This scope can hold both constants and invocation variables scope.

addInvocationVariable("x"); // Register variable to be used in calls to #evaluate. Values are passed in the order they are registered scope.

create("y",3); // Create named constant y with value 3

Expression expression = parser.parse("x * 4 + pow(2, y)", scope); // expression.

evaluate(3); // 20 (3*4 + 2^3 = 20)

Performance

The expression (sin(x) + 2 + ((7-5) * (3.14159 * x^(14-10)) + sin(-3.141) + (0%x)) * x/3 * 3/sqrt(x)) was evaluated in 3 different expression libraries.

The test was run for 3 iterations of 1 second each to allow the JIT to warmup and optimize as much as possible, the next 3 iterations of 1 second each were timed and averaged. This was then repeated 3 times, with a new JVM each time, and the results were averaged.

The native, native (simplified), native (optimized) tests each tested a hard-coded method containing the expanded, simplified, and optimized expression, respectively.

Benchmark Score
exp4J 332.326 ± 15.629 ns/op
parsii 97.120 ± 3.215 ns/op
native 33.003 ± 0.699 ns/op
native (simplified) 23.084 ± 0.299 ns/op
Paralithic 5.541 ± 0.166 ns/op
native (optimized) 5.306 ± 0.114 ns/op

Results are from tests run on an Intel i7-1165G7.

Full Benchmark Results
Benchmark                                    (input)                                                                        (testExpression)  Mode  Cnt    Score    Error  Units
PerformanceTest.exp4JPerformance                   1  (sin(x) + 2 + ((7-5) * (3.14159 * x^(14-10)) + sin(-3.141) + (0%x)) * x/3 * 3/sqrt(x))  avgt    9  336.341 ± 46.461  ns/op
PerformanceTest.exp4JPerformance                1000  (sin(x) + 2 + ((7-5) * (3.14159 * x^(14-10)) + sin(-3.141) + (0%x)) * x/3 * 3/sqrt(x))  avgt    9  321.425 ±  9.348  ns/op
PerformanceTest.exp4JPerformance               23422  (sin(x) + 2 + ((7-5) * (3.14159 * x^(14-10)) + sin(-3.141) + (0%x)) * x/3 * 3/sqrt(x))  avgt    9  332.326 ± 15.629  ns/op
PerformanceTest.nativePerformance                  1  (sin(x) + 2 + ((7-5) * (3.14159 * x^(14-10)) + sin(-3.141) + (0%x)) * x/3 * 3/sqrt(x))  avgt    9   33.102 ±  0.384  ns/op
PerformanceTest.nativePerformance               1000  (sin(x) + 2 + ((7-5) * (3.14159 * x^(14-10)) + sin(-3.141) + (0%x)) * x/3 * 3/sqrt(x))  avgt    9   33.286 ±  1.389  ns/op
PerformanceTest.nativePerformance              23422  (sin(x) + 2 + ((7-5) * (3.14159 * x^(14-10)) + sin(-3.141) + (0%x)) * x/3 * 3/sqrt(x))  avgt    9   33.003 ±  0.699  ns/op
PerformanceTest.nativePerformanceOptimized         1  (sin(x) + 2 + ((7-5) * (3.14159 * x^(14-10)) + sin(-3.141) + (0%x)) * x/3 * 3/sqrt(x))  avgt    9    5.301 ±  0.185  ns/op
PerformanceTest.nativePerformanceOptimized      1000  (sin(x) + 2 + ((7-5) * (3.14159 * x^(14-10)) + sin(-3.141) + (0%x)) * x/3 * 3/sqrt(x))  avgt    9    5.257 ±  0.043  ns/op
PerformanceTest.nativePerformanceOptimized     23422  (sin(x) + 2 + ((7-5) * (3.14159 * x^(14-10)) + sin(-3.141) + (0%x)) * x/3 * 3/sqrt(x))  avgt    9    5.306 ±  0.114  ns/op
PerformanceTest.nativePerformanceSimplified        1  (sin(x) + 2 + ((7-5) * (3.14159 * x^(14-10)) + sin(-3.141) + (0%x)) * x/3 * 3/sqrt(x))  avgt    9   23.112 ±  0.243  ns/op
PerformanceTest.nativePerformanceSimplified     1000  (sin(x) + 2 + ((7-5) * (3.14159 * x^(14-10)) + sin(-3.141) + (0%x)) * x/3 * 3/sqrt(x))  avgt    9   22.905 ±  0.610  ns/op
PerformanceTest.nativePerformanceSimplified    23422  (sin(x) + 2 + ((7-5) * (3.14159 * x^(14-10)) + sin(-3.141) + (0%x)) * x/3 * 3/sqrt(x))  avgt    9   23.084 ±  0.299  ns/op
PerformanceTest.paralithicPerformance              1  (sin(x) + 2 + ((7-5) * (3.14159 * x^(14-10)) + sin(-3.141) + (0%x)) * x/3 * 3/sqrt(x))  avgt    9    5.442 ±  0.079  ns/op
PerformanceTest.paralithicPerformance           1000  (sin(x) + 2 + ((7-5) * (3.14159 * x^(14-10)) + sin(-3.141) + (0%x)) * x/3 * 3/sqrt(x))  avgt    9    5.432 ±  0.115  ns/op
PerformanceTest.paralithicPerformance          23422  (sin(x) + 2 + ((7-5) * (3.14159 * x^(14-10)) + sin(-3.141) + (0%x)) * x/3 * 3/sqrt(x))  avgt    9    5.541 ±  0.166  ns/op
PerformanceTest.parsiiPerformance                  1  (sin(x) + 2 + ((7-5) * (3.14159 * x^(14-10)) + sin(-3.141) + (0%x)) * x/3 * 3/sqrt(x))  avgt    9   97.353 ±  3.488  ns/op
PerformanceTest.parsiiPerformance               1000  (sin(x) + 2 + ((7-5) * (3.14159 * x^(14-10)) + sin(-3.141) + (0%x)) * x/3 * 3/sqrt(x))  avgt    9   96.237 ±  2.216  ns/op
PerformanceTest.parsiiPerformance              23422  (sin(x) + 2 + ((7-5) * (3.14159 * x^(14-10)) + sin(-3.141) + (0%x)) * x/3 * 3/sqrt(x))  avgt    9   97.120 ±  3.215  ns/op

Paralithic generated the following class from the input function (decompiled with Vineflower, a fork of FernFlower):

public class ExpressionIMPL_0 implements Expression {
    public ExpressionIMPL_0() {
    }

    public double evaluate(Context var1, double[] var2) {
        return TrigonometryFunctions.sin(var2[0]) + Math.fma(Math.fma(6.28318, IntegerFunctions.iPow(var2[0], 4.0), -7.669050828553736E-4) * var2[0], AlgebraFunctions.invSqrt(var2[0]), 2.0);
    }
}

License

Paralithic is licensed under the MIT License.

Paralithic is a "fork" of a modified version of Parsii, licensed under the MIT license.