├── .github └── workflows │ └── update_benchmark.yml ├── .gitignore ├── .npmrc ├── LICENSE ├── README.md ├── README.template.md ├── benchmark ├── arguments_to_array.js ├── array_from_set.js ├── clone_object.js ├── co_wrap_vs_bluebrid_coroutine.js ├── for_loop.js ├── hidden_class.js ├── inner_function.js ├── is_object_empty.js ├── iterate_object.js ├── map_loop.js ├── new_array.js ├── new_promise.js ├── obj_index.js ├── random_int.js ├── regex_method.js ├── sample_from_array.js ├── start_with.js ├── str_concat.js ├── str_to_int_number.js ├── try_catch.js ├── uniq_str_array.js ├── util.format.js └── yield_vs_closure.js ├── benchmark_template.js ├── compile.js ├── gen_readme.js ├── package.json ├── run_benchmark.js └── utils.js /.github/workflows/update_benchmark.yml: -------------------------------------------------------------------------------- 1 | name: update_benchmark 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - master 8 | 9 | jobs: 10 | # Set the job key. The key is displayed as the job name 11 | # when a job name is not provided 12 | update_benchmark: 13 | # Name the Job 14 | name: update benchmark results 15 | # Set the type of machine to run on 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | # Checks out a copy of your repository on the ubuntu-latest machine 20 | - uses: actions/checkout@v2 21 | - uses: actions/setup-node@v2 22 | with: 23 | node-version: "19" 24 | - run: npm i 25 | - name: system build deps 26 | run: sudo apt-get install -y build-essential gcc-multilib 27 | - name: install quickjs latest version 28 | run: | 29 | git clone https://github.com/bellard/quickjs.git && \ 30 | cd quickjs && \ 31 | make && \ 32 | sudo make install && \ 33 | rm -rf quickjs 34 | - name: run benchmark and generate readme 35 | run: npm run generate 36 | - uses: EndBug/add-and-commit@v7 37 | with: 38 | message: "update benchmark results" 39 | add: "." 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | dist -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 alsotang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fast-js 2 | 3 | :heart_eyes: Writing fast JavaScript on Node.js and QuickJS 4 | 5 | Here are many benchmark results produced from Node.js and QuickJS runtime. 6 | 7 | # env 8 | 9 | - Node.js: 19.7.0 10 | - v8: 10.8.168.25-node.11 11 | - QuickJS: QuickJS version 2021-03-27 12 | 13 | # benchmark 14 | 15 | 16 | [arguments_to_array.js](benchmark/arguments_to_array.js) 17 | 18 | 19 | Node.js output: 20 | 21 | ``` 22 | [].slice.apply x 22,420,279 ops/sec ±0.30% (97 runs sampled) 23 | [].slice.call x 22,448,514 ops/sec ±0.63% (94 runs sampled) 24 | Array.prototype.slice.apply x 22,692,906 ops/sec ±0.20% (96 runs sampled) 25 | Array.prototype.slice.call x 22,427,875 ops/sec ±0.47% (96 runs sampled) 26 | lodash.toArray x 10,996,166 ops/sec ±0.16% (98 runs sampled) 27 | Fastest is Array.prototype.slice.apply 28 | ``` 29 | 30 | QuickJS output: 31 | 32 | ``` 33 | [].slice.apply x 940,288 ops/sec ±0.11% (66 runs sampled) 34 | [].slice.call x 965,813 ops/sec ±0.14% (67 runs sampled) 35 | Array.prototype.slice.apply x 982,746 ops/sec ±0.16% (67 runs sampled) 36 | Array.prototype.slice.call x 1,018,669 ops/sec ±0.14% (67 runs sampled) 37 | lodash.toArray x 417,679 ops/sec ±0.16% (59 runs sampled) 38 | Fastest is Array.prototype.slice.call 39 | ``` 40 | 41 | 42 | 43 | [array_from_set.js](benchmark/array_from_set.js) 44 | 45 | 46 | Node.js output: 47 | 48 | ``` 49 | Array.from x 3,308,311 ops/sec ±0.30% (97 runs sampled) 50 | Set#forEach x 682,543 ops/sec ±0.26% (95 runs sampled) 51 | arr[index] x 790,529 ops/sec ±0.12% (98 runs sampled) 52 | Fastest is Array.from 53 | ``` 54 | 55 | QuickJS output: 56 | 57 | ``` 58 | Array.from x 166,771 ops/sec ±0.41% (65 runs sampled) 59 | Set#forEach x 70,539 ops/sec ±0.35% (64 runs sampled) 60 | arr[index] x 121,853 ops/sec ±0.40% (64 runs sampled) 61 | Fastest is Array.from 62 | ``` 63 | 64 | 65 | 66 | [clone_object.js](benchmark/clone_object.js) 67 | 68 | 69 | Node.js output: 70 | 71 | ``` 72 | JSON.parse(JSON.stringify) x 342,741 ops/sec ±0.24% (95 runs sampled) 73 | lodash.cloneDeep x 277,438 ops/sec ±0.41% (90 runs sampled) 74 | lodash.clone. this is shallow clone x 2,263,402 ops/sec ±0.08% (90 runs sampled) 75 | Fastest is lodash.clone. this is shallow clone 76 | ``` 77 | 78 | QuickJS output: 79 | 80 | ``` 81 | JSON.parse(JSON.stringify) x 124,549 ops/sec ±0.20% (61 runs sampled) 82 | lodash.cloneDeep x 21,672 ops/sec ±0.91% (67 runs sampled) 83 | lodash.clone. this is shallow clone x 252,017 ops/sec ±0.19% (67 runs sampled) 84 | Fastest is lodash.clone. this is shallow clone 85 | ``` 86 | 87 | 88 | 89 | [co_wrap_vs_bluebrid_coroutine.js](benchmark/co_wrap_vs_bluebrid_coroutine.js) 90 | 91 | 92 | Node.js output: 93 | 94 | ``` 95 | co.wrap x 9,849 ops/sec ±0.44% (87 runs sampled) 96 | bluebird.coroutine x 82.25 ops/sec ±0.48% (79 runs sampled) 97 | Fastest is co.wrap 98 | ``` 99 | 100 | QuickJS output: 101 | 102 | ``` 103 | co.wrap x 353 ops/sec ±0.19% (63 runs sampled) 104 | bluebird.coroutine x 76.83 ops/sec ±0.15% (62 runs sampled) 105 | Fastest is co.wrap 106 | ``` 107 | 108 | 109 | 110 | [for_loop.js](benchmark/for_loop.js) 111 | 112 | 113 | Node.js output: 114 | 115 | ``` 116 | normal for loop. i < arr.length x 16,533 ops/sec ±0.06% (98 runs sampled) 117 | normal for loop. cache arr.length x 16,539 ops/sec ±0.03% (100 runs sampled) 118 | native forEach x 951 ops/sec ±0.13% (94 runs sampled) 119 | lodash.forEach x 1,044 ops/sec ±0.25% (96 runs sampled) 120 | Fastest is normal for loop. cache arr.length,normal for loop. i < arr.length 121 | ``` 122 | 123 | QuickJS output: 124 | 125 | ``` 126 | normal for loop. i < arr.length x 323 ops/sec ±0.15% (63 runs sampled) 127 | normal for loop. cache arr.length x 422 ops/sec ±0.17% (65 runs sampled) 128 | native forEach x 236 ops/sec ±0.19% (61 runs sampled) 129 | lodash.forEach x 165 ops/sec ±0.22% (61 runs sampled) 130 | Fastest is normal for loop. cache arr.length 131 | ``` 132 | 133 | 134 | 135 | [hidden_class.js](benchmark/hidden_class.js) 136 | 137 | 138 | Node.js output: 139 | 140 | ``` 141 | withoutHiddenClass x 593,008,455 ops/sec ±0.33% (96 runs sampled) 142 | withHiddenClass x 592,211,394 ops/sec ±0.09% (97 runs sampled) 143 | Fastest is withHiddenClass 144 | ``` 145 | 146 | QuickJS output: 147 | 148 | ``` 149 | withoutHiddenClass x 1,558,953 ops/sec ±0.27% (67 runs sampled) 150 | withHiddenClass x 1,450,963 ops/sec ±0.31% (67 runs sampled) 151 | Fastest is withoutHiddenClass 152 | ``` 153 | 154 | 155 | 156 | [inner_function.js](benchmark/inner_function.js) 157 | 158 | 159 | Node.js output: 160 | 161 | ``` 162 | inner x 588,177,701 ops/sec ±0.29% (95 runs sampled) 163 | outter x 590,210,535 ops/sec ±0.11% (98 runs sampled) 164 | Fastest is outter 165 | ``` 166 | 167 | QuickJS output: 168 | 169 | ``` 170 | inner x 2,111,907 ops/sec ±0.15% (66 runs sampled) 171 | outter x 6,111,227 ops/sec ±0.23% (55 runs sampled) 172 | Fastest is outter 173 | ``` 174 | 175 | 176 | 177 | [is_object_empty.js](benchmark/is_object_empty.js) 178 | 179 | 180 | Node.js output: 181 | 182 | ``` 183 | Object.keys().length === 0 x 102,582,561 ops/sec ±0.76% (93 runs sampled) 184 | lodash.isEmpty(obj) x 16,100,344 ops/sec ±0.23% (98 runs sampled) 185 | JSON.stringify(obj) == {} x 9,652,633 ops/sec ±0.20% (96 runs sampled) 186 | Array.length === 0 x 590,246,087 ops/sec ±0.31% (98 runs sampled) 187 | lodash.isEmpty(arr) x 16,212,321 ops/sec ±0.21% (98 runs sampled) 188 | Fastest is Array.length === 0 189 | ``` 190 | 191 | QuickJS output: 192 | 193 | ``` 194 | Object.keys().length === 0 x 3,516,456 ops/sec ±0.19% (65 runs sampled) 195 | lodash.isEmpty(obj) x 848,532 ops/sec ±0.17% (60 runs sampled) 196 | JSON.stringify(obj) == {} x 1,044,491 ops/sec ±0.09% (68 runs sampled) 197 | Array.length === 0 x 8,692,101 ops/sec ±0.45% (67 runs sampled) 198 | lodash.isEmpty(arr) x 989,557 ops/sec ±0.14% (66 runs sampled) 199 | Fastest is Array.length === 0 200 | ``` 201 | 202 | 203 | 204 | [iterate_object.js](benchmark/iterate_object.js) 205 | 206 | 207 | Node.js output: 208 | 209 | ``` 210 | for .. in .. x 88,202,202 ops/sec ±0.55% (92 runs sampled) 211 | Object.keys x 10,296,848 ops/sec ±0.22% (94 runs sampled) 212 | lodash.forEach x 6,293,506 ops/sec ±0.25% (98 runs sampled) 213 | Fastest is for .. in .. 214 | ``` 215 | 216 | QuickJS output: 217 | 218 | ``` 219 | for .. in .. x 718,486 ops/sec ±0.29% (66 runs sampled) 220 | Object.keys x 516,502 ops/sec ±0.14% (67 runs sampled) 221 | lodash.forEach x 264,105 ops/sec ±0.13% (66 runs sampled) 222 | Fastest is for .. in .. 223 | ``` 224 | 225 | 226 | 227 | [map_loop.js](benchmark/map_loop.js) 228 | 229 | 230 | Node.js output: 231 | 232 | ``` 233 | normal loop. use push x 494 ops/sec ±0.67% (88 runs sampled) 234 | normal loop. use index x 522 ops/sec ±0.54% (91 runs sampled) 235 | new Array(arr.length) x 1,474 ops/sec ±0.23% (94 runs sampled) 236 | native map x 526 ops/sec ±0.53% (91 runs sampled) 237 | lodash.forEach x 622 ops/sec ±0.62% (91 runs sampled) 238 | Fastest is new Array(arr.length) 239 | ``` 240 | 241 | QuickJS output: 242 | 243 | ``` 244 | normal loop. use push x 66.62 ops/sec ±0.71% (50 runs sampled) 245 | normal loop. use index x 127 ops/sec ±0.28% (59 runs sampled) 246 | new Array(arr.length) x 125 ops/sec ±0.28% (58 runs sampled) 247 | native map x 104 ops/sec ±0.19% (60 runs sampled) 248 | lodash.forEach x 107 ops/sec ±0.29% (56 runs sampled) 249 | Fastest is normal loop. use index 250 | ``` 251 | 252 | 253 | 254 | [new_array.js](benchmark/new_array.js) 255 | 256 | 257 | Node.js output: 258 | 259 | ``` 260 | new Array() x 588,630,799 ops/sec ±0.53% (92 runs sampled) 261 | [] x 591,918,305 ops/sec ±0.10% (98 runs sampled) 262 | [] and assign x 52.80 ops/sec ±1.42% (68 runs sampled) 263 | new Array(length) and assign x 316 ops/sec ±0.63% (89 runs sampled) 264 | Fastest is [] 265 | ``` 266 | 267 | QuickJS output: 268 | 269 | ``` 270 | new Array() x 3,210,533 ops/sec ±0.20% (66 runs sampled) 271 | [] x 5,612,233 ops/sec ±0.19% (66 runs sampled) 272 | [] and assign x 34.52 ops/sec ±0.56% (46 runs sampled) 273 | new Array(length) and assign x 35.21 ops/sec ±0.33% (47 runs sampled) 274 | Fastest is [] 275 | ``` 276 | 277 | 278 | 279 | [new_promise.js](benchmark/new_promise.js) 280 | 281 | 282 | Node.js output: 283 | 284 | ``` 285 | native `new promise` x 8,698,650 ops/sec ±0.67% (88 runs sampled) 286 | bluebird `new promise` x 15,615 ops/sec ±0.39% (84 runs sampled) 287 | native promise.resolve x 11,007,499 ops/sec ±0.53% (86 runs sampled) 288 | bluebird promise.resolve x 15,961 ops/sec ±0.31% (89 runs sampled) 289 | co x 494,601 ops/sec ±0.85% (88 runs sampled) 290 | Fastest is native promise.resolve 291 | ``` 292 | 293 | QuickJS output: 294 | 295 | ``` 296 | native `new promise` x 295,727 ops/sec ±0.22% (59 runs sampled) 297 | bluebird `new promise` x 88,457 ops/sec ±0.14% (64 runs sampled) 298 | native promise.resolve x 300,930 ops/sec ±0.20% (64 runs sampled) 299 | bluebird promise.resolve x 104,206 ops/sec ±0.19% (64 runs sampled) 300 | co x 110,070 ops/sec ±0.69% (60 runs sampled) 301 | Fastest is native promise.resolve 302 | ``` 303 | 304 | 305 | 306 | [obj_index.js](benchmark/obj_index.js) 307 | 308 | 309 | Node.js output: 310 | 311 | ``` 312 | number index `1` x 249 ops/sec ±0.02% (90 runs sampled) 313 | string index `1` x 249 ops/sec ±0.02% (90 runs sampled) 314 | dot index `b` x 249 ops/sec ±0.01% (90 runs sampled) 315 | string index `b` x 249 ops/sec ±0.01% (90 runs sampled) 316 | Fastest is string index `b`,dot index `b`,number index `1` 317 | ``` 318 | 319 | QuickJS output: 320 | 321 | ``` 322 | number index `1` x 3.83 ops/sec ±0.44% (14 runs sampled) 323 | string index `1` x 3.09 ops/sec ±0.19% (12 runs sampled) 324 | dot index `b` x 4.38 ops/sec ±0.14% (15 runs sampled) 325 | string index `b` x 2.64 ops/sec ±0.36% (11 runs sampled) 326 | Fastest is dot index `b` 327 | ``` 328 | 329 | 330 | 331 | [random_int.js](benchmark/random_int.js) 332 | 333 | 334 | Node.js output: 335 | 336 | ``` 337 | Math.random % range x 88,586,385 ops/sec ±0.30% (95 runs sampled) 338 | lodash.random x 58,950,769 ops/sec ±0.25% (94 runs sampled) 339 | Fastest is Math.random % range 340 | ``` 341 | 342 | QuickJS output: 343 | 344 | ``` 345 | Math.random % range x 5,507,277 ops/sec ±0.32% (66 runs sampled) 346 | lodash.random x 1,728,663 ops/sec ±0.14% (67 runs sampled) 347 | Fastest is Math.random % range 348 | ``` 349 | 350 | 351 | 352 | [regex_method.js](benchmark/regex_method.js) 353 | 354 | 355 | Node.js output: 356 | 357 | ``` 358 | String.match x 22,361,873 ops/sec ±0.23% (97 runs sampled) 359 | Regex.exec x 24,495,232 ops/sec ±0.26% (95 runs sampled) 360 | String.search x 32,979,505 ops/sec ±0.12% (98 runs sampled) 361 | test x 41,783,349 ops/sec ±0.93% (98 runs sampled) 362 | Fastest is test 363 | ``` 364 | 365 | QuickJS output: 366 | 367 | ``` 368 | String.match x 851,659 ops/sec ±0.13% (67 runs sampled) 369 | Regex.exec x 1,054,563 ops/sec ±0.18% (66 runs sampled) 370 | String.search x 845,103 ops/sec ±0.17% (60 runs sampled) 371 | test x 969,057 ops/sec ±0.27% (67 runs sampled) 372 | Fastest is Regex.exec 373 | ``` 374 | 375 | 376 | 377 | [sample_from_array.js](benchmark/sample_from_array.js) 378 | 379 | 380 | Node.js output: 381 | 382 | ``` 383 | Math.random % arr.length x 77,100,706 ops/sec ±0.40% (94 runs sampled) 384 | lodash.sample x 75,686,572 ops/sec ±0.22% (96 runs sampled) 385 | Fastest is Math.random % arr.length 386 | ``` 387 | 388 | QuickJS output: 389 | 390 | ``` 391 | Math.random % arr.length x 4,707,656 ops/sec ±0.11% (67 runs sampled) 392 | lodash.sample x 2,770,724 ops/sec ±0.28% (67 runs sampled) 393 | Fastest is Math.random % arr.length 394 | ``` 395 | 396 | 397 | 398 | [start_with.js](benchmark/start_with.js) 399 | 400 | 401 | Node.js output: 402 | 403 | ``` 404 | regex /^ab/ x 36,988,104 ops/sec ±0.24% (95 runs sampled) 405 | indexOf === 0 x 591,613,760 ops/sec ±0.16% (96 runs sampled) 406 | lodash.startsWith x 586,243,127 ops/sec ±0.15% (94 runs sampled) 407 | Fastest is indexOf === 0 408 | ``` 409 | 410 | QuickJS output: 411 | 412 | ``` 413 | regex /^ab/ x 839,571 ops/sec ±0.18% (67 runs sampled) 414 | indexOf === 0 x 5,899,069 ops/sec ±0.24% (53 runs sampled) 415 | lodash.startsWith x 1,990,439 ops/sec ±0.18% (66 runs sampled) 416 | Fastest is indexOf === 0 417 | ``` 418 | 419 | 420 | 421 | [str_concat.js](benchmark/str_concat.js) 422 | 423 | 424 | Node.js output: 425 | 426 | ``` 427 | + x 589,022,865 ops/sec ±0.49% (95 runs sampled) 428 | += x 590,854,420 ops/sec ±0.13% (96 runs sampled) 429 | arr.join('') x 6,150,158 ops/sec ±0.34% (97 runs sampled) 430 | str.concat x 590,290,792 ops/sec ±0.12% (97 runs sampled) 431 | Fastest is +=,str.concat,+ 432 | ``` 433 | 434 | QuickJS output: 435 | 436 | ``` 437 | + x 4,568,997 ops/sec ±1.29% (64 runs sampled) 438 | += x 3,306,686 ops/sec ±0.93% (59 runs sampled) 439 | arr.join('') x 1,306,007 ops/sec ±0.98% (60 runs sampled) 440 | str.concat x 4,083,164 ops/sec ±0.92% (65 runs sampled) 441 | Fastest is + 442 | ``` 443 | 444 | 445 | 446 | [str_to_int_number.js](benchmark/str_to_int_number.js) 447 | 448 | 449 | Node.js output: 450 | 451 | ``` 452 | +str x 591,555,625 ops/sec ±0.16% (97 runs sampled) 453 | ~~str x 591,189,893 ops/sec ±0.09% (98 runs sampled) 454 | Number(str) x 590,851,241 ops/sec ±0.19% (97 runs sampled) 455 | parseInt(str) x 215,256,987 ops/sec ±0.44% (94 runs sampled) 456 | parseInt(str, 10) x 203,432,942 ops/sec ±0.37% (93 runs sampled) 457 | str - 0 x 591,344,941 ops/sec ±0.14% (97 runs sampled) 458 | Fastest is ~~str,str - 0,Number(str) 459 | ``` 460 | 461 | QuickJS output: 462 | 463 | ``` 464 | +str x 6,245,957 ops/sec ±0.12% (64 runs sampled) 465 | ~~str x 5,853,389 ops/sec ±0.15% (65 runs sampled) 466 | Number(str) x 4,878,886 ops/sec ±0.34% (45 runs sampled) 467 | parseInt(str) x 4,762,535 ops/sec ±0.22% (44 runs sampled) 468 | parseInt(str, 10) x 5,173,711 ops/sec ±0.12% (66 runs sampled) 469 | str - 0 x 5,991,062 ops/sec ±0.15% (67 runs sampled) 470 | Fastest is +str 471 | ``` 472 | 473 | 474 | 475 | [try_catch.js](benchmark/try_catch.js) 476 | 477 | 478 | Node.js output: 479 | 480 | ``` 481 | JSON.parse with try catch x 57,243 ops/sec ±0.35% (95 runs sampled) 482 | JSON.parse without try catch x 57,092 ops/sec ±0.10% (94 runs sampled) 483 | for loop with try catch x 531 ops/sec ±0.74% (87 runs sampled) 484 | for loop without try catch x 531 ops/sec ±0.70% (87 runs sampled) 485 | Fastest is JSON.parse with try catch 486 | ``` 487 | 488 | QuickJS output: 489 | 490 | ``` 491 | JSON.parse with try catch x 11,669 ops/sec ±0.21% (61 runs sampled) 492 | JSON.parse without try catch x 11,718 ops/sec ±0.17% (62 runs sampled) 493 | for loop with try catch x 356 ops/sec ±0.22% (65 runs sampled) 494 | for loop without try catch x 353 ops/sec ±0.31% (64 runs sampled) 495 | Fastest is JSON.parse without try catch 496 | ``` 497 | 498 | 499 | 500 | [uniq_str_array.js](benchmark/uniq_str_array.js) 501 | 502 | 503 | Node.js output: 504 | 505 | ``` 506 | obj[key] = true x 44,707 ops/sec ±0.29% (95 runs sampled) 507 | lodash.uniq x 40,612 ops/sec ±0.11% (96 runs sampled) 508 | Set x 42,079 ops/sec ±0.26% (97 runs sampled) 509 | Fastest is obj[key] = true 510 | ``` 511 | 512 | QuickJS output: 513 | 514 | ``` 515 | obj[key] = true x 12,533 ops/sec ±0.37% (65 runs sampled) 516 | lodash.uniq x 7,176 ops/sec ±12.43% (41 runs sampled) 517 | Set x 7,097 ops/sec ±13.45% (40 runs sampled) 518 | Fastest is obj[key] = true 519 | ``` 520 | 521 | 522 | 523 | [util.format.js](benchmark/util.format.js) 524 | 525 | 526 | Node.js output: 527 | 528 | ``` 529 | util.format x 870,369 ops/sec ±0.27% (96 runs sampled) 530 | str.replace x 8,062,080 ops/sec ±0.25% (95 runs sampled) 531 | custom fn x 590,969,343 ops/sec ±0.10% (97 runs sampled) 532 | Fastest is custom fn 533 | ``` 534 | 535 | QuickJS output: 536 | 537 | ``` 538 | util.format x 149,700 ops/sec ±0.15% (66 runs sampled) 539 | str.replace x 1,688,493 ops/sec ±0.60% (66 runs sampled) 540 | custom fn x 3,810,283 ops/sec ±0.28% (65 runs sampled) 541 | Fastest is custom fn 542 | ``` 543 | 544 | 545 | 546 | [yield_vs_closure.js](benchmark/yield_vs_closure.js) 547 | 548 | 549 | Node.js output: 550 | 551 | ``` 552 | yield x 399 ops/sec ±0.30% (91 runs sampled) 553 | yield* x 189 ops/sec ±0.86% (87 runs sampled) 554 | closure x 2,960 ops/sec ±0.03% (99 runs sampled) 555 | Fastest is closure 556 | ``` 557 | 558 | QuickJS output: 559 | 560 | ``` 561 | yield x 33.38 ops/sec ±0.26% (45 runs sampled) 562 | yield* x 25.81 ops/sec ±0.27% (46 runs sampled) 563 | closure x 186 ops/sec ±0.29% (60 runs sampled) 564 | Fastest is closure 565 | ``` 566 | 567 | 568 | # contribute 569 | 570 | 1. add your test to the `./benchmark` dir 571 | 1. run `$ node run_benchmark.js only_file_name_without_dir.js` to run the benchmark 572 | 1. send a pr to me, I would add the result to README.md 573 | 574 | # complete build 575 | 576 | see [.github/workflows/update_benchmark.yml](.github/workflows/update_benchmark.yml) 577 | -------------------------------------------------------------------------------- /README.template.md: -------------------------------------------------------------------------------- 1 | # fast-js 2 | 3 | :heart_eyes: Writing fast JavaScript on Node.js and QuickJS 4 | 5 | Here are many benchmark results produced from Node.js and QuickJS runtime. 6 | 7 | # env 8 | 9 | - Node.js: <%= node_version %> 10 | - v8: <%= v8_version %> 11 | - QuickJS: <%= quickjs_version %> 12 | 13 | # benchmark 14 | 15 | <%= benchmark_result %> 16 | 17 | # contribute 18 | 19 | 1. add your test to the `./benchmark` dir 20 | 1. run `$ node run_benchmark.js only_file_name_without_dir.js` to run the benchmark 21 | 1. send a pr to me, I would add the result to README.md 22 | 23 | # complete build 24 | 25 | see [.github/workflows/update_benchmark.yml](.github/workflows/update_benchmark.yml) 26 | -------------------------------------------------------------------------------- /benchmark/arguments_to_array.js: -------------------------------------------------------------------------------- 1 | const _ = require("lodash"); 2 | 3 | var args = (function () { 4 | return arguments; 5 | })(1, 2, 3, 4, 5); 6 | 7 | const cases = [ 8 | { 9 | name: "[].slice.apply", 10 | fn: function () { 11 | /* 12 | every invode would create a literal empty Array 13 | */ 14 | var a = [].slice.apply(args); 15 | }, 16 | }, 17 | { 18 | name: "[].slice.call", 19 | fn: function () { 20 | var a = [].slice.call(args); 21 | }, 22 | }, 23 | { 24 | name: "Array.prototype.slice.apply", 25 | fn: function () { 26 | var a = Array.prototype.slice.apply(args); 27 | }, 28 | }, 29 | { 30 | name: "Array.prototype.slice.call", 31 | fn: function () { 32 | var a = Array.prototype.slice.call(args); 33 | }, 34 | }, 35 | { 36 | name: "lodash.toArray", 37 | fn: function () { 38 | /* 39 | use while loop 40 | */ 41 | var a = _.toArray(args); 42 | }, 43 | }, 44 | ]; 45 | 46 | exports = module.exports = { cases }; 47 | -------------------------------------------------------------------------------- /benchmark/array_from_set.js: -------------------------------------------------------------------------------- 1 | var _ = require("lodash"); 2 | var testSet = new Set(_.range(100)); 3 | 4 | const cases = [ 5 | { 6 | name: "Array.from", 7 | fn: function () { 8 | Array.from(testSet); 9 | }, 10 | }, 11 | { 12 | name: "Set#forEach", 13 | fn: function () { 14 | var arr = []; 15 | 16 | testSet.forEach(function (v) { 17 | arr.push(v); 18 | }); 19 | }, 20 | }, 21 | { 22 | name: "arr[index]", 23 | fn: function () { 24 | var arr = new Array(testSet.size); 25 | 26 | var index = 0; 27 | testSet.forEach(function (v) { 28 | arr[index++] = v; 29 | }); 30 | }, 31 | }, 32 | ]; 33 | 34 | exports = module.exports = { cases }; 35 | -------------------------------------------------------------------------------- /benchmark/clone_object.js: -------------------------------------------------------------------------------- 1 | var _ = require("lodash"); 2 | 3 | var obj1 = { 4 | a: { 5 | b: { 6 | c: { 7 | d: { 8 | e: { 9 | name: "alsotang", 10 | at: "china", 11 | }, 12 | }, 13 | }, 14 | }, 15 | }, 16 | }; 17 | 18 | // !!!!NOTICE 19 | // According to what kind of element you want, 20 | // result of the three is not always the same. 21 | 22 | /* 23 | the JSON approach doesn't work with objects well (like date, regexp, string object, functions). 24 | Lodash handles them as well as typed arrays and array buffers. 25 | -- by @jdalton 26 | */ 27 | 28 | const cases = [ 29 | { 30 | name: "JSON.parse(JSON.stringify)", 31 | fn: function () { 32 | var obj2 = JSON.parse(JSON.stringify(obj1)); 33 | }, 34 | }, 35 | { 36 | name: "lodash.cloneDeep", 37 | fn: function () { 38 | var obj2 = _.cloneDeep(obj1); 39 | }, 40 | }, 41 | { 42 | name: "lodash.clone. this is shallow clone", 43 | fn: function () { 44 | var obj2 = _.clone(obj1); 45 | }, 46 | }, 47 | ]; 48 | 49 | exports = module.exports = { cases }; 50 | -------------------------------------------------------------------------------- /benchmark/co_wrap_vs_bluebrid_coroutine.js: -------------------------------------------------------------------------------- 1 | var co = require("co"); 2 | var bluebird = require("bluebird"); 3 | 4 | const cases = [ 5 | { 6 | name: "co.wrap", 7 | fn: function (next) { 8 | co.wrap(function* () { 9 | for (var i = 0; i < 1000; i++) { 10 | yield Promise.resolve(1); 11 | } 12 | })().then(next); 13 | }, 14 | }, 15 | { 16 | name: "bluebird.coroutine", 17 | fn: function (next) { 18 | bluebird 19 | .coroutine(function* () { 20 | for (var i = 0; i < 1000; i++) { 21 | yield Promise.resolve(1); 22 | } 23 | })() 24 | .then(next); 25 | }, 26 | }, 27 | ]; 28 | 29 | exports = module.exports = { cases }; 30 | -------------------------------------------------------------------------------- /benchmark/for_loop.js: -------------------------------------------------------------------------------- 1 | var _ = require("lodash"); 2 | 3 | var arr = _.range(100000); 4 | 5 | const cases = [ 6 | { 7 | name: "normal for loop. i < arr.length", 8 | fn: function () { 9 | for (var i = 0; i < arr.length; i++) { 10 | arr[i] + 1; 11 | } 12 | }, 13 | }, 14 | { 15 | name: "normal for loop. cache arr.length", 16 | fn: function () { 17 | for (var i = 0, len = arr.length; i < len; i++) { 18 | arr[i] + 1; 19 | } 20 | }, 21 | }, 22 | { 23 | name: "native forEach", 24 | fn: function () { 25 | arr.forEach(function (item) { 26 | item + 1; 27 | }); 28 | }, 29 | }, 30 | { 31 | name: "lodash.forEach", 32 | fn: function () { 33 | _.forEach(arr, function (item) { 34 | item + 1; 35 | }); 36 | }, 37 | }, 38 | ]; 39 | 40 | exports = module.exports = { cases }; 41 | -------------------------------------------------------------------------------- /benchmark/hidden_class.js: -------------------------------------------------------------------------------- 1 | // perhaps microbenchmarking here make no sense 2 | // can anyone provide a more meaningful cases? 3 | 4 | // about hidden class: https://developers.google.com/v8/design#prop_access 5 | function withoutHiddenClass() {} 6 | withoutHiddenClass.prototype.timeout = timeout; 7 | withoutHiddenClass.prototype.url = url; 8 | withoutHiddenClass.prototype.type = type; 9 | 10 | function withHiddenClass() { 11 | this._timeout = 0; 12 | this._url = ""; 13 | this._type = ""; 14 | } 15 | withHiddenClass.prototype.timeout = timeout; 16 | withHiddenClass.prototype.url = url; 17 | withHiddenClass.prototype.type = type; 18 | 19 | function timeout(timeout) { 20 | this._timeout = timeout; 21 | } 22 | 23 | function url(url) { 24 | this._url = url; 25 | } 26 | 27 | function type(type) { 28 | this._type = type; 29 | } 30 | 31 | const cases = [ 32 | { 33 | name: "withoutHiddenClass", 34 | fn: function () { 35 | var obj = new withoutHiddenClass(); 36 | obj.timeout(1); 37 | obj.url("google.com"); 38 | obj.type("get"); 39 | }, 40 | }, 41 | { 42 | name: "withHiddenClass", 43 | fn: function () { 44 | var obj = new withHiddenClass(); 45 | obj.timeout(1); 46 | obj.url("google.com"); 47 | obj.type("get"); 48 | }, 49 | }, 50 | ]; 51 | 52 | exports = module.exports = { cases }; 53 | -------------------------------------------------------------------------------- /benchmark/inner_function.js: -------------------------------------------------------------------------------- 1 | function innerFunc(num) { 2 | function add100(_num) { 3 | return _num + 100; 4 | } 5 | return add100(num); 6 | } 7 | 8 | function add100_2(num) { 9 | return num + 100; 10 | } 11 | function outterFunc(num) { 12 | return add100_2(num); 13 | } 14 | 15 | const cases = [ 16 | { 17 | name: "inner", 18 | fn: function () { 19 | /* 20 | inner function would be created in each invoke 21 | so this is slow 22 | */ 23 | innerFunc(1); 24 | }, 25 | }, 26 | { 27 | name: "outter", 28 | fn: function () { 29 | outterFunc(1); 30 | }, 31 | }, 32 | ]; 33 | 34 | exports = module.exports = { cases }; 35 | -------------------------------------------------------------------------------- /benchmark/is_object_empty.js: -------------------------------------------------------------------------------- 1 | var _ = require("lodash"); 2 | 3 | var emptyObj = {}; 4 | var emptyArr = []; 5 | 6 | const cases = [ 7 | { 8 | name: "Object.keys().length === 0", 9 | fn: function () { 10 | var b = Object.keys(emptyObj).length === 0; 11 | }, 12 | }, 13 | { 14 | name: "lodash.isEmpty(obj)", 15 | fn: function () { 16 | var b = _.isEmpty(emptyObj); 17 | }, 18 | }, 19 | { 20 | name: "JSON.stringify(obj) == {}", 21 | fn: function () { 22 | var b = JSON.stringify(emptyObj) == "{}"; 23 | }, 24 | }, 25 | { 26 | name: "Array.length === 0", 27 | fn: function () { 28 | var b = emptyArr.length; 29 | }, 30 | }, 31 | { 32 | name: "lodash.isEmpty(arr)", 33 | fn: function () { 34 | var b = _.isEmpty(emptyArr); 35 | }, 36 | }, 37 | ]; 38 | 39 | exports = module.exports = { cases }; 40 | -------------------------------------------------------------------------------- /benchmark/iterate_object.js: -------------------------------------------------------------------------------- 1 | var _ = require("lodash"); 2 | 3 | var obj = { a: 1, b: 2, c: 3, d: 4, e: 5, "-": 6 }; 4 | 5 | // maybe we should check .hasOwnProperty here 6 | 7 | const cases = [ 8 | { 9 | name: "for .. in ..", 10 | fn: function () { 11 | for (var k in obj) { 12 | obj[k]; 13 | } 14 | }, 15 | }, 16 | { 17 | name: "Object.keys", 18 | fn: function () { 19 | Object.keys(obj).forEach(function (key) { 20 | obj[key]; 21 | }); 22 | }, 23 | }, 24 | { 25 | name: "lodash.forEach", 26 | fn: function () { 27 | _.forEach(obj, function (value, key) { 28 | value; 29 | }); 30 | }, 31 | }, 32 | ]; 33 | 34 | exports = module.exports = { cases }; 35 | -------------------------------------------------------------------------------- /benchmark/map_loop.js: -------------------------------------------------------------------------------- 1 | var _ = require("lodash"); 2 | 3 | var arr = _.range(100000); 4 | 5 | function plusOne(num) { 6 | return num + 1; 7 | } 8 | 9 | const cases = [ 10 | { 11 | name: "normal loop. use push", 12 | fn: function () { 13 | var result = []; 14 | for (var i = 0; i < arr.length; i++) { 15 | result.push(plusOne(arr[i])); 16 | } 17 | }, 18 | }, 19 | { 20 | name: "normal loop. use index", 21 | fn: function () { 22 | var result = []; 23 | for (var i = 0; i < arr.length; i++) { 24 | result[i] = plusOne(arr[i]); 25 | } 26 | }, 27 | }, 28 | { 29 | name: "new Array(arr.length)", 30 | fn: function () { 31 | // avoid dynamic memory alloc when array grows 32 | var result = new Array(arr.length); 33 | for (var i = 0; i < arr.length; i++) { 34 | result[i] = plusOne(arr[i]); 35 | } 36 | }, 37 | }, 38 | { 39 | name: "native map", 40 | fn: function () { 41 | var result = arr.map(function (item) { 42 | return plusOne(item); 43 | }); 44 | }, 45 | }, 46 | { 47 | name: "lodash.forEach", 48 | fn: function () { 49 | var result = _.map(arr, function (item) { 50 | return plusOne(item); 51 | }); 52 | }, 53 | }, 54 | ]; 55 | 56 | exports = module.exports = { cases }; 57 | -------------------------------------------------------------------------------- /benchmark/new_array.js: -------------------------------------------------------------------------------- 1 | /* 2 | http://stackoverflow.com/questions/7375120/why-is-arr-faster-than-arr-new-array 3 | */ 4 | 5 | const cases = [ 6 | { 7 | name: "new Array()", 8 | fn: function () { 9 | var a = new Array(); 10 | }, 11 | }, 12 | { 13 | name: "[]", 14 | fn: function () { 15 | var a = []; 16 | }, 17 | }, 18 | { 19 | name: "[] and assign", 20 | fn: function () { 21 | var a = []; 22 | for (var i = 0; i < 1000000; i++) { 23 | a[i] = i; 24 | } 25 | }, 26 | }, 27 | { 28 | name: "new Array(length) and assign", 29 | fn: function () { 30 | var a = new Array(1000000); 31 | for (var i = 0; i < 1000000; i++) { 32 | a[i] = i; 33 | } 34 | }, 35 | }, 36 | ]; 37 | 38 | exports = module.exports = { cases }; 39 | -------------------------------------------------------------------------------- /benchmark/new_promise.js: -------------------------------------------------------------------------------- 1 | var bluebird = require("bluebird"); 2 | var co = require("co"); 3 | 4 | const cases = [ 5 | { 6 | name: "native `new promise`", 7 | fn: function (next) { 8 | new Promise(function (resolve, reject) { 9 | resolve(1); 10 | }).then(next); 11 | }, 12 | }, 13 | { 14 | name: "bluebird `new promise`", 15 | fn: function (next) { 16 | new bluebird(function (resolve, reject) { 17 | resolve(1); 18 | }).then(next); 19 | }, 20 | }, 21 | { 22 | name: "native promise.resolve", 23 | fn: function (next) { 24 | Promise.resolve(1).then(next); 25 | }, 26 | }, 27 | { 28 | name: "bluebird promise.resolve", 29 | fn: function (next) { 30 | bluebird.resolve(1).then(next); 31 | }, 32 | }, 33 | { 34 | name: "co", 35 | fn: function (next) { 36 | co(function* () { 37 | return 1; 38 | }).then(next); 39 | }, 40 | }, 41 | ]; 42 | 43 | exports = module.exports = { cases }; 44 | -------------------------------------------------------------------------------- /benchmark/obj_index.js: -------------------------------------------------------------------------------- 1 | var obj = { 2 | 1: "a", 3 | b: "c", 4 | }; 5 | 6 | const cases = [ 7 | { 8 | name: "number index `1`", 9 | fn: function () { 10 | for (var i = 0; i < 10000000; i++) { 11 | obj[1]; 12 | } 13 | }, 14 | }, 15 | { 16 | name: "string index `1`", 17 | fn: function () { 18 | for (var i = 0; i < 10000000; i++) { 19 | obj["1"]; 20 | } 21 | }, 22 | }, 23 | { 24 | name: "dot index `b`", 25 | fn: function () { 26 | for (var i = 0; i < 10000000; i++) { 27 | obj.b; 28 | } 29 | }, 30 | }, 31 | { 32 | name: "string index `b`", 33 | fn: function () { 34 | for (var i = 0; i < 10000000; i++) { 35 | obj["b"]; 36 | } 37 | }, 38 | }, 39 | ]; 40 | 41 | exports = module.exports = { cases }; 42 | -------------------------------------------------------------------------------- /benchmark/random_int.js: -------------------------------------------------------------------------------- 1 | var _ = require("lodash"); 2 | 3 | const cases = [ 4 | { 5 | name: "Math.random % range", 6 | fn: function () { 7 | var num = ~~(Math.random() * 1000); 8 | }, 9 | }, 10 | { 11 | name: "lodash.random", 12 | fn: function () { 13 | var num = _.random(1000); 14 | }, 15 | }, 16 | ]; 17 | 18 | exports = module.exports = { cases }; 19 | -------------------------------------------------------------------------------- /benchmark/regex_method.js: -------------------------------------------------------------------------------- 1 | var str = "Teste 1,\n 52,56368,9)7(9.9q7w7,*e*d*/;7.b/,a"; 2 | var regex = /[^aeiou0-9\s]/; 3 | 4 | const cases = [ 5 | { 6 | name: "String.match", 7 | fn: function () { 8 | var m = str.match(regex); 9 | }, 10 | }, 11 | { 12 | name: "Regex.exec", 13 | fn: function () { 14 | var m = regex.exec(str); 15 | }, 16 | }, 17 | { 18 | name: "String.search", 19 | fn: function () { 20 | var m = str.search(regex); 21 | }, 22 | }, 23 | { 24 | name: "test", 25 | fn: function () { 26 | var m = regex.test(str); 27 | }, 28 | }, 29 | ]; 30 | 31 | exports = module.exports = { cases }; 32 | -------------------------------------------------------------------------------- /benchmark/sample_from_array.js: -------------------------------------------------------------------------------- 1 | var _ = require("lodash"); 2 | 3 | var arr = _.range(1000); 4 | 5 | const cases = [ 6 | { 7 | name: "Math.random % arr.length", 8 | fn: function () { 9 | var index = ~~(Math.random() * arr.length); 10 | var sample = arr[index]; 11 | }, 12 | }, 13 | { 14 | name: "lodash.sample", 15 | fn: function () { 16 | var sample = _.sample(arr); 17 | }, 18 | }, 19 | ]; 20 | 21 | exports = module.exports = { cases }; 22 | -------------------------------------------------------------------------------- /benchmark/start_with.js: -------------------------------------------------------------------------------- 1 | var _ = require("lodash"); 2 | 3 | var str = "abcd"; 4 | 5 | const cases = [ 6 | { 7 | name: "regex /^ab/", 8 | fn: function () { 9 | /^ab/.test(str); 10 | }, 11 | }, 12 | { 13 | name: "indexOf === 0", 14 | fn: function () { 15 | str.indexOf("ab") === 0; 16 | }, 17 | }, 18 | { 19 | name: "lodash.startsWith", 20 | fn: function () { 21 | _.startsWith(str, "ab"); 22 | }, 23 | }, 24 | ]; 25 | 26 | exports = module.exports = { cases }; 27 | -------------------------------------------------------------------------------- /benchmark/str_concat.js: -------------------------------------------------------------------------------- 1 | // long length. length === 756 2 | var longstra = 3 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; 4 | // same as above 5 | var longstrb = 6 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; 7 | 8 | // in v8, just use `+`, and everything would be ok 9 | 10 | const cases = [ 11 | { 12 | name: "+", 13 | fn: function () { 14 | var c = longstra + longstrb; 15 | }, 16 | }, 17 | { 18 | name: "+=", 19 | fn: function () { 20 | var c = ""; 21 | c += longstra; 22 | c += longstrb; 23 | }, 24 | }, 25 | { 26 | name: "arr.join('')", 27 | fn: function () { 28 | var c = [longstra, longstrb].join(""); 29 | }, 30 | }, 31 | { 32 | name: "str.concat", 33 | fn: function () { 34 | var c = longstra.concat(longstrb); 35 | }, 36 | }, 37 | ]; 38 | 39 | exports = module.exports = { cases }; 40 | -------------------------------------------------------------------------------- /benchmark/str_to_int_number.js: -------------------------------------------------------------------------------- 1 | var str = "100"; 2 | 3 | const cases = [ 4 | { 5 | name: "+str", 6 | fn: function () { 7 | var a = +str; 8 | }, 9 | }, 10 | { 11 | name: "~~str", 12 | fn: function () { 13 | var a = ~~str; 14 | }, 15 | }, 16 | { 17 | name: "Number(str)", 18 | fn: function () { 19 | var a = Number(str); 20 | }, 21 | }, 22 | { 23 | name: "parseInt(str)", 24 | fn: function () { 25 | var a = parseInt(str); 26 | }, 27 | }, 28 | { 29 | name: "parseInt(str, 10)", 30 | fn: function () { 31 | var a = parseInt(str, 10); 32 | }, 33 | }, 34 | { 35 | name: "str - 0", 36 | fn: function () { 37 | var a = str - 0; 38 | }, 39 | }, 40 | ]; 41 | 42 | exports = module.exports = { cases }; 43 | -------------------------------------------------------------------------------- /benchmark/try_catch.js: -------------------------------------------------------------------------------- 1 | var _ = require("lodash"); 2 | 3 | var obj = _.range(1000); 4 | var str = JSON.stringify(obj); 5 | 6 | const cases = [ 7 | { 8 | name: "JSON.parse with try catch", 9 | fn: function () { 10 | try { 11 | JSON.parse(str); 12 | } catch (e) { 13 | console.error(e); 14 | } 15 | }, 16 | }, 17 | { 18 | name: "JSON.parse without try catch", 19 | fn: function () { 20 | JSON.parse(str); 21 | }, 22 | }, 23 | { 24 | name: "for loop with try catch", 25 | fn: function () { 26 | var arr = []; 27 | try { 28 | for (var i = 0; i < 100000; i++) { 29 | arr[i] = i; 30 | } 31 | } catch (e) { 32 | console.error(e); 33 | } 34 | }, 35 | }, 36 | { 37 | name: "for loop without try catch", 38 | fn: function () { 39 | var arr = []; 40 | for (var i = 0; i < 100000; i++) { 41 | arr[i] = i; 42 | } 43 | }, 44 | }, 45 | ]; 46 | 47 | exports = module.exports = { cases }; 48 | -------------------------------------------------------------------------------- /benchmark/uniq_str_array.js: -------------------------------------------------------------------------------- 1 | var _ = require("lodash"); 2 | 3 | var arr = []; 4 | 5 | for (var i = 0; i < 1000; i++) { 6 | arr[i] = "aa" + String(~~(Math.random() * 100)); 7 | } 8 | 9 | const cases = [ 10 | { 11 | name: "obj[key] = true", 12 | fn: function () { 13 | var _map = {}; 14 | for (var i = 0; i < arr.length; i++) { 15 | _map[arr[i]] = true; 16 | } 17 | var newArr = Object.keys(_map); 18 | }, 19 | }, 20 | { 21 | name: "lodash.uniq", 22 | fn: function () { 23 | var newArr = _.uniq(arr); 24 | }, 25 | }, 26 | { 27 | name: "Set", 28 | fn: function () { 29 | var newArr = Array.from(new Set(arr)); 30 | }, 31 | }, 32 | ]; 33 | 34 | exports = module.exports = { cases }; 35 | -------------------------------------------------------------------------------- /benchmark/util.format.js: -------------------------------------------------------------------------------- 1 | var util = require("util"); 2 | 3 | var longString1 = 4 | "googlegooglegooglegooglegooglegooglegooglegooglegooglegooglegooglegoogle%sgooglegooglegooglegooglegooglegooglegooglegooglegooglegooglegooglegoogle"; 5 | var longString2 = 6 | "googlegooglegooglegooglegooglegooglegooglegooglegooglegooglegooglegoogle$$$googlegooglegooglegooglegooglegooglegooglegooglegooglegooglegooglegoogle"; 7 | var longString3 = function (_replacer) { 8 | return ( 9 | "googlegooglegooglegooglegooglegooglegooglegooglegooglegooglegooglegoogle" + 10 | _replacer + 11 | "googlegooglegooglegooglegooglegooglegooglegooglegooglegooglegooglegoogle" 12 | ); 13 | }; 14 | 15 | var replacer = "tablename"; 16 | 17 | const cases = [ 18 | { 19 | name: "util.format", 20 | fn: function () { 21 | util.format(longString1, replacer); 22 | }, 23 | }, 24 | { 25 | name: "str.replace", 26 | fn: function () { 27 | longString2.replace("$$$", replacer); 28 | }, 29 | }, 30 | { 31 | name: "custom fn", 32 | fn: function () { 33 | longString3(replacer); 34 | }, 35 | }, 36 | ]; 37 | 38 | exports = module.exports = { cases }; 39 | -------------------------------------------------------------------------------- /benchmark/yield_vs_closure.js: -------------------------------------------------------------------------------- 1 | var assert = require("assert"); 2 | var _ = require("lodash"); 3 | 4 | const cases = [ 5 | { 6 | name: "yield", 7 | fn: function () { 8 | var yieldFn = function () { 9 | var num = 0; 10 | return function* () { 11 | while (true) { 12 | yield num++; 13 | } 14 | }; 15 | }; 16 | 17 | var gen = yieldFn()(); 18 | for (var i = 0; i < 100000; i++) { 19 | gen.next(); 20 | } 21 | 22 | assert(gen.next().value == 100000); 23 | }, 24 | }, 25 | { 26 | name: "yield*", 27 | fn: function () { 28 | var arr = _.range(100001); 29 | var yieldFn = function* () { 30 | yield* arr; 31 | }; 32 | 33 | var gen = yieldFn(); 34 | for (var i = 0; i < 100000; i++) { 35 | gen.next(); 36 | } 37 | 38 | assert(gen.next().value == 100000); 39 | }, 40 | }, 41 | { 42 | name: "closure", 43 | fn: function () { 44 | var closureFn = function () { 45 | var i = 0; 46 | return function () { 47 | return i++; 48 | }; 49 | }; 50 | var fn = closureFn(); 51 | for (var i = 0; i < 100000; i++) { 52 | fn(); 53 | } 54 | 55 | assert(fn() == 100000); 56 | }, 57 | }, 58 | ]; 59 | 60 | exports = module.exports = { cases }; 61 | -------------------------------------------------------------------------------- /benchmark_template.js: -------------------------------------------------------------------------------- 1 | const Benchmark = require("benchmark"); 2 | const suite = new Benchmark.Suite(); 3 | const benchmark = require("{{{benchmark_file}}}"); 4 | 5 | benchmark.cases.forEach((caseObj) => { 6 | const isDefer = caseObj.fn.length === 1; 7 | suite.add(caseObj.name, function (deferred) { 8 | if (!isDefer) { 9 | caseObj.fn(); 10 | } else { 11 | caseObj.fn(function () { 12 | deferred.resolve(); 13 | }) 14 | } 15 | }, { 16 | defer: isDefer 17 | }); 18 | }); 19 | 20 | suite 21 | .on("cycle", function (event) { 22 | console.log(String(event.target)); 23 | }) 24 | .on("complete", function () { 25 | console.log("Fastest is " + this.filter("fastest").map("name")); 26 | }); 27 | 28 | suite.run(); 29 | -------------------------------------------------------------------------------- /compile.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const mkdirp = require("mkdirp"); 3 | const pathlib = require("path"); 4 | const browserify = require("browserify"); 5 | const stream = require("stream"); 6 | const { benchmarkDir, nodejsDistDir, quickjsDistDir } = require("./utils"); 7 | 8 | // mkdir dist dir 9 | mkdirp.sync(nodejsDistDir); 10 | mkdirp.sync(quickjsDistDir); 11 | 12 | // shim code for quickjs 13 | const quickjsShimCode = ` 14 | import * as os from 'os'; 15 | globalThis.global = globalThis; 16 | globalThis.setTimeout = globalThis.setTimeout || os.setTimeout; 17 | `; 18 | 19 | async function compile(srcFilename) { 20 | return new Promise((resolve) => { 21 | console.log(`compiling ${srcFilename} ...`); 22 | const benchmarkTemplate = fs.readFileSync( 23 | pathlib.join(__dirname, "benchmark_template.js"), 24 | "utf-8" 25 | ); 26 | 27 | const benchmarkFilePath = pathlib.join(benchmarkDir, srcFilename); 28 | // inject require path 29 | const finalBenchmarkContent = benchmarkTemplate.replace( 30 | "{{{benchmark_file}}}", 31 | benchmarkFilePath 32 | ); 33 | 34 | // construct readable stream for browserify 35 | const readableStream = new stream.Readable(); 36 | readableStream.push(finalBenchmarkContent); 37 | readableStream.push(null); 38 | 39 | // bundle and output to dist 40 | browserify(readableStream, { basedir: benchmarkDir }).bundle(function ( 41 | err, 42 | buf 43 | ) { 44 | const nodejsTarget = pathlib.join(nodejsDistDir, srcFilename); 45 | const quickjsTarget = pathlib.join(quickjsDistDir, srcFilename); 46 | 47 | fs.writeFileSync(nodejsTarget, `${buf.toString()}`); 48 | fs.writeFileSync(quickjsTarget, `${quickjsShimCode} ${buf.toString()}`); 49 | resolve(); 50 | }); 51 | }); 52 | } 53 | 54 | exports = module.exports = compile; 55 | -------------------------------------------------------------------------------- /gen_readme.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | const fs = require("fs"); 4 | const pathlib = require("path"); 5 | const _ = require("lodash"); 6 | const execSync = require("child_process").execSync; 7 | const { run } = require("./run_benchmark"); 8 | const { benchmarkDir, nodejsDistDir, quickjsDistDir } = require("./utils"); 9 | 10 | const readmeTemplate = _.template( 11 | fs.readFileSync(pathlib.join(__dirname, "README.template.md"), "utf-8") 12 | ); 13 | const readmeLocate = pathlib.join(__dirname, "README.md"); 14 | 15 | let allBenchmarks = fs.readdirSync(benchmarkDir); 16 | allBenchmarks = allBenchmarks.filter(function (filename) { 17 | return _.endsWith(filename, ".js"); 18 | }); 19 | 20 | const codeBlock = "```"; 21 | const benchmarkBlockTemplate = _.template(String.raw` 22 | [<%= filename %>](benchmark/<%= filename %>) 23 | 24 | 25 | Node.js output: 26 | 27 | ${codeBlock} 28 | <%= nodejs_benchmark_result %> 29 | ${codeBlock} 30 | 31 | QuickJS output: 32 | 33 | ${codeBlock} 34 | <%= qjs_benchmark_result %> 35 | ${codeBlock} 36 | `); 37 | 38 | async function main() { 39 | const result = []; 40 | 41 | for (const srcFilename of allBenchmarks) { 42 | const { nodejsOutput, qjsOutput } = await run(srcFilename); 43 | 44 | const bmresult = benchmarkBlockTemplate({ 45 | filename: srcFilename, 46 | nodejs_benchmark_result: nodejsOutput.trim(), 47 | qjs_benchmark_result: qjsOutput.trim(), 48 | }); 49 | 50 | result.push(bmresult); 51 | } 52 | 53 | const readmeText = readmeTemplate({ 54 | benchmark_result: result.join("\n\n"), 55 | node_version: process.versions.node, 56 | v8_version: process.versions.v8, 57 | quickjs_version: execSync("qjs -h | grep version").toString().trim(), 58 | }); 59 | 60 | fs.writeFileSync(readmeLocate, readmeText); 61 | } 62 | 63 | main(); 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fast-js", 3 | "version": "1.0.0", 4 | "description": "Writing Fast JavaScript", 5 | "main": "index.js", 6 | "scripts": { 7 | "generate": "node gen_readme.js", 8 | "prettier": "prettier --write ." 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/alsotang/fast-js.git" 13 | }, 14 | "keywords": [ 15 | "fast", 16 | "performance", 17 | "benchmark" 18 | ], 19 | "author": "alsotang ", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/alsotang/fast-js/issues" 23 | }, 24 | "homepage": "https://github.com/alsotang/fast-js#readme", 25 | "dependencies": { 26 | "benchmark": "^2.1.4", 27 | "bluebird": "*", 28 | "co": "*", 29 | "lodash": "*" 30 | }, 31 | "devDependencies": { 32 | "browserify": "^17.0.0", 33 | "mkdirp": "^1.0.4", 34 | "prettier": "^2.2.1", 35 | "rimraf": "^3.0.2" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /run_benchmark.js: -------------------------------------------------------------------------------- 1 | const compile = require("./compile"); 2 | const pathlib = require("path"); 3 | 4 | const { benchmarkDir, nodejsDistDir, quickjsDistDir } = require("./utils"); 5 | const execSync = require("child_process").execSync; 6 | 7 | async function run(srcFilename) { 8 | await compile(srcFilename); 9 | 10 | const nodejsFilePath = pathlib.join(nodejsDistDir, srcFilename); 11 | const nodejsCommand = `node ${nodejsFilePath}`; 12 | console.log(nodejsCommand); 13 | const nodejsOutput = execSync(nodejsCommand).toString(); 14 | console.log(nodejsOutput); 15 | 16 | const quickjsFilePath = pathlib.join(quickjsDistDir, srcFilename); 17 | const quickjsCommand = `qjs ${quickjsFilePath}`; 18 | console.log(quickjsCommand); 19 | const qjsOutput = execSync(quickjsCommand).toString(); 20 | console.log(qjsOutput); 21 | 22 | return { 23 | nodejsOutput, 24 | qjsOutput, 25 | }; 26 | } 27 | exports.run = run; 28 | 29 | if (require.main === module) { 30 | const argv = process.argv; 31 | const srcFilename = argv[2]; 32 | 33 | run(srcFilename); 34 | } 35 | -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | const pathlib = require("path"); 2 | 3 | const projectRoot = (exports.projectRoot = __dirname); 4 | exports.benchmarkDir = pathlib.join(projectRoot, "benchmark"); 5 | exports.nodejsDistDir = pathlib.join(projectRoot, "dist", "node"); 6 | exports.quickjsDistDir = pathlib.join(projectRoot, "dist", "quickjs"); 7 | --------------------------------------------------------------------------------