Skip to content

Commit 2da498c

Browse files
committed
updated README
1 parent a4333c5 commit 2da498c

11 files changed

Lines changed: 1106 additions & 401 deletions

File tree

EXAMPLES.md

Lines changed: 432 additions & 0 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 81 additions & 361 deletions
Large diffs are not rendered by default.
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package com.github.gbenroscience.parser.ng.bench;
2+
3+
/**
4+
*
5+
* @author GBEMIRO
6+
*/
7+
import com.github.gbenroscience.parser.MathExpression;
8+
import com.github.gbenroscience.simd.turbo.tools.SIMDVectorTurboEvaluator;
9+
import org.openjdk.jmh.annotations.*;
10+
import com.github.gbenroscience.simd.turbo.SIMDCompositeExpression;
11+
import com.github.gbenroscience.simd.turbo.tools.FlatMatrixF;
12+
import org.openjdk.jmh.infra.Blackhole;
13+
14+
import java.util.concurrent.ThreadLocalRandom;
15+
import java.util.concurrent.TimeUnit;
16+
import org.openjdk.jmh.runner.Runner;
17+
import org.openjdk.jmh.runner.RunnerException;
18+
import org.openjdk.jmh.runner.options.Options;
19+
import org.openjdk.jmh.runner.options.OptionsBuilder;
20+
import org.openjdk.jmh.runner.options.TimeValue;
21+
22+
/**
23+
* Use this to build in the parser-ng-pro directory mvn clean install -Dgpg.skip
24+
* Use this to run the benchmarks
25+
*
26+
* java -jar target/benchmarks.jar SIMDTurboBench -prof perfasm
27+
*
28+
* java -jar target/benchmarks.jar SIMDTurboBench -prof perfasm >
29+
* perf_output.txt Now find your specific method's assembly grep -A 50
30+
* "parserNG" perf_output.txt
31+
*
32+
* @author GBEMIRO
33+
*/
34+
@BenchmarkMode(Mode.AverageTime) // ns/op
35+
@OutputTimeUnit(TimeUnit.NANOSECONDS)
36+
@State(Scope.Benchmark)
37+
@Fork(value = 3, jvmArgsAppend = {
38+
"-Xms4g", "-Xmx4g",
39+
"-XX:+UnlockDiagnosticVMOptions",
40+
"--add-modules", "jdk.incubator.vector", "-XX:+UnlockDiagnosticVMOptions"
41+
// "-XX:+PrintAssembly", "-XX:CompileCommand=print,*SIMDCompositeExpression.applyMatrixKernel" // uncomment if you have hsdis
42+
})
43+
@Warmup(iterations = 5, time = 2)
44+
@Measurement(iterations = 5, time = 2)
45+
public class ActivationBench {
46+
47+
@Param({"512", "1024", "2048", "4096"})
48+
public int sz;
49+
50+
private SIMDCompositeExpression evaluator;
51+
private FlatMatrixF in1;
52+
private FlatMatrixF in2;
53+
private FlatMatrixF out;
54+
private int N;
55+
56+
@Setup(Level.Trial)
57+
public void setup() throws Throwable {
58+
MathExpression me = new MathExpression("x * 0.5 * (1 + tanh(0.79788456 * (x + 0.044715 * x * x * x)))");
59+
evaluator = (SIMDCompositeExpression) new SIMDVectorTurboEvaluator(me).compile();
60+
61+
N = sz * sz;
62+
in1 = new FlatMatrixF(sz, sz);
63+
in2 = new FlatMatrixF(sz, sz);
64+
out = new FlatMatrixF(sz, sz);
65+
FlatMatrixF.randomFill(in1);
66+
FlatMatrixF.randomFill(in2);
67+
}
68+
69+
@Setup(Level.Iteration) // C2 can't optimize across iterations
70+
public void dirtyInput() {
71+
// JMH will call this before each measurement iteration
72+
// This makes the input unprovable without timing it
73+
for (int j = 0; j < N; j++) {
74+
in1.data[j] = ThreadLocalRandom.current().nextFloat();
75+
in2.data[j] = in1.data[j]*2.13f;
76+
}
77+
}
78+
79+
@Benchmark
80+
public void gelu(Blackhole bh) {
81+
evaluator.applyMatrixKernel(new FlatMatrixF[]{in1}, out, "gelu");
82+
// Consume the whole output so C2 can't DCE
83+
/*
84+
for (int j = 0; j < N; j++) {
85+
bh.consume(Float.floatToRawIntBits(out.data[j]));
86+
}
87+
*/
88+
bh.consume(out.data[0]);
89+
}
90+
@Benchmark
91+
public void swiglu(Blackhole bh) {
92+
evaluator.applyMatrixKernel(new FlatMatrixF[]{in1, in2}, out, "swiglu");
93+
// Consume the whole output so C2 can't DCE
94+
bh.consume(out.data[0]);
95+
}
96+
97+
public static void main(String[] args) throws RunnerException {
98+
OptionsBuilder opt = new OptionsBuilder();
99+
opt.include(ActivationBench.class.getSimpleName());
100+
101+
Options configurations = opt.mode(Mode.AverageTime)
102+
.timeUnit(TimeUnit.NANOSECONDS)
103+
.warmupIterations(5)
104+
.warmupTime(TimeValue.milliseconds(200L))
105+
.measurementIterations(5)
106+
.measurementTime(TimeValue.milliseconds(500))
107+
.forks(2)
108+
.addProfiler(org.openjdk.jmh.profile.GCProfiler.class)
109+
.jvmArgs("-Xms8g", "-Xmx8g", "-Dbenchmark.index=1")
110+
.jvmArgsAppend("--add-modules", "jdk.incubator.vector", "-XX:+UnlockDiagnosticVMOptions")
111+
.build();
112+
113+
new Runner(configurations).run();
114+
}
115+
}
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
package com.github.gbenroscience.parser.ng.bench;
2+
3+
/**
4+
*
5+
* @author GBEMIRO
6+
*/
7+
import com.github.gbenroscience.parser.MathExpression;
8+
import com.github.gbenroscience.simd.turbo.tools.SIMDVectorTurboEvaluator;
9+
import com.github.gbenroscience.simd.turbo.tools.VectorTurboEvaluator;
10+
import org.openjdk.jmh.annotations.*;
11+
import java.util.concurrent.TimeUnit;
12+
import java.util.Random;
13+
import java.util.concurrent.ThreadLocalRandom;
14+
import org.openjdk.jmh.infra.Blackhole;
15+
import org.openjdk.jmh.runner.Runner;
16+
import org.openjdk.jmh.runner.RunnerException;
17+
import org.openjdk.jmh.runner.options.Options;
18+
import org.openjdk.jmh.runner.options.OptionsBuilder;
19+
import org.openjdk.jmh.runner.options.TimeValue;
20+
21+
/**
22+
* Use this to build in the parser-ng-pro directory mvn clean install -Dgpg.skip
23+
* Use this to run the benchmarks
24+
*
25+
* java -jar target/benchmarks.jar SIMDTurboBench -prof perfasm
26+
*
27+
* java -jar target/benchmarks.jar SIMDTurboBench -prof perfasm >
28+
* perf_output.txt Now find your specific method's assembly grep -A 50
29+
* "parserNG" perf_output.txt
30+
*
31+
* @author GBEMIRO
32+
*/
33+
@BenchmarkMode(Mode.AverageTime)
34+
@OutputTimeUnit(TimeUnit.NANOSECONDS)
35+
@Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS)
36+
@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
37+
@Fork(value = 3, jvmArgs = {
38+
"-Xms5g", "-Xmx5g",
39+
"-XX:+UseG1GC",
40+
"-XX:-UseCompressedOops", // Avoids compressed oops artifacts
41+
"--add-modules", "jdk.incubator.vector", "-XX:+UnlockDiagnosticVMOptions"
42+
})
43+
@State(Scope.Thread)
44+
public class VectorTurboBenchStrict {
45+
46+
@Param({"12*x1 + 3*x2 - 4*x3 + 5*x1 - x2 - 4*x3 + 2*x1 + x2", "0.39894228 / x1 * exp(-((x2 - x3) * (x2 - x3)) / (2 * x1 * x1))"})
47+
String expression;
48+
49+
//static final String expression = "x1^3+x2^3+x3^3+x4^3+x5^3+x6^3";
50+
double[] result;
51+
52+
@Param({"20000000"})
53+
private int dataSize;
54+
55+
private double[] vars;
56+
private VectorTurboBench.JaninoMathFunction fastEvaluator;
57+
58+
private VectorTurboEvaluator.BatchedVectorCompositeExpression vectorTurbo;
59+
private String[] expressionVars;
60+
private int varCount;
61+
62+
private static double[][][] dataSink;
63+
64+
@Setup(Level.Trial)
65+
public void setup() {
66+
System.out.println("================EXPRESSION: " + expression + "================");
67+
MathExpression me = new MathExpression(expression);
68+
expressionVars = me.getVariablesNames();
69+
varCount = expressionVars.length;
70+
this.vars = new double[varCount];
71+
72+
result = new double[dataSize];
73+
74+
// Fill once per trial, not per iteration
75+
Random r = new Random(42);
76+
77+
if (dataSink == null) {
78+
// Allocate SoA: [5 scenarios][varCount variables][dataSize samples per variable]
79+
dataSink = new double[5][varCount][dataSize];
80+
81+
for (int i = 0; i < dataSink.length; i++) {
82+
for (int v = 0; v < varCount; v++) {
83+
for (int s = 0; s < dataSize; s++) {
84+
dataSink[i][v][s] = r.nextDouble(20);
85+
}
86+
}
87+
}
88+
System.out.println("Repopulated data sink with SoA layout: " + varCount + " variables per track.");
89+
}
90+
91+
this.fastEvaluator = VectorTurboBench.setupJanino(expression, expressionVars);
92+
93+
setupParserNG(me);
94+
95+
// Validation against the SoA structure
96+
for (int i = 0; i < dataSink.length; i++) {
97+
// Ensure your validate method is updated to handle double[][]
98+
// instead of a single flat array
99+
vectorTurbo.validate(dataSink[i], result);
100+
}
101+
}
102+
103+
@Benchmark
104+
@BenchmarkMode(Mode.AverageTime)
105+
@OutputTimeUnit(TimeUnit.NANOSECONDS)
106+
public void janino(Blackhole bh) {
107+
final int limit = dataSize;
108+
final int stride = varCount;
109+
// Select one scenario (SoA matrix)
110+
final double[][] input = dataSink[ThreadLocalRandom.current().nextInt(dataSink.length)];
111+
final double[] localVars = this.vars;
112+
final VectorTurboBench.JaninoMathFunction evaluator = this.fastEvaluator;
113+
114+
for (int i = 0; i < limit; i++) {
115+
// Linear access: read each variable for the current sample 'i'
116+
for (int j = 0; j < stride; j++) {
117+
localVars[j] = input[j][i];
118+
}
119+
bh.consume(evaluator.apply(localVars));
120+
}
121+
}
122+
123+
@Benchmark
124+
@BenchmarkMode(Mode.AverageTime)
125+
@OutputTimeUnit(TimeUnit.NANOSECONDS)
126+
public void parserNG(Blackhole bh) {
127+
// Select one scenario (SoA matrix)
128+
// Ensure simdVectorTurbo.applyBulk is updated to accept double[][]
129+
final double[][] input = dataSink[ThreadLocalRandom.current().nextInt(dataSink.length)];
130+
131+
// Measure execution of the SoA-optimized kernel
132+
vectorTurbo.applyBulk(input, result);
133+
134+
bh.consume(result);
135+
}
136+
137+
private void setupParserNG(MathExpression me) {
138+
try {
139+
vectorTurbo = (VectorTurboEvaluator.BatchedVectorCompositeExpression) new VectorTurboEvaluator(me).compile();
140+
} catch (Throwable ex) {
141+
System.getLogger(VectorTurboBenchStrict.class.getName()).log(System.Logger.Level.ERROR, (String) null, ex);
142+
}
143+
}
144+
145+
public static void main(String[] args) throws RunnerException {
146+
OptionsBuilder opt = new OptionsBuilder();
147+
opt.include(VectorTurboBenchStrict.class.getSimpleName()); // Always include baseline
148+
// 4. Fluent, modern JMH Configuration
149+
Options configurations = opt.mode(Mode.AverageTime)
150+
.timeUnit(TimeUnit.NANOSECONDS)
151+
.warmupIterations(5)
152+
.warmupTime(TimeValue.milliseconds(200L))
153+
.measurementIterations(5)
154+
.measurementTime(TimeValue.milliseconds(500))
155+
.forks(2)
156+
.addProfiler(org.openjdk.jmh.profile.GCProfiler.class)
157+
.jvmArgs("-Xms8g", "-Xmx8g", "-Dbenchmark.index=1")
158+
.jvmArgsAppend("--add-modules", "jdk.incubator.vector", "-XX:+UnlockDiagnosticVMOptions")
159+
.build();
160+
161+
new Runner(configurations).run();
162+
}
163+
}

parser-ng-simd/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ High-performance, hardware-accelerated mathematical kernels for Java. **No JNI.
66

77
`parser-ng-simd` provides an architectural extension for ParserNG, enabling zero-dependency, tile-based bulk evaluations via `jdk.incubator.vector`. It compiles mathematical expression trees into vectorized loops optimized for L1/L2 cache residency, achieving near-native throughput directly on the JVM without the configuration overhead of complex native toolchains (such as ND4J/`libnd4j`).
88

9+
For examples and more information:
10+
11+
- [SIMD-EXAMPLES.md](../parser-ng/BULK.md) — what’s new in 2.0.5
12+
913
---
1014

1115
### Performance Benchmarks

parser-ng-simd/pom.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,9 @@
101101
<artifactId>maven-surefire-plugin</artifactId>
102102
<version>3.2.5</version>
103103
<configuration>
104-
<argLine>--add-modules jdk.incubator.vector</argLine>
104+
<argLine>
105+
--add-modules jdk.incubator.vector
106+
</argLine>
105107
</configuration>
106108
</plugin>
107109

0 commit comments

Comments
 (0)