@@ -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
2929const 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;
7583export const languages = Object . keys ( languageNameMap ) as Language [ ] ;
7684
7785class 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 = / < i n p u t .* ? v a l u e = [ ' " ] ( .+ ?) [ ' " ] \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
0 commit comments