@@ -16,14 +16,14 @@ export type BenchmarkResult = {
16
16
} ;
17
17
18
18
// 过滤掉异常数据,m为判断为异常值的标准差倍数
19
- const removeOutliers = ( data , m = 2 ) => {
19
+ const removeOutliers = ( data , m = 1 ) => {
20
20
if ( data . length <= 2 ) return data ;
21
21
const mean = data . reduce ( ( a , b ) => a + b , 0 ) / data . length ;
22
22
const standardDeviation = Math . sqrt (
23
23
data . map ( ( x ) => Math . pow ( x - mean , 2 ) ) . reduce ( ( a , b ) => a + b ) / data . length
24
24
) ;
25
25
26
- return data . filter ( ( x ) => Math . abs ( x - mean ) < m * standardDeviation ) ;
26
+ return data . filter ( ( x ) => Math . abs ( x - mean ) <= m * standardDeviation ) ;
27
27
} ;
28
28
29
29
const tableColumns = [
@@ -57,6 +57,14 @@ const tableColumns = [
57
57
} ,
58
58
] ;
59
59
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
+
60
68
/**
61
69
* Key is sql directory name, value is module export name.
62
70
*/
@@ -75,17 +83,26 @@ export type Language = keyof typeof languageNameMap;
75
83
export const languages = Object . keys ( languageNameMap ) as Language [ ] ;
76
84
77
85
class SqlBenchmark {
78
- constructor ( language : string ) {
86
+ constructor ( language : string , isHot : boolean , isRelease : boolean ) {
79
87
this . language = language ;
88
+ this . isHot = isHot ;
89
+ this . isRelease = isRelease ;
90
+
80
91
this . _lastResultsCache = this . getLastResults ( ) ;
81
92
}
82
93
83
94
public results : BenchmarkResult [ ] = [ ] ;
84
95
85
96
public readonly language : string ;
86
97
98
+ public readonly isHot : boolean ;
99
+
100
+ public readonly isRelease : boolean ;
101
+
87
102
private _DEFAULT_LOOP_TIMES = 5 ;
88
103
104
+ private _RELEASE_LOOP_TIMES = 15 ;
105
+
89
106
/**
90
107
* If current average time difference from last time grater than DIFF_RATIO, we will highlight that record.
91
108
*/
@@ -95,25 +112,39 @@ class SqlBenchmark {
95
112
96
113
/**
97
114
* 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.
100
115
*/
101
116
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
+ }
111
120
const Parser = require ( path . resolve ( `src/parser/${ this . language } /index.ts` ) ) [
112
121
languageNameMap [ this . language ]
113
122
] ;
114
123
return new Parser ( ) ;
115
124
}
116
125
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
+
117
148
/**
118
149
* @param type Which parser method you want to run
119
150
* @param name Benchmark name
@@ -126,11 +157,22 @@ class SqlBenchmark {
126
157
name : string ,
127
158
params : any [ ] ,
128
159
sqlRows : number ,
129
- loopTimes : number = this . _DEFAULT_LOOP_TIMES
160
+ loops : number = this . _DEFAULT_LOOP_TIMES
130
161
) {
162
+ let avgTime = 0 ;
163
+ let loopTimes = this . isRelease ? this . _RELEASE_LOOP_TIMES : loops ;
131
164
const costTimes : number [ ] = [ ] ;
132
165
const lastResult =
133
166
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
+
134
176
for ( let i = 0 ; i < loopTimes ; i ++ ) {
135
177
const parser = this . getSqlParser ( ) ;
136
178
if ( ! parser [ type ] || typeof parser [ type ] !== 'function' ) return ;
@@ -141,10 +183,12 @@ class SqlBenchmark {
141
183
142
184
costTimes . push ( Math . round ( costTime ) ) ;
143
185
}
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 (
146
189
filteredData . reduce ( ( prev , curr ) => prev + curr , 0 ) / filteredData . length
147
190
) ;
191
+
148
192
const result = {
149
193
name,
150
194
avgTime,
@@ -159,11 +203,18 @@ class SqlBenchmark {
159
203
}
160
204
161
205
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
+
164
214
const report = fs . readFileSync ( reportPath , { encoding : 'utf-8' } ) ;
165
215
const pattern = / < i n p u t .* ? v a l u e = [ ' " ] ( .+ ?) [ ' " ] \s * \/ > / ;
166
216
const match = pattern . exec ( report ) ;
217
+
167
218
if ( match ) {
168
219
const lastResultsStr = match [ 1 ] ;
169
220
try {
@@ -174,6 +225,7 @@ class SqlBenchmark {
174
225
return null ;
175
226
}
176
227
}
228
+
177
229
return null ;
178
230
}
179
231
@@ -188,11 +240,12 @@ class SqlBenchmark {
188
240
lastCostTime !== undefined &&
189
241
Math . abs ( lastCostTime - currentCostTime ) / currentCostTime >
190
242
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' , ' ' ] ;
196
249
197
250
table . addRow (
198
251
{
@@ -218,12 +271,17 @@ class SqlBenchmark {
218
271
) ;
219
272
const currentVersion = require ( '../package.json' ) . version ;
220
273
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
+ ) ;
221
279
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 } ) ;
224
282
}
225
283
226
- const writePath = path . join ( __dirname , `./reports /${ this . language } .benchmark.md` ) ;
284
+ const writePath = path . join ( baseDir , `./${ this . language } .benchmark.md` ) ;
227
285
const writter = new MarkdownWritter ( writePath ) ;
228
286
writter . writeHeader ( 'Benchmark' , 2 ) ;
229
287
writter . writeLine ( ) ;
@@ -239,21 +297,28 @@ class SqlBenchmark {
239
297
writter . writeHeader ( 'Device' , 3 ) ;
240
298
writter . writeText ( parsedEnvInfo . System . OS ) ;
241
299
writter . writeText ( parsedEnvInfo . System . CPU ) ;
242
- writter . writeText ( parsedEnvInfo . System . Memory ) ;
300
+ writter . writeText ( parsedEnvInfo . System . Memory ?. split ( '/' ) [ 1 ] ?. trim ( ) ) ;
243
301
writter . writeLine ( ) ;
244
302
245
303
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' ) ;
249
312
writter . writeLine ( ) ;
250
313
251
314
writter . writeHeader ( 'Report' , 3 ) ;
252
- writter . writeTable ( tableColumns , this . results ) ;
315
+
316
+ const columns = this . getColumns ( ) ;
317
+ writter . writeTable ( columns , this . results ) ;
253
318
writter . writeLine ( ) ;
254
319
255
320
// Save original data via hidden input
256
- writter . writeHiddenInput ( this . results ) ;
321
+ ! this . isRelease && writter . writeHiddenInput ( this . results ) ;
257
322
258
323
writter . save ( ) ;
259
324
0 commit comments