Skip to content

Commit fe860dd

Browse files
feat: adopt proper JMH based benchmark
Signed-off-by: Andreas Reichel <andreas@manticore-projects.com>
1 parent d35028c commit fe860dd

File tree

9 files changed

+2302
-3
lines changed

9 files changed

+2302
-3
lines changed

README.md

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# [JSqlParser 5.1 Website](https://jsqlparser.github.io/JSqlParser) <img src="src/site/sphinx/_images/logo-no-background.svg" alt="drawing" width="200" align="right"/>
1+
# [JSqlParser 5.2 Website](https://jsqlparser.github.io/JSqlParser) <img src="src/site/sphinx/_images/logo-no-background.svg" alt="drawing" width="200" align="right"/>
22

33
[![CI](https://github.com/JSQLParser/JSqlParser/actions/workflows/ci.yml/badge.svg)](https://github.com/JSQLParser/JSqlParser/actions/workflows/ci.yml)
44
[![Coverage Status](https://coveralls.io/repos/JSQLParser/JSqlParser/badge.svg?branch=master)](https://coveralls.io/r/JSQLParser/JSqlParser?branch=master)
@@ -69,6 +69,18 @@ JSQLParser-4.9 was the last JDK8 compatible version. JSQLParser-5.0 and later de
6969

7070
Building JSQLParser-5.1 and newer with Gradle will depend on a JDK17 toolchain due to the used plugins.
7171

72+
## Performan ce
73+
74+
Unfortunately the released JSQLParser-5.2 shows a performance deterioration caused by commit [30cf5d7](https://github.com/JSQLParser/JSqlParser/commit/30cf5d7b930ae0a076f49deb5cc841d39e62b3dc) related to `FunctionAllColumns()`.
75+
This has been resolved in JSQLParser 5.3-SNAPSHOT and JMH benchmarks have been added to avoid such regressions in the future. Further all `LOOKAHEADS` have been revised one by one, and we have gained back a very good performance of the Parser.
76+
77+
```text
78+
Benchmark (version) Mode Cnt Score Error Units
79+
JSQLParserBenchmark.parseSQLStatements latest avgt 30 83.504 ± 1.557 ms/op <-- `FunctionAllColumns()` disabled
80+
JSQLParserBenchmark.parseSQLStatements 5.2 avgt 30 400.876 ± 8.291 ms/op
81+
JSQLParserBenchmark.parseSQLStatements 5.1 avgt 30 85.731 ± 1.288 ms/op
82+
```
83+
7284
## [Supported Grammar and Syntax](https://jsqlparser.github.io/JSqlParser/syntax.html)
7385

7486
**JSqlParser** aims to support the SQL standard as well as all major RDBMS. Any missing syntax or features can be added on demand.

build.gradle

+10-1
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,8 @@ dependencies {
117117
testImplementation 'net.java.dev.javacc:javacc:+'
118118
javacc 'net.java.dev.javacc:javacc:+'
119119

120-
120+
jmh 'org.openjdk.jmh:jmh-core:1.37'
121+
jmh 'org.openjdk.jmh:jmh-generator-annprocess:1.37'
121122
}
122123

123124
compileJavacc {
@@ -633,3 +634,11 @@ tasks.register('upload') {
633634
check {
634635
dependsOn jacocoTestCoverageVerification
635636
}
637+
638+
jmh {
639+
includes = ['.*JSQLParserBenchmark.*']
640+
warmupIterations = 3
641+
fork = 3
642+
iterations = 10
643+
timeOnIteration = '1s'
644+
}
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
-- Optimised LOOKAHEADS (replacing all syntactic lookahead by numeric lookaheads)
2+
Benchmark (version) Mode Cnt Score Error Units
3+
JSQLParserBenchmark.parseSQLStatements latest avgt 15 264.132 ± 9.636 ms/op
4+
JSQLParserBenchmark.parseSQLStatements 5.2 avgt 15 415.744 ± 20.602 ms/op
5+
JSQLParserBenchmark.parseSQLStatements 5.1 avgt 15 89.387 ± 1.916 ms/op
6+
JSQLParserBenchmark.parseSQLStatements 5.0 avgt 15 68.810 ± 2.591 ms/op
7+
JSQLParserBenchmark.parseSQLStatements 4.9 avgt 15 60.515 ± 1.650 ms/op
8+
JSQLParserBenchmark.parseSQLStatements 4.8 avgt 15 60.002 ± 1.259 ms/op
9+
JSQLParserBenchmark.parseSQLStatements 4.7 avgt 15 73.291 ± 3.049 ms/op
10+
11+
-- Optimised LOOKAHEADS (replacing huge numeric lookaheads with syntactic lookaheads again)
12+
Benchmark (version) Mode Cnt Score Error Units
13+
JSQLParserBenchmark.parseSQLStatements latest avgt 15 249.408 ± 11.340 ms/op
14+
JSQLParserBenchmark.parseSQLStatements 5.2 avgt 15 388.453 ± 13.149 ms/op
15+
16+
-- Disable `FunctionAllColumns()`
17+
Benchmark (version) Mode Cnt Score Error Units
18+
JSQLParserBenchmark.parseSQLStatements latest avgt 30 83.504 ± 1.557 ms/op
19+
JSQLParserBenchmark.parseSQLStatements 5.2 avgt 30 400.876 ± 8.291 ms/op
20+
JSQLParserBenchmark.parseSQLStatements 5.1 avgt 30 85.731 ± 1.288 ms/op

src/site/sphinx/contribution.rst

+16-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,22 @@ The JSQLParser is generated by ``JavaCC`` based on the provided Grammar. The Gra
7777
7878
mvn verify
7979
80-
7) Create your `GitHub Pull Request <https://www.youtube.com/watch?v=nCKdihvneS0>`_
80+
7) Verify the performance and avoid any deterioration
81+
82+
.. code-block:: shell
83+
:caption: Gradle `check` Task
84+
85+
gradle jmh
86+
87+
.. code-block:: text
88+
:caption: JMH performance results
89+
90+
Benchmark (version) Mode Cnt Score Error Units
91+
JSQLParserBenchmark.parseSQLStatements latest avgt 30 83.504 ± 1.557 ms/op
92+
JSQLParserBenchmark.parseSQLStatements 5.2 avgt 30 400.876 ± 8.291 ms/op
93+
JSQLParserBenchmark.parseSQLStatements 5.1 avgt 30 85.731 ± 1.288 ms/op
94+
95+
8) Create your `GitHub Pull Request <https://www.youtube.com/watch?v=nCKdihvneS0>`_
8196

8297
Manage Reserved Keywords
8398
------------------------------
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package net.sf.jsqlparser.benchmark;
2+
3+
import net.sf.jsqlparser.parser.CCJSqlParser;
4+
import net.sf.jsqlparser.statement.Statements;
5+
6+
import java.lang.reflect.Method;
7+
import java.net.URLClassLoader;
8+
import java.util.concurrent.ExecutorService;
9+
import java.util.function.Consumer;
10+
11+
public class DynamicParserRunner implements SqlParserRunner {
12+
private final Method parseStatementsMethod;
13+
14+
public DynamicParserRunner(URLClassLoader loader) throws Exception {
15+
Class<?> utilClass = loader.loadClass("net.sf.jsqlparser.parser.CCJSqlParserUtil");
16+
Class<?> ccjClass = loader.loadClass("net.sf.jsqlparser.parser.CCJSqlParser");
17+
Class<?> consumerClass = Class.forName("java.util.function.Consumer"); // interface OK
18+
parseStatementsMethod = utilClass.getMethod(
19+
"parseStatements",
20+
String.class,
21+
ExecutorService.class,
22+
consumerClass);
23+
}
24+
25+
@Override
26+
public Statements parseStatements(String sql,
27+
ExecutorService executorService,
28+
Consumer<CCJSqlParser> consumer) throws Exception {
29+
return (Statements) parseStatementsMethod.invoke(null, sql, executorService, null);
30+
}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package net.sf.jsqlparser.benchmark;
2+
3+
import net.sf.jsqlparser.parser.CCJSqlParser;
4+
import net.sf.jsqlparser.statement.Statements;
5+
import org.openjdk.jmh.annotations.*;
6+
7+
import java.io.IOException;
8+
import java.io.InputStream;
9+
import java.net.URL;
10+
import java.net.URLClassLoader;
11+
import java.nio.charset.StandardCharsets;
12+
import java.nio.file.*;
13+
import java.util.concurrent.*;
14+
import java.util.function.Consumer;
15+
16+
@BenchmarkMode(Mode.AverageTime)
17+
@OutputTimeUnit(TimeUnit.MILLISECONDS)
18+
@State(Scope.Benchmark)
19+
public class JSQLParserBenchmark {
20+
21+
private String sqlContent;
22+
private ExecutorService executorService;
23+
24+
SqlParserRunner runner;
25+
26+
// @Param({ "latest", "5.2", "5.1", "5.0", "4.9", "4.8", "4.7", "4.6", "4.5" })
27+
@Param({"latest", "5.2", "5.1"})
28+
public String version;
29+
30+
@Setup(Level.Trial)
31+
public void setup() throws Exception {
32+
if ("latest".equals(version)) {
33+
runner = new LatestClasspathRunner(); // direct call, no reflection
34+
} else {
35+
Path jarPath = downloadJsqlparserJar(version);
36+
URLClassLoader loader = new URLClassLoader(new URL[] {jarPath.toUri().toURL()}, null);
37+
runner = new DynamicParserRunner(loader);
38+
}
39+
40+
// Adjust path as necessary based on where source root is during test execution
41+
Path path = Paths.get("src/test/resources/net/sf/jsqlparser/performance.sql");
42+
sqlContent = Files.readString(path, StandardCharsets.UTF_8);
43+
executorService = Executors.newSingleThreadExecutor();
44+
}
45+
46+
private Path downloadJsqlparserJar(String version) throws IOException {
47+
String jarUrl = String.format(
48+
"https://repo1.maven.org/maven2/com/github/jsqlparser/jsqlparser/%s/jsqlparser-%s.jar",
49+
version, version);
50+
51+
Path cacheDir = Paths.get("build/libs/downloaded-jars");
52+
Files.createDirectories(cacheDir);
53+
Path jarFile = cacheDir.resolve("jsqlparser-" + version + ".jar");
54+
55+
if (!Files.exists(jarFile)) {
56+
System.out.println("Downloading " + version);
57+
try (InputStream in = new URL(jarUrl).openStream()) {
58+
Files.copy(in, jarFile);
59+
}
60+
}
61+
62+
return jarFile;
63+
}
64+
65+
@Benchmark
66+
public void parseSQLStatements() throws Exception {
67+
final Statements statements = runner.parseStatements(
68+
sqlContent,
69+
executorService,
70+
(Consumer<CCJSqlParser>) parser -> {
71+
// No-op consumer (or you can log/validate each parser if desired)
72+
});
73+
assert statements.size() == 4;
74+
}
75+
76+
@TearDown(Level.Trial)
77+
public void tearDown() {
78+
executorService.shutdown();
79+
}
80+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package net.sf.jsqlparser.benchmark;
2+
3+
import net.sf.jsqlparser.parser.CCJSqlParser;
4+
import net.sf.jsqlparser.statement.Statements;
5+
6+
import java.util.concurrent.ExecutorService;
7+
import java.util.function.Consumer;
8+
9+
public class LatestClasspathRunner implements SqlParserRunner {
10+
11+
@Override
12+
public Statements parseStatements(String sql,
13+
ExecutorService executorService,
14+
Consumer<CCJSqlParser> consumer) throws Exception {
15+
return net.sf.jsqlparser.parser.CCJSqlParserUtil.parseStatements(sql, executorService,
16+
consumer);
17+
}
18+
}
19+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package net.sf.jsqlparser.benchmark;
2+
3+
import net.sf.jsqlparser.parser.CCJSqlParser;
4+
import net.sf.jsqlparser.statement.Statements;
5+
6+
import java.util.concurrent.ExecutorService;
7+
import java.util.function.Consumer;
8+
9+
public interface SqlParserRunner {
10+
Statements parseStatements(String sql, ExecutorService executorService,
11+
Consumer<CCJSqlParser> consumer) throws Exception;
12+
}

0 commit comments

Comments
 (0)