Skip to content

Commit cb69bab

Browse files
JackWang032jialan
andauthored
chore: benchmark optimize (#348)
* feat: add benchmark hot runs mode and release reports * feat: update benchmark reports * feat: hot start will clear atn cache every benchmark test and add version limit * feat: hot start support benchmark results --------- Co-authored-by: jialan <jialan@dtstack.com>
1 parent b835c4b commit cb69bab

18 files changed

+682
-50
lines changed

benchmark/run.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { Table } from 'console-table-printer';
1010

1111
const argv = argsParser(process.argv.slice(2));
1212
const isChooseAll = argv.lang === 'all';
13+
const isHot = argv.hot !== undefined;
14+
const isRelease = argv.release !== undefined;
1315
const testFiles = config.testFiles;
1416

1517
let benchmarkResults: SqlBenchmark[] = [];
@@ -115,7 +117,7 @@ const printSummaryReport = () => {
115117
};
116118

117119
const benchmark = (lang: Language) => {
118-
const sqlBenchmark = new SqlBenchmark(lang);
120+
const sqlBenchmark = new SqlBenchmark(lang, isHot, isRelease);
119121
const originalParams = readParams();
120122

121123
testFiles.forEach((testInfo) => {

benchmark/sqlBenchmark.ts

Lines changed: 98 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ export type BenchmarkResult = {
1616
};
1717

1818
// 过滤掉异常数据,m为判断为异常值的标准差倍数
19-
const removeOutliers = (data, m = 2) => {
19+
const removeOutliers = (data, m = 1) => {
2020
if (data.length <= 2) return data;
2121
const mean = data.reduce((a, b) => a + b, 0) / data.length;
2222
const standardDeviation = Math.sqrt(
2323
data.map((x) => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / data.length
2424
);
2525

26-
return data.filter((x) => Math.abs(x - mean) < m * standardDeviation);
26+
return data.filter((x) => Math.abs(x - mean) <= m * standardDeviation);
2727
};
2828

2929
const tableColumns = [
@@ -57,6 +57,14 @@ const tableColumns = [
5757
},
5858
];
5959

60+
const releaseColumns = tableColumns.filter(
61+
(column) => !['lastCostTime', 'costTimes', 'loopTimes'].includes(column.name)
62+
);
63+
64+
const hotRunColumns = tableColumns.filter(
65+
(column) => !['avgTime', 'lastCostTime'].includes(column.name)
66+
);
67+
6068
/**
6169
* Key is sql directory name, value is module export name.
6270
*/
@@ -75,17 +83,26 @@ export type Language = keyof typeof languageNameMap;
7583
export const languages = Object.keys(languageNameMap) as Language[];
7684

7785
class SqlBenchmark {
78-
constructor(language: string) {
86+
constructor(language: string, isHot: boolean, isRelease: boolean) {
7987
this.language = language;
88+
this.isHot = isHot;
89+
this.isRelease = isRelease;
90+
8091
this._lastResultsCache = this.getLastResults();
8192
}
8293

8394
public results: BenchmarkResult[] = [];
8495

8596
public readonly language: string;
8697

98+
public readonly isHot: boolean;
99+
100+
public readonly isRelease: boolean;
101+
87102
private _DEFAULT_LOOP_TIMES = 5;
88103

104+
private _RELEASE_LOOP_TIMES = 15;
105+
89106
/**
90107
* If current average time difference from last time grater than DIFF_RATIO, we will highlight that record.
91108
*/
@@ -95,25 +112,39 @@ class SqlBenchmark {
95112

96113
/**
97114
* Returns SqlParser instance of specific language.
98-
*
99-
* Due to the presence of ATN cache in antlr4, we will clear the module cache to ensure that each parser is brand new and with no cache.
100115
*/
101116
getSqlParser(): BasicSQL {
102-
const caches = Object.keys(require.cache);
103-
caches.forEach((moduleName) => {
104-
const module = require.cache[moduleName]!;
105-
// Fix Memory Leak
106-
if (module.parent) {
107-
module.parent.children = [];
108-
}
109-
delete require.cache[moduleName];
110-
});
117+
if (!this.isHot) {
118+
this.clearATNCache();
119+
}
111120
const Parser = require(path.resolve(`src/parser/${this.language}/index.ts`))[
112121
languageNameMap[this.language]
113122
];
114123
return new Parser();
115124
}
116125

126+
/**
127+
* Due to the presence of ATN cache in antlr4, we will clear the module cache to ensure that each parser is brand new and with no cache.
128+
*/
129+
clearATNCache() {
130+
const caches = Object.keys(require.cache);
131+
const sourcePath = path.join(__dirname, '../src');
132+
caches
133+
.filter((cache) => cache.includes(sourcePath))
134+
.forEach((moduleName) => {
135+
const module = require.cache[moduleName]!;
136+
// Fix Memory Leak
137+
if (module.parent) {
138+
module.parent.children = [];
139+
}
140+
delete require.cache[moduleName];
141+
});
142+
}
143+
144+
getColumns = () => {
145+
return this.isRelease ? releaseColumns : this.isHot ? hotRunColumns : tableColumns;
146+
};
147+
117148
/**
118149
* @param type Which parser method you want to run
119150
* @param name Benchmark name
@@ -126,11 +157,22 @@ class SqlBenchmark {
126157
name: string,
127158
params: any[],
128159
sqlRows: number,
129-
loopTimes: number = this._DEFAULT_LOOP_TIMES
160+
loops: number = this._DEFAULT_LOOP_TIMES
130161
) {
162+
let avgTime = 0;
163+
let loopTimes = this.isRelease ? this._RELEASE_LOOP_TIMES : loops;
131164
const costTimes: number[] = [];
132165
const lastResult =
133166
this._lastResultsCache?.find((item) => item.type === type && item.name === name) ?? {};
167+
168+
if (this.isHot) {
169+
this.clearATNCache();
170+
}
171+
172+
if (this.isHot && loopTimes < 2) {
173+
throw new Error('Hot start should run at least 2 times');
174+
}
175+
134176
for (let i = 0; i < loopTimes; i++) {
135177
const parser = this.getSqlParser();
136178
if (!parser[type] || typeof parser[type] !== 'function') return;
@@ -141,10 +183,12 @@ class SqlBenchmark {
141183

142184
costTimes.push(Math.round(costTime));
143185
}
144-
const filteredData = removeOutliers(costTimes);
145-
const avgTime = Math.round(
186+
187+
const filteredData = removeOutliers(this.isHot ? costTimes.slice(1) : costTimes);
188+
avgTime = Math.round(
146189
filteredData.reduce((prev, curr) => prev + curr, 0) / filteredData.length
147190
);
191+
148192
const result = {
149193
name,
150194
avgTime,
@@ -159,11 +203,18 @@ class SqlBenchmark {
159203
}
160204

161205
getLastResults() {
162-
const reportPath = path.join(__dirname, `./reports/${this.language}.benchmark.md`);
163-
if (!fs.existsSync(reportPath)) return null;
206+
const reportPath = path.join(
207+
__dirname,
208+
'./reports',
209+
this.isHot ? 'hot_start' : 'cold_start',
210+
`${this.language}.benchmark.md`
211+
);
212+
if (this.isRelease || !fs.existsSync(reportPath)) return null;
213+
164214
const report = fs.readFileSync(reportPath, { encoding: 'utf-8' });
165215
const pattern = /<input .*? value=['"](.+?)['"]\s*\/>/;
166216
const match = pattern.exec(report);
217+
167218
if (match) {
168219
const lastResultsStr = match[1];
169220
try {
@@ -174,6 +225,7 @@ class SqlBenchmark {
174225
return null;
175226
}
176227
}
228+
177229
return null;
178230
}
179231

@@ -188,11 +240,12 @@ class SqlBenchmark {
188240
lastCostTime !== undefined &&
189241
Math.abs(lastCostTime - currentCostTime) / currentCostTime >
190242
this._HIGHLIGHT_DIFF_RATIO;
191-
const [color, icon] = isSignificantDiff
192-
? currentCostTime > lastCostTime
193-
? ['red', '↓']
194-
: ['green', '↑']
195-
: ['#FFF', ' '];
243+
const [color, icon] =
244+
isSignificantDiff && !this.isHot
245+
? currentCostTime > lastCostTime
246+
? ['red', '↑']
247+
: ['green', '↓']
248+
: ['#FFF', ' '];
196249

197250
table.addRow(
198251
{
@@ -218,12 +271,17 @@ class SqlBenchmark {
218271
);
219272
const currentVersion = require('../package.json').version;
220273
const parsedEnvInfo = JSON.parse(envInfo);
274+
const baseDir = path.join(
275+
__dirname,
276+
this.isRelease ? '../benchmark_reports' : './reports',
277+
this.isHot ? 'hot_start' : 'cold_start'
278+
);
221279

222-
if (!fs.existsSync(path.join(__dirname, `./reports`))) {
223-
fs.mkdirSync(path.join(__dirname, `./reports`), { recursive: true });
280+
if (!fs.existsSync(baseDir)) {
281+
fs.mkdirSync(baseDir, { recursive: true });
224282
}
225283

226-
const writePath = path.join(__dirname, `./reports/${this.language}.benchmark.md`);
284+
const writePath = path.join(baseDir, `./${this.language}.benchmark.md`);
227285
const writter = new MarkdownWritter(writePath);
228286
writter.writeHeader('Benchmark', 2);
229287
writter.writeLine();
@@ -239,21 +297,28 @@ class SqlBenchmark {
239297
writter.writeHeader('Device', 3);
240298
writter.writeText(parsedEnvInfo.System.OS);
241299
writter.writeText(parsedEnvInfo.System.CPU);
242-
writter.writeText(parsedEnvInfo.System.Memory);
300+
writter.writeText(parsedEnvInfo.System.Memory?.split('/')[1]?.trim());
243301
writter.writeLine();
244302

245303
writter.writeHeader('Version', 3);
246-
writter.writeText(`dt-sql-parser: ${currentVersion}`);
247-
writter.writeText(`antlr4-c3: ${parsedEnvInfo.npmPackages['antlr4-c3']?.installed}`);
248-
writter.writeText(`antlr4ng: ${parsedEnvInfo.npmPackages['antlr4ng']?.installed}`);
304+
writter.writeText(`\`nodejs\`: ${process.version}`);
305+
writter.writeText(`\`dt-sql-parser\`: v${currentVersion}`);
306+
writter.writeText(`\`antlr4-c3\`: v${parsedEnvInfo.npmPackages['antlr4-c3']?.installed}`);
307+
writter.writeText(`\`antlr4ng\`: v${parsedEnvInfo.npmPackages['antlr4ng']?.installed}`);
308+
writter.writeLine();
309+
310+
writter.writeHeader('Running Mode', 3);
311+
writter.writeText(this.isHot ? 'Hot Start' : 'Cold Start');
249312
writter.writeLine();
250313

251314
writter.writeHeader('Report', 3);
252-
writter.writeTable(tableColumns, this.results);
315+
316+
const columns = this.getColumns();
317+
writter.writeTable(columns, this.results);
253318
writter.writeLine();
254319

255320
// Save original data via hidden input
256-
writter.writeHiddenInput(this.results);
321+
!this.isRelease && writter.writeHiddenInput(this.results);
257322

258323
writter.save();
259324

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
## Benchmark
2+
3+
### Language
4+
FlinkSQL
5+
6+
### Report Time
7+
2024/9/9 19:55:03
8+
9+
### Device
10+
macOS 14.4.1
11+
(8) arm64 Apple M1 Pro
12+
16.00 GB
13+
14+
### Version
15+
`nodejs`: v21.6.1
16+
`dt-sql-parser`: v4.0.2
17+
`antlr4-c3`: v3.3.7
18+
`antlr4ng`: v2.0.11
19+
20+
### Running Mode
21+
Cold Start
22+
23+
### Report
24+
| Benchmark Name | Method Name |SQL Rows|Average Time(ms)|
25+
|----------------|----------------------------|--------|----------------|
26+
|Query Collection| getAllTokens | 1015 | 227 |
27+
|Query Collection| validate | 1015 | 221 |
28+
| Insert Columns | getAllTokens | 1001 | 65 |
29+
| Insert Columns | validate | 1001 | 65 |
30+
| Create Table | getAllTokens | 1004 | 27 |
31+
| Create Table | validate | 1004 | 26 |
32+
| Split SQL | splitSQLByStatement | 999 | 52 |
33+
|Collect Entities| getAllEntities | 1056 | 141 |
34+
| Suggestion |getSuggestionAtCaretPosition| 1056 | 131 |
35+
36+
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
## Benchmark
2+
3+
### Language
4+
HiveSQL
5+
6+
### Report Time
7+
2024/9/9 19:55:03
8+
9+
### Device
10+
macOS 14.4.1
11+
(8) arm64 Apple M1 Pro
12+
16.00 GB
13+
14+
### Version
15+
`nodejs`: v21.6.1
16+
`dt-sql-parser`: v4.0.2
17+
`antlr4-c3`: v3.3.7
18+
`antlr4ng`: v2.0.11
19+
20+
### Running Mode
21+
Cold Start
22+
23+
### Report
24+
| Benchmark Name | Method Name |SQL Rows|Average Time(ms)|
25+
|----------------|----------------------------|--------|----------------|
26+
|Query Collection| getAllTokens | 1015 | 185 |
27+
|Query Collection| validate | 1015 | 179 |
28+
| Update Table | getAllTokens | 1011 | 112 |
29+
| Update Table | validate | 1011 | 109 |
30+
| Insert Columns | getAllTokens | 1001 | 329 |
31+
| Insert Columns | validate | 1001 | 329 |
32+
| Create Table | getAllTokens | 1002 | 21 |
33+
| Create Table | validate | 1002 | 20 |
34+
| Split SQL | splitSQLByStatement | 1001 | 72 |
35+
|Collect Entities| getAllEntities | 1066 | 106 |
36+
| Suggestion |getSuggestionAtCaretPosition| 1066 | 100 |
37+
38+
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
## Benchmark
2+
3+
### Language
4+
ImpalaSQL
5+
6+
### Report Time
7+
2024/9/9 19:55:03
8+
9+
### Device
10+
macOS 14.4.1
11+
(8) arm64 Apple M1 Pro
12+
16.00 GB
13+
14+
### Version
15+
`nodejs`: v21.6.1
16+
`dt-sql-parser`: v4.0.2
17+
`antlr4-c3`: v3.3.7
18+
`antlr4ng`: v2.0.11
19+
20+
### Running Mode
21+
Cold Start
22+
23+
### Report
24+
| Benchmark Name | Method Name |SQL Rows|Average Time(ms)|
25+
|----------------|----------------------------|--------|----------------|
26+
|Query Collection| getAllTokens | 1015 | 71 |
27+
|Query Collection| validate | 1015 | 71 |
28+
| Update Table | getAllTokens | 1011 | 113 |
29+
| Update Table | validate | 1011 | 108 |
30+
| Insert Columns | getAllTokens | 1001 | 208 |
31+
| Insert Columns | validate | 1001 | 213 |
32+
| Create Table | getAllTokens | 1002 | 23 |
33+
| Create Table | validate | 1002 | 23 |
34+
| Split SQL | splitSQLByStatement | 1001 | 65 |
35+
|Collect Entities| getAllEntities | 1066 | 82 |
36+
| Suggestion |getSuggestionAtCaretPosition| 1066 | 83 |
37+
38+

0 commit comments

Comments
 (0)