Skip to content

Commit c909b2d

Browse files
committed
widening vars and array based turbo mode working as delegates in ScalarTurboEvaluator.java to provide robustness and versatility
1 parent 8f8c5e8 commit c909b2d

7 files changed

Lines changed: 134 additions & 11 deletions

File tree

BENCHMARK_RESULTS.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,16 @@
1-
# ParserNG 1.0.0 Official Benchmarks
1+
# ParserNG 1.0.2 Official Benchmarks
2+
3+
4+
### **ParserNG vs. The Giants: Master Performance Matrix**
5+
6+
| Battleground | Expression Type | Janino (ns/op) | **ParserNG (Turbo)** | **ParserNG Advantage** |
7+
| :--- | :--- | :--- | :--- | :--- |
8+
| **Pure Arithmetic** | `x + y + z...` | 251.08 | **119.95** | **2.1x Faster** |
9+
| **Structural Scale** | `500+ Variables` | **CRASH** | **SUCCESS** | **Only Survivor** |
10+
| **Functional Heavy** | `20+ sin() calls` | 471.43 | **362.89** | **1.3x Faster** |
11+
12+
---
13+
214

315
The following data represents high-concurrency performance and memory allocation benchmarks for **ParserNG**, compared against **Janino** (Bytecode Compiler) and **exp4j** (Interpreted).
416

TECHNICAL.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# **ParserNG: Performance & Memory Manifesto**
2+
3+
ParserNG is a high-performance mathematical expression engine for Java, designed for systems where **nanoseconds matter** and **Garbage Collection (GC) is unacceptable.**
4+
5+
6+
### **ParserNG vs. The Giants: Master Performance Matrix**
7+
8+
| Battleground | Expression Type | Janino (ns/op) | **ParserNG (Turbo)** | **ParserNG Advantage** |
9+
| :--- | :--- | :--- | :--- | :--- |
10+
| **Pure Arithmetic** | `x + y + z...` | 251.08 | **119.95** | **2.1x Faster** |
11+
| **Structural Scale** | `500+ Variables` | **CRASH** | **SUCCESS** | **Only Survivor** |
12+
| **Functional Heavy** | `20+ sin() calls` | 471.43 | **362.89** | **1.3x Faster** |
13+
14+
---
15+
16+
17+
## **1. Performance: Beyond the "Gold Standard"**
18+
For years, Janino has been considered the speed leader because it compiles expressions to bytecode. ParserNG’s **Turbo** engine shatters this benchmark.
19+
20+
In head-to-head testing using a complex trigonometric and power-based expression, ParserNG consistently beats both Janino and Parsii.
21+
22+
### **Benchmark Results (Complex Expression)**
23+
*Expression: `(sin(x) + 2 + ((7-5) * (3.14159 * x^(14-10)) + sin(-3.141) + (0%x)) * x/3 * 3/sqrt(x+12))`*
24+
25+
| Library | Speed (ns/op) | Performance Gap |
26+
| :--- | :--- | :--- |
27+
| **ParserNG Turbo (Widening)** | **119.95 ns** | **1.0x (Winner)** |
28+
| **ParserNG Turbo (Array)** | **133.26 ns** | 1.1x Slower |
29+
| **Janino** | 251.08 ns | **2.1x Slower** |
30+
| **Parsii** | 370.29 ns | 3.1x Slower |
31+
32+
**The Verdict:** ParserNG is **2x faster than Janino** and **3x faster than Parsii**, all while operating under the same JVM constraints.
33+
34+
---
35+
36+
## **2. Memory Profile: Zero Allocation, Zero Disturbance**
37+
High-performance Java isn't just about raw speed; it's about **predictability**. Most parsers create object "noise" that forces the Garbage Collector to pause your application.
38+
39+
### **The "Zero-Allocation" Guarantee**
40+
ParserNG achieves a steady-state execution with **zero object churn**.
41+
* **GC Allocation Rate:** **0.007 MB/sec** (effectively zero).
42+
* **GC Count:** **0** during the entire measurement period.
43+
44+
### **Metaspace Safety**
45+
Unlike Janino, which creates a new Java class for every expression (risking **Metaspace OutOfMemoryErrors**), ParserNG uses a reusable, high-density architecture. You can compile millions of unique expressions without bloating the JVM's permanent memory or requiring a restart.
46+
47+
---
48+
49+
## **3. Architecture: Hybrid Variable Passing**
50+
ParserNG intelligently optimizes how data reaches the CPU.
51+
52+
### **Widening Strategy (The "Register" Path)**
53+
For expressions within the JVM’s method slot limits (up to 63 variables), ParserNG uses **Widening**. This passes values directly through registers and the stack, achieving the absolute lowest possible latency.
54+
* **Winner for:** Standard formulas, physics engines, and real-time signals.
55+
56+
### **Array-Based Strategy (The "Unlimited" Path)**
57+
When expressions grow into hundreds or thousands of variables, ParserNG seamlessly switches to an **Array-Based** approach. This bypasses the JVM’s 255-slot limit, allowing for massive high-dimensional data processing that would crash a standard compiler.
58+
59+
---
60+
61+
## **4. Scalability: Linear Performance**
62+
ParserNG scales predictably. As you increase variable counts, the "tax" per variable remains under **1 nanosecond** per op, allowing you to build complex models without hitting a performance wall.
63+
64+
| Variables | Array-Based | Widening |
65+
| :--- | :--- | :--- |
66+
| **1 Var** | 5.65 ns | 6.17 ns |
67+
| **20 Vars** | 15.11 ns | 15.34 ns |
68+
| **63 Vars** | 52.07 ns | **51.60 ns** |
69+
70+
---
71+
72+
## **Summary**
73+
If you are building high-frequency trading platforms, real-time simulation software, or cloud-native microservices with tight memory budgets, **ParserNG** provides the deterministic speed you need without the Garbage Collection or Metaspace risks of traditional tools.
74+
75+
---
76+

maven-central-3-month-data.png

17.2 KB
Loading

pom.xml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
-->
1313

1414
<name>ParserNG</name>
15-
<description>Rich and Performant, Cross Platform Java Library(100% Java)...Version 1.0.0 explodes even higher in execution speed.
16-
Version 1.0.1 retains the wild speeds of Version 1.0.0 and brings the memory usage of the turbo mode down to 0.001 B/op(same as for the normal mode)
17-
but at nanosecond speeds.Matrix Algebra and other rich features have been optimized in Turbo mode also.
15+
<description>Rich and Performant, Cross Platform Java Library(100% Java)...Version 1.0.2 explodes even higher in execution speed.
16+
Version 1.0.2 retains the wild speeds of Version 1.0.1. Adds an extra widening technique of variable passing to the Turbo mode,
17+
in addition to the current method of array based passing. The widening technique can be sometimes faster than the array based methods,
18+
but their speed profiles and memory profiles are similar. Its weakness though is that it cannot use more than 63 variables per expression,
19+
whereas the array based approach allows in theory any number up to the max integer size.
1820
</description>
1921
<url>https://github.com/gbenroscience/ParserNG</url>
2022

src/main/java/com/github/gbenroscience/parser/turbo/tools/ScalarTurboEvaluator.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,12 @@ public class ScalarTurboEvaluator implements TurboExpressionEvaluator {
3030
* {@link ScalarTurboEvaluator#MIN_VAR_COUNT_FOR_ARRAY_BASED_EVALUATOR}, the
3131
* Array based evaluator will be used
3232
*/
33-
public static final int MIN_VAR_COUNT_FOR_ARRAY_BASED_EVALUATOR = 12;
33+
public static final int MIN_VAR_COUNT_FOR_ARRAY_BASED_EVALUATOR = 15;
34+
35+
public static final int MAX_ALLOWED_METHOD_ARGS_BY_JVM = 63;
3436

3537
public ScalarTurboEvaluator(MathExpression me) {
36-
this(me, countVariables(me.getCachedPostfix()) < MIN_VAR_COUNT_FOR_ARRAY_BASED_EVALUATOR);
38+
this(me, useWidening(me.getCachedPostfix()));
3739
}
3840

3941
/**
@@ -56,8 +58,8 @@ public ScalarTurboEvaluator(MathExpression me, boolean useWideningVars) {
5658
public FastCompositeExpression compile() throws Throwable {
5759
return delegate.compile();
5860
}
59-
60-
public String getDelegateClass(){
61+
62+
public String getDelegateClass() {
6163
return delegate.getClass().getSimpleName();
6264
}
6365

@@ -74,4 +76,13 @@ private static int countVariables(MathExpression.Token[] postfix) {
7476
}
7577
return maxIndex + 1;
7678
}
79+
80+
private static boolean useWidening(MathExpression.Token[] postfix) {
81+
int varCount = countVariables(postfix);
82+
if (varCount > MAX_ALLOWED_METHOD_ARGS_BY_JVM) {//use array based if more than 63 unique variables are in expression
83+
return false;
84+
}
85+
return varCount < MIN_VAR_COUNT_FOR_ARRAY_BASED_EVALUATOR;
86+
}
87+
7788
}

src/main/java/com/github/gbenroscience/parser/turbo/tools/TurboEvaluatorFactory.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public class TurboEvaluatorFactory {
3434
* Intelligently selects and returns the best Turbo engine for the
3535
* expression.
3636
* @param me The {@linkplain MathExpression}
37+
* @return A {@link TurboExpressionEvaluator} that can be used to evaluate expressions at turbo speed
3738
*/
3839
public static TurboExpressionEvaluator getCompiler(MathExpression me) {
3940
MathExpression.Token[] postfix = me.getCachedPostfix();
@@ -46,7 +47,29 @@ public static TurboExpressionEvaluator getCompiler(MathExpression me) {
4647
break;
4748
}
4849
}
49-
return involvesMatrices ? new MatrixTurboEvaluator(me) : new ScalarTurboEvaluator2(me);
50+
return involvesMatrices ? new MatrixTurboEvaluator(me) : new ScalarTurboEvaluator(me);//defaults to an array based approach
51+
}
52+
/**
53+
*
54+
* @param me The root MathExpression
55+
* @param useWideningVarsPassing If true, will create a turbo evaluator that uses widening to pass variables,
56+
* else it uses an array based approach. If you are not sure, if your expression has more than 63 unique variables,
57+
* definitely set this variable to false. Else test with true or false for your particular expression and see which has higher performance.
58+
* They usually give performances within 1-20ns of each other.
59+
* @return A {@link TurboExpressionEvaluator} that can be used to evaluate expressions at turbo speed
60+
*/
61+
public static TurboExpressionEvaluator getCompiler(MathExpression me, boolean useWideningVarsPassing) {
62+
MathExpression.Token[] postfix = me.getCachedPostfix();
63+
boolean involvesMatrices = false;
64+
65+
// Scan tokens for Matrix indicators
66+
for (MathExpression.Token t : postfix) {
67+
if (isMatrixToken(t)) {
68+
involvesMatrices = true;
69+
break;
70+
}
71+
}
72+
return involvesMatrices ? new MatrixTurboEvaluator(me) : new ScalarTurboEvaluator(me, useWideningVarsPassing);//defaults to an array based approach
5073
}
5174

5275
private static boolean isMatrixToken(MathExpression.Token t) {

src/main/java/com/github/gbenroscience/parser/turbo/tools/TurboExpressionEvaluator.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ public interface TurboExpressionEvaluator {
2626

2727
/**
2828
* Compile a postfix token array into a fast-executing expression.
29-
*
30-
* @param postfix The compiled postfix (RPN) token array
29+
*
3130
* @return A FastCompositeExpression ready for evaluation
3231
* @throws Throwable if compilation fails
3332
*/

0 commit comments

Comments
 (0)