├── .editorconfig ├── .eslintignore ├── .eslintrc.cjs ├── .gitignore ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── benchmarks ├── baseline.js ├── valibot.js ├── validator.js └── zod.js ├── brainstorming.tmp.md ├── bun.lockb ├── notebook ├── .ipynb_checkpoints │ └── Untitled-checkpoint.ipynb └── Untitled.ipynb ├── package.json ├── src ├── _index.js ├── ast.js ├── balancer.js ├── compile │ ├── baseline-node-main.js │ ├── baseline-node-startup.js │ ├── custom-node-main.js │ ├── custom-node-startup.js │ ├── index.js │ └── utils.js ├── constants.js ├── csv.js ├── index.js └── utils.js └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | node_modules 4 | pnpm-lock.yaml 5 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: "@typescript-eslint/parser", 4 | extends: [ 5 | "eslint:recommended", 6 | "plugin:@typescript-eslint/recommended", 7 | "prettier", 8 | ], 9 | plugins: ["@typescript-eslint"], 10 | parserOptions: { 11 | sourceType: "module", 12 | ecmaVersion: 2022, 13 | }, 14 | rules: { 15 | "@typescript-eslint/no-explicit-any": "off", 16 | "no-constant-condition": "off", 17 | "@typescript-eslint/no-empty-function": "off", 18 | "no-async-promise-executor": "off" 19 | }, 20 | env: { 21 | browser: true, 22 | es2022: true, 23 | node: true, 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .code 3 | .idea 4 | .obsidian 5 | 6 | node_modules 7 | dist 8 | coverage 9 | results 10 | examples/*.md 11 | *.bench.js 12 | *.bench.ts 13 | .Rproj.user 14 | 15 | 16 | # Added by cargo 17 | 18 | /target 19 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | node_modules 4 | pnpm-lock.yaml 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": false, 3 | "semi": true, 4 | "trailingComma": "all", 5 | "printWidth": 80 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Yamiteru 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Docs are outdated. I'm currently rewriting the whole library 2 | -------------------------------------------------------------------------------- /benchmarks/baseline.js: -------------------------------------------------------------------------------- 1 | export const $baseline = { 2 | $default: 0 || 0, 3 | $generator: function() { 4 | return Math.round(Math.random() * 10); 5 | }, 6 | $function: function(value, blackbox) { 7 | blackbox(value + value); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /benchmarks/valibot.js: -------------------------------------------------------------------------------- 1 | import { object, string, number, minLength, maxLength, minValue, maxValue, parse } from "valibot"; 2 | 3 | const valibot_user = object({ 4 | name: string(), 5 | password: string([minLength(8), maxLength(16)]), 6 | age: number([minValue(0), maxValue(150)]), 7 | }); 8 | 9 | export const $valibot = { 10 | $default: 0 || 0, 11 | $generator: function() { 12 | return { 13 | name: "Yamiteru", 14 | password: "Test123456", 15 | age: 26 16 | }; 17 | }, 18 | $function: function(value, blackbox) { 19 | blackbox(parse(valibot_user, value)); 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /benchmarks/validator.js: -------------------------------------------------------------------------------- 1 | const assert = (fn) => (value) => { 2 | if(fn(value) === false) throw void 0; 3 | }; 4 | 5 | const rangeValue = (min, max) => assert((v) => v >= min && v <= max); 6 | const rangeLength = (min, max) => assert((v) => v.length >= min && v.length <= max); 7 | 8 | const both = (fn1, fn2) => (value) => (fn1(value), fn2(value)); 9 | 10 | const object = (schema) => { 11 | const keys = Object.keys(schema); 12 | const length = keys.length; 13 | 14 | return (value) => { 15 | for(let i = 0; i < length; ++i) { 16 | schema[keys[i]](value[keys[i]]); 17 | } 18 | }; 19 | }; 20 | 21 | const tString = (v) => assert(typeof v === "string"); 22 | const tNumber = (v) => assert(typeof v === "number"); 23 | 24 | const validator_user = object({ 25 | name: tString, 26 | password: both(tString, rangeLength(8, 16)), 27 | age: both(tNumber, rangeValue(0, 150)), 28 | }); 29 | 30 | export const $validator = { 31 | $default: 0 || 0, 32 | $generator: function() { 33 | return { 34 | name: "Yamiteru", 35 | password: "Test123456", 36 | age: 26 37 | }; 38 | }, 39 | $function: function(value, blackbox) { 40 | blackbox(validator_user(value)); 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /benchmarks/zod.js: -------------------------------------------------------------------------------- 1 | import { object, string, number } from "zod"; 2 | 3 | const zod_user = object({ 4 | name: string(), 5 | password: string().min(8).max(16), 6 | age: number().min(0).max(150) 7 | }); 8 | 9 | export const $zod = { 10 | $default: 0 || 0, 11 | $generator: function() { 12 | return { 13 | name: "Yamiteru", 14 | password: "Test123456", 15 | age: 26 16 | }; 17 | }, 18 | $function: function(value, blackbox) { 19 | blackbox(zod_user.parse(value)); 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /brainstorming.tmp.md: -------------------------------------------------------------------------------- 1 | ## Experiments 2 | 3 | - [x] how to disallow fetching and IO in NodeJS? 4 | - `blkio`/`ulimit` 5 | - `unshare`/`firejail`/`ip netns add jail/`iptables -I OUTPUT 1 -m owner --gid-owner no-internet -j DROP` 6 | - [?] is sending data over a pipe more expensive/noisy than lock-free shared memory via napi-rs? 7 | - [x] what node/v8 flags result in a low variance and high repeatability? 8 | - [ ] what system settings affect variance the most and how to set them? 9 | - [ ] is it possible to execute two Node functions at exactly the same time? 10 | 11 | --- 12 | 13 | ## Reduce variance 14 | 15 | - `sudo cpupower frequency-set --governor performance` 16 | - `echo 0 | sudo tee /sys/devices/system/cpu/cpufreq/boost` 17 | - `sudo nvram boot-args=””`, `sudo nvram boot-args=”kerneltask_priority=HIGH”` 18 | - `taskset -c 0 ./mybenchmark` 19 | - Fully preemptive Kernel (`CONFIG_PREEMPT_RT`) 20 | - Disabling Hyperthreading/SMT 21 | - Close other programs 22 | - Randomized Multiple Interleaved Trials 23 | - Duet benchmarking 24 | 25 | --- 26 | 27 | ## Stop all benchmarks conditions 28 | 29 | - Build command throws error 30 | - Benchmark check throws error 31 | - Use of fetch/IO in a benchmark 32 | - Unhandled error in a benchmark 33 | - High system temperature/throttling 34 | - Stopping/killing the process 35 | 36 | --- 37 | 38 | ## Definition 39 | 40 | If value of `data` is a branch we don't treat it as data but rather as a group of data. 41 | If value of `benchmark` is an object we don't treat it as a benchmark but rather as a group of benchmarks. 42 | 43 | `data` is optional IF the benchmark ends up being used as a reference. 44 | 45 | Each benchmark is run with each set of data. 46 | 47 | ### Single benchmark 48 | 49 | 50 | ```ts 51 | export const $for = { 52 | data: { 53 | 10: random_smi_array(10), 54 | 100: random_smi_array(100), 55 | 1000: random_smi_array(1000), 56 | }, 57 | benchmark: (data, set) => { 58 | for(let i = 0; i < data.length; ++i) { 59 | set(data[i]); 60 | } 61 | } 62 | }; 63 | ``` 64 | 65 | ### Multiple benchmarks 66 | 67 | ```ts 68 | export const $for = { 69 | data: { 70 | number: { 71 | smi: { 72 | 10: random_smi_array(10), 73 | 100: random_smi_array(100), 74 | 1000: random_smi_array(1000), 75 | // .. 76 | }, 77 | int: { 78 | 10: random_int_array(10), 79 | 100: random_int_array(100), 80 | 1000: random_int_array(1000), 81 | // .. 82 | }, 83 | float: { 84 | 10: random_float_array(10), 85 | 100: random_float_array(100), 86 | 1000: random_float_array(1000), 87 | // .. 88 | }, 89 | }, 90 | // .. 91 | }, 92 | benchmark: { 93 | before: { 94 | inline: (data, set) => { 95 | for(let i = 0; i < data.length; ++i) { 96 | set(data[i]); 97 | } 98 | }, 99 | cached: (data, set) => { 100 | const length = data.legnth; 101 | 102 | for(let i = 0; i < length; ++i) { 103 | set(data[i]); 104 | } 105 | }, 106 | // .. 107 | }, 108 | after: { 109 | inline: (data, set) => { 110 | for(let i = 0; i < data.length; i++) { 111 | set(data[i]); 112 | } 113 | }, 114 | cached: (data, set) => { 115 | const length = data.legnth; 116 | 117 | for(let i = 0; i < length; i++) { 118 | set(data[i]); 119 | } 120 | }, 121 | // .. 122 | }, 123 | } 124 | }; 125 | ``` 126 | 127 | ### Reference 128 | 129 | ```ts 130 | export const $loops = { 131 | data: { 132 | // .. 133 | }, 134 | benchmark: { 135 | $for, 136 | $for_of, 137 | $for_each, 138 | $while, 139 | $recursive 140 | } 141 | }; 142 | ``` 143 | 144 | ### Lifecycle 145 | 146 | ```ts 147 | export const $loops = { 148 | // .. 149 | beforeRun: (data, index) => { 150 | // .. 151 | }, 152 | afterRun: (data, index) => { 153 | // .. 154 | }, 155 | beforeIteration: (data, index) => { 156 | // .. 157 | }, 158 | afterIteration: (data, index) => { 159 | // .. 160 | }, 161 | } 162 | ``` 163 | 164 | --- 165 | 166 | ## NodeJS flags 167 | 168 | We don't need Node to run as fast or efficiently as possible. 169 | 170 | What we need is low variance and high repeatability. 171 | 172 | ```shell 173 | --predictable 174 | --use-strict 175 | --allow-child-process=false 176 | --allow-fs-read=false 177 | --allow-fs-write=false 178 | --allow-worker=false 179 | --no-experimental-fetch 180 | ``` 181 | 182 | --- 183 | 184 | ## CLI 185 | 186 | - [ ] `isitfast before` 187 | - applies system optimizations 188 | 189 | - [ ] `isitfast check [path=.]` 190 | - returns all warnings and errors compared to `run` which ignores warnings and fails on the first error 191 | - checks that there are benchmarks 192 | - checks that benchmarks have data or are used as a reference in a group benchmark that has data 193 | - checks that benchmarks have functions 194 | 195 | - [ ] `isitfast compile [path=.]` 196 | - mainly used for debugging 197 | - compiles all benchmarks 198 | 199 | - [ ] `isitfast run [path=.]` 200 | - compiles all benchmarks 201 | - runs all benchmarks 202 | - saves collected data 203 | 204 | - [ ] `isitfast stats [path=.]` 205 | - prints stats based on collected data 206 | 207 | - [ ] `isitfast compare [...paths]` 208 | - finds all comparable benchmarks 209 | - prints comparison of benchmarks 210 | 211 | - [ ] `isitfast after` 212 | - cancels system optimizations 213 | 214 | --- 215 | 216 | ## Config 217 | 218 | We can use config file `.isitfastrc{.$}` where `$` is `json|yaml|toml|js|ts` to change global `isitfast` settings. 219 | 220 | ```ts 221 | export type Config = Partial<{ 222 | // preset for compiling and running the benchmark code 223 | // for now only NodeJS is supported (Bun is on the roadmap) 224 | preset: "node"; 225 | // project benchmarkName 226 | // if it's not defined we use package.json benchmarkName or folder benchmarkName 227 | benchmarkName: string; 228 | // build command used before going through `path` 229 | build: string; 230 | 231 | // TODO: think again about `source` and `dist` they seem a bit weird 232 | 233 | // directory of the source code 234 | // if no `path` for `check`/`compile` is provided we use `source` 235 | // if no `path`/`dist` for `run` is provided we use `source` 236 | source: string; 237 | // directory of the `build`ed code 238 | // if no `path` for `run` is provided we use `dist` 239 | dist: string; 240 | // TODO: add more 241 | }>; 242 | ``` 243 | 244 | --- 245 | 246 | ## Typescript 247 | 248 | Uses `build` command from the config file. 249 | 250 | If `.ts` file is used but no `build` command is provided we default to `tsc`. 251 | 252 | ```ts 253 | // TODO: I can have either function or object with cases as a preset for Suite 254 | import type { Benchmark, Case, Suite } from "isitfast"; 255 | 256 | type LoopData = Record; 257 | 258 | // Benchmark with no data 259 | // has to be used in a Suite 260 | export const $while_loop = (data, set) => { 261 | // .. 262 | } satisfies Case; 263 | 264 | // Benchmark with its own data 265 | // cannot be used in a Suite 266 | export const $for_loop = { 267 | data: { 268 | // .. 269 | }, 270 | case: (data, set) => { 271 | // .. 272 | } 273 | } satisfies Benchmark; 274 | 275 | // Suite of benchmark cases 276 | // has to have its own data 277 | export const $loops = { 278 | data: { 279 | // .. 280 | }, 281 | cases: { 282 | $while_loop, 283 | // .. 284 | } 285 | } satisfies Suite; 286 | ``` 287 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yamiteru/isitfast/6ce082c513a8aecafd31256a4bc21a743adcced9/bun.lockb -------------------------------------------------------------------------------- /notebook/.ipynb_checkpoints/Untitled-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [], 3 | "metadata": {}, 4 | "nbformat": 4, 5 | "nbformat_minor": 5 6 | } 7 | -------------------------------------------------------------------------------- /notebook/Untitled.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "f4fa84fd", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": 5, 14 | "id": "e57fcd5e", 15 | "metadata": {}, 16 | "outputs": [ 17 | { 18 | "data": { 19 | "text/plain": [ 20 | "" 21 | ] 22 | }, 23 | "execution_count": 5, 24 | "metadata": {}, 25 | "output_type": "execute_result" 26 | }, 27 | { 28 | "data": { 29 | "image/png": "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FGLevHmsW7du2Hn6+vpYsGABZWVlVFRUcPnllxOLxYYd88orr3DaaacRDAZpbm7mtttuOyTXuLe0HXqaPNctcGuEEEKII8+oDk3f/va3+fGPf8wPfvADVq9ezbe//W1uu+027r777vwxt912G3fddRf33HMPL7zwApFIhPnz55NKpfLHLFiwgFWrVrFw4UIefPBBFi1axJVXXpm/PxqNctZZZzF+/HiWLVvGd77zHW655RZ++tOfHtLr3RN9xzlNEpqEEEKIQ84sdAP25LnnnuN973sf5557LgATJkzgD3/4A0uWLAFUL9Mdd9zBl7/8Zd73vvcB8Jvf/Ib6+noeeOABLrroIlavXs2jjz7K0qVLOf744wG4++67Oeecc/jud79LU1MT9957L5lMhl/84hf4/X5mzpzJ8uXLuf3224eFqx2l02nS6e3L/6PR6MF8KbavnstYeK6LdlCfTQghhBBvNqp7mk4++WSeeOIJ1q5dC8CKFSv497//zbvf/W4ANm7cSEdHB/Pmzcs/pry8nBNPPJHFixcDsHjxYioqKvKBCWDevHnous4LL7yQP+b000/Hn90UF2D+/PmsWbOG/v7+Xbbt1ltvpby8PP/R3Nw8shf/Jlpghw17padJCCGEOORGdU/TF7/4RaLRKNOmTcMwDBzH4X/+539YsGABAB0dHQDU19cPe1x9fX3+vo6ODurq6obdb5omVVVVw45paWnZ6Ry5+yorK3dq24033sj111+f/zwajR7U4DRsw17HOWjPI4QQQohdG9Wh6U9/+hP33nsvv//97/NDZtdddx1NTU1ceumlBW1bIBAgkJ1ndChsXz1nDZsIL4QQQohDY1SHps9//vN88Ytf5KKLLgJg1qxZbN68mVtvvZVLL72UhoYGADo7O2lsbMw/rrOzkzlz5gDQ0NBAV1fXsPPatk1fX1/+8Q0NDXR2dg47Jvd57phCy9dpsiyQ0CSEEEIccqN6TlMikUDXhzfRMAzc7JyelpYWGhoaeOKJJ/L3R6NRXnjhBU466SQATjrpJAYGBli2bFn+mCeffBLXdTnxxBPzxyxatAhrh2rbCxcuZOrUqbscmiuEYRXBZXhOCCGEOORGdWg677zz+J//+R8eeughNm3axP3338/tt9/O+9//fgA0TeO6667jG9/4Bn//+9959dVXueSSS2hqauL8888HYPr0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", 30 | "text/plain": [ 31 | "
" 32 | ] 33 | }, 34 | "metadata": {}, 35 | "output_type": "display_data" 36 | } 37 | ], 38 | "source": [ 39 | "import matplotlib.pyplot as plt\n", 40 | "import pandas as pd\n", 41 | "import seaborn as sns\n", 42 | "\n", 43 | "#plt.ylim(2000, 7000)\n", 44 | "#plt.xlim(0, 1500)\n", 45 | "\n", 46 | "baseline = pd.read_csv(\"/Users/yamiteru/.isitfast/results/baseline.js-$baseline-main.csv\")\n", 47 | "validator = pd.read_csv(\"/Users/yamiteru/.isitfast/results/validator.js-$validator-main.csv\")\n", 48 | "valibot = pd.read_csv(\"/Users/yamiteru/.isitfast/results/valibot.js-$valibot-main.csv\")\n", 49 | "zod = pd.read_csv(\"/Users/yamiteru/.isitfast/results/zod.js-$zod-main.csv\")\n", 50 | "\n", 51 | "#baseline = pd.read_csv(\"/Users/yamiteru/.isitfast/results/baseline.js-$baseline-startup.csv\")\n", 52 | "#validator = pd.read_csv(\"/Users/yamiteru/.isitfast/results/validator.js-$validator-startup.csv\")\n", 53 | "#valibot = pd.read_csv(\"/Users/yamiteru/.isitfast/results/valibot.js-$valibot-startup.csv\")\n", 54 | "#zod = pd.read_csv(\"/Users/yamiteru/.isitfast/results/zod.js-$zod-startup.csv\")\n", 55 | "\n", 56 | "baseline = baseline.groupby(by=\"run\").rolling(50).median()\n", 57 | "validator = validator.groupby(by=\"run\").rolling(50).median()\n", 58 | "valibot = valibot.groupby(by=\"run\").rolling(50).median()\n", 59 | "zod = zod.groupby(by=\"run\").rolling(50).median()\n", 60 | "\n", 61 | "sns.lineplot(x=\"iteration\", y=\"cpu\", data=baseline)\n", 62 | "sns.lineplot(x=\"iteration\", y=\"cpu\", data=validator)\n", 63 | "sns.lineplot(x=\"iteration\", y=\"cpu\", data=valibot)\n", 64 | "sns.lineplot(x=\"iteration\", y=\"cpu\", data=zod)\n", 65 | "\n", 66 | "#key = \"duration\"\n", 67 | "\n", 68 | "#sns.boxplot(data=baseline[key])\n", 69 | "#sns.boxplot(data=validator[key])\n", 70 | "#sns.boxplot(data=valibot[key])\n", 71 | "#sns.boxplot(data=zod[key])" 72 | ] 73 | }, 74 | { 75 | "cell_type": "code", 76 | "execution_count": null, 77 | "id": "96902e20", 78 | "metadata": {}, 79 | "outputs": [], 80 | "source": [] 81 | } 82 | ], 83 | "metadata": { 84 | "kernelspec": { 85 | "display_name": "Python 3 (ipykernel)", 86 | "language": "python", 87 | "name": "python3" 88 | }, 89 | "language_info": { 90 | "codemirror_mode": { 91 | "name": "ipython", 92 | "version": 3 93 | }, 94 | "file_extension": ".py", 95 | "mimetype": "text/x-python", 96 | "name": "python", 97 | "nbconvert_exporter": "python", 98 | "pygments_lexer": "ipython3", 99 | "version": "3.9.6" 100 | } 101 | }, 102 | "nbformat": 4, 103 | "nbformat_minor": 5 104 | } 105 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "isitfast", 3 | "type": "module", 4 | "version": "0.0.8", 5 | "main": "dist/index.js", 6 | "bin": "./dist/index.js", 7 | "scripts": { 8 | "benchmark:validate:validator": "FILE=validate.js BENCHMARK=validator node newer/index.js", 9 | "benchmark:validate:zod": "FILE=validate.js BENCHMARK=zod node newer/index.js", 10 | "benchmark:validate:valibot": "FILE=validate.js BENCHMARK=valibot node newer/index.js", 11 | "compile": "node newer/compile.js", 12 | "ast:template": "FILE=newer/template.js node ast.js > template.json", 13 | "start": "node src/index.js benchmarks" 14 | }, 15 | "devDependencies": { 16 | "@types/node": "20.9.0", 17 | "husky": "8.0.3", 18 | "npm-run-all": "4.1.5", 19 | "typescript": "5.2.2" 20 | }, 21 | "packageManager": "yarn@4.0.2", 22 | "dependencies": { 23 | "@paralleldrive/cuid2": "2.2.2", 24 | "@swc/core": "1.4.8", 25 | "terser": "^5.29.2", 26 | "valibot": "0.30.0", 27 | "zod": "3.22.4" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/_index.js: -------------------------------------------------------------------------------- 1 | import { appendFile, writeFile } from "node:fs/promises"; 2 | import { join } from "node:path"; 3 | import { spawn } from "node:child_process"; 4 | 5 | const HERE = process.cwd(); 6 | const BENCHMARK = process.env.BENCHMARK; 7 | const FILE = process.env.FILE; 8 | const SAMPLES = 5_000; 9 | const RUNS = 20; 10 | const BUFFER = Buffer.alloc(1); 11 | const STATS_FILE_NAME = join(HERE, "results", "newer", `${FILE}-$${BENCHMARK}.csv`); 12 | 13 | export const STATS_COLUMNS = [ 14 | "run", 15 | "iteration", 16 | "cpu", 17 | "ram", 18 | ]; 19 | 20 | export const row = (values) => `${values.join(",")}\n`; 21 | export const header = (values) => `${values.map((v) => `"${v}"`).join(",")}\n`; 22 | 23 | let iterationIndex = 0; 24 | let runIndex = 0; 25 | 26 | const run = async () => { 27 | const proc = spawn( 28 | "node", 29 | [join(HERE, "compile", `${FILE}-$${BENCHMARK}.js`)], 30 | { stdio: ["inherit", "inherit", "inherit", "pipe"] } 31 | ); 32 | 33 | const stream = proc.stdio[3]; 34 | 35 | stream.on("data", async (buffer) => { 36 | const type = buffer.readUInt32LE(0); 37 | 38 | if(type === 1) { 39 | const cpu = Number(buffer.readBigInt64LE(12) - buffer.readBigInt64LE(4)); 40 | const ram = buffer.readUInt32LE(24) - buffer.readUInt32LE(20); 41 | 42 | if(cpu >= 0 && ram >= 0) { 43 | await appendFile(STATS_FILE_NAME, row([ 44 | runIndex, 45 | iterationIndex, 46 | cpu, 47 | ram, 48 | ])); 49 | 50 | if(++iterationIndex === SAMPLES) { 51 | proc.kill(); 52 | 53 | if(++runIndex < RUNS) { 54 | iterationIndex = 0; 55 | await run(); 56 | } 57 | } else { 58 | stream.write(BUFFER); 59 | } 60 | } else { 61 | stream.write(BUFFER); 62 | } 63 | } else { 64 | stream.write(BUFFER); 65 | } 66 | }); 67 | }; 68 | 69 | (async () => { 70 | await writeFile(STATS_FILE_NAME, header(STATS_COLUMNS)); 71 | await run(); 72 | })(); 73 | -------------------------------------------------------------------------------- /src/ast.js: -------------------------------------------------------------------------------- 1 | export const span = { start: 0, end: 0, ctxt: 0 }; 2 | 3 | export const variableDeclarator = (name, ast) => { 4 | return { 5 | type: 'VariableDeclarator', 6 | span, 7 | id: { 8 | type: 'Identifier', 9 | span, 10 | value: name, 11 | optional: false, 12 | typeAnnotation: null 13 | }, 14 | init: ast, 15 | definite: false 16 | }; 17 | }; 18 | 19 | export const variableDeclaration = (declarations, kind = "const") => { 20 | return { 21 | type: 'VariableDeclaration', 22 | span, 23 | kind, 24 | declare: false, 25 | declarations 26 | }; 27 | }; 28 | 29 | export const module = (body) => { 30 | return { 31 | type: 'Module', 32 | span: { ...span, start: 1 }, 33 | body, 34 | interpreter: null 35 | }; 36 | }; 37 | -------------------------------------------------------------------------------- /src/balancer.js: -------------------------------------------------------------------------------- 1 | import { appendFile, writeFile } from "node:fs/promises"; 2 | import { CSV_COLUMN_MAP, BENCHMARKS, BUFFER_TYPE_INDEX, BUFFER_CPU_AFTER_INDEX, BUFFER_CPU_BEFORE_INDEX, BUFFER_RAM_AFTER_INDEX, BUFFER_RAM_BEFORE_INDEX, BUFFER, MAIN_SAMPLES, RUNS, BUFFER_DURATION_INDEX, STARTUP_SAMPLES } from "./constants.js"; 3 | import { spawn } from "node:child_process"; 4 | import { header, row } from "./csv.js"; 5 | import { randomItem } from "./utils.js"; 6 | 7 | let now = process.hrtime.bigint(); 8 | let activeItem; 9 | let activeProcess; 10 | let activeStream; 11 | let remaining = []; 12 | 13 | let resultsCpu = []; 14 | let resultsRam = []; 15 | 16 | const RESULT_BUFFER = 1000; 17 | const RESULT_STEP = 100; 18 | const RESULT_STABLE = 500; 19 | 20 | async function runMain(buffer) { 21 | const { name, result } = activeItem; 22 | const type = buffer.readUInt32LE(BUFFER_TYPE_INDEX); 23 | 24 | if(type === 1) { 25 | const cpu = Number(buffer.readBigInt64LE(BUFFER_CPU_AFTER_INDEX) - buffer.readBigInt64LE(BUFFER_CPU_BEFORE_INDEX)); 26 | const ram = buffer.readUInt32LE(BUFFER_RAM_AFTER_INDEX) - buffer.readUInt32LE(BUFFER_RAM_BEFORE_INDEX); 27 | 28 | if(cpu >= 0 && ram >= 0) { 29 | resultsCpu.push(cpu); 30 | resultsRam.push(ram); 31 | 32 | await appendFile(result.path, row([ 33 | result.run, 34 | result.iteration, 35 | cpu, 36 | ram, 37 | ])); 38 | 39 | ++result.iteration; 40 | 41 | if(result.iteration >= RESULT_BUFFER && result.iteration % RESULT_STEP === 0) { 42 | // const slice = resultsCpu.slice(RESULT_STABLE); 43 | 44 | activeProcess.kill(); 45 | 46 | if(++result.run >= RUNS) { 47 | remaining = remaining.filter((v) => v !== name); 48 | } 49 | 50 | result.iteration = 0; 51 | next(); 52 | } else { 53 | activeStream.write(BUFFER); 54 | } 55 | } else { 56 | activeStream.write(BUFFER); 57 | } 58 | } else { 59 | activeStream.write(BUFFER); 60 | } 61 | } 62 | 63 | async function runStartup(buffer) { 64 | const { name, result } = activeItem; 65 | const duration = Number(buffer.readBigInt64LE(BUFFER_DURATION_INDEX) - now); 66 | 67 | await appendFile(result.path, row([ 68 | result.iteration, 69 | duration, 70 | ])); 71 | 72 | activeProcess.kill(); 73 | 74 | if(++result.iteration === STARTUP_SAMPLES) { 75 | remaining = remaining.filter((v) => v !== name); 76 | } 77 | 78 | next(); 79 | } 80 | 81 | const RUN_MAP = { 82 | main: runMain, 83 | startup: runStartup 84 | }; 85 | 86 | export const init = () => { 87 | remaining = [...BENCHMARKS.keys()]; 88 | }; 89 | 90 | export const next = () => { 91 | if(remaining.length === 0) return; 92 | 93 | activeItem = BENCHMARKS.get(randomItem(activeItem, remaining)); 94 | resultsCpu = []; 95 | resultsRam = []; 96 | 97 | console.log("RUN - ", activeItem); 98 | 99 | (async () => { 100 | const { 101 | type, 102 | compile, 103 | result 104 | } = activeItem; 105 | 106 | if(!result.created) { 107 | await writeFile( 108 | result.path, 109 | header(CSV_COLUMN_MAP[type]) 110 | ); 111 | 112 | result.created = true; 113 | } 114 | 115 | now = process.hrtime.bigint(); 116 | activeProcess = spawn( 117 | "node", 118 | [compile.path], 119 | { stdio: ["inherit", "inherit", "inherit", "pipe"] } 120 | ); 121 | 122 | activeStream = activeProcess.stdio[3]; 123 | activeStream.on("data", RUN_MAP[type]); 124 | })(); 125 | }; 126 | -------------------------------------------------------------------------------- /src/compile/baseline-node-main.js: -------------------------------------------------------------------------------- 1 | import { join } from "path"; 2 | import { 3 | ISITFAST_BASELINE_PATH, 4 | BASELINE_BENCHMARK_NAME, 5 | BUFFER_MAIN_SIZE, 6 | BUFFER_TYPE_INDEX, 7 | BUFFER_CPU_BEFORE_INDEX, 8 | BUFFER_CPU_AFTER_INDEX, 9 | BUFFER_RAM_BEFORE_INDEX, 10 | BUFFER_RAM_AFTER_INDEX, 11 | TEMPLATE_SOCKET_CLASS, 12 | TEMPLATE_SOCKET_INSTANCE, 13 | TEMPLATE_SOCKET_ON_DATA, 14 | TEMPLATE_BUFFER, 15 | TEMPLATE_BENCHMARK, 16 | TEMPLATE_GENERATOR, 17 | TEMPLATE_TMP, 18 | TEMPLATE_BLACKBOX, 19 | BENCHMARKS 20 | } from "../constants.js"; 21 | import { writeCompiledContent } from "./utils.js"; 22 | 23 | export const baselineCompileMainNode = async () => { 24 | await writeCompiledContent( 25 | join(ISITFAST_BASELINE_PATH, `${BASELINE_BENCHMARK_NAME}-main.js`), 26 | ` 27 | import { Socket as ${TEMPLATE_SOCKET_CLASS} } from "node:net"; 28 | 29 | const ${TEMPLATE_BENCHMARK} = (value, blackbox) => { 30 | blackbox(value + value); 31 | }; 32 | 33 | const ${TEMPLATE_GENERATOR} = () => { 34 | return Math.round(Math.random() * 10); 35 | }; 36 | 37 | let ${TEMPLATE_TMP} = 0; 38 | 39 | const ${TEMPLATE_SOCKET_INSTANCE} = new ${TEMPLATE_SOCKET_CLASS}({ fd: 3, readable: true, writable: true }); 40 | const ${TEMPLATE_BUFFER} = Buffer.alloc(${BUFFER_MAIN_SIZE}); 41 | 42 | const ${TEMPLATE_BLACKBOX} = (v) => { 43 | ${TEMPLATE_TMP} = v; 44 | } 45 | 46 | const ${TEMPLATE_SOCKET_ON_DATA} = () => { 47 | const data = ${TEMPLATE_GENERATOR}(); 48 | 49 | ${TEMPLATE_BUFFER}.writeUInt32LE(1, ${BUFFER_TYPE_INDEX}); 50 | 51 | ${TEMPLATE_BUFFER}.writeBigUInt64LE(process.hrtime.bigint(), ${BUFFER_CPU_BEFORE_INDEX}); 52 | ${TEMPLATE_BUFFER}.writeUInt32LE(process.memoryUsage().heapUsed, ${BUFFER_RAM_BEFORE_INDEX}); 53 | 54 | ${TEMPLATE_BENCHMARK}(data, ${TEMPLATE_BLACKBOX}); 55 | 56 | ${TEMPLATE_BUFFER}.writeUInt32LE(process.memoryUsage().heapUsed, ${BUFFER_RAM_AFTER_INDEX}); 57 | ${TEMPLATE_BUFFER}.writeBigUInt64LE(process.hrtime.bigint(), ${BUFFER_CPU_AFTER_INDEX}); 58 | 59 | ${TEMPLATE_SOCKET_INSTANCE}.write(${TEMPLATE_BUFFER}); 60 | } 61 | 62 | ${TEMPLATE_SOCKET_INSTANCE}.on("data", ${TEMPLATE_SOCKET_ON_DATA}); 63 | 64 | ${TEMPLATE_BUFFER}.writeUInt8(0, ${BUFFER_TYPE_INDEX}); 65 | 66 | ${TEMPLATE_SOCKET_INSTANCE}.write(${TEMPLATE_BUFFER}); 67 | ` 68 | ); 69 | }; 70 | 71 | -------------------------------------------------------------------------------- /src/compile/baseline-node-startup.js: -------------------------------------------------------------------------------- 1 | import { join } from "path"; 2 | import { 3 | ISITFAST_BASELINE_PATH, 4 | BASELINE_BENCHMARK_NAME, 5 | BUFFER_STARTUP_SIZE, 6 | BUFFER_TYPE_INDEX, 7 | BUFFER_DURATION_INDEX, 8 | TEMPLATE_BENCHMARK, 9 | TEMPLATE_GENERATOR, 10 | TEMPLATE_PERFORMANCE_TIMING, 11 | TEMPLATE_SOCKET_CLASS, 12 | TEMPLATE_SOCKET_INSTANCE, 13 | TEMPLATE_BUFFER, 14 | } from "../constants.js"; 15 | import { writeCompiledContent } from "./utils.js"; 16 | 17 | export const baselineCompileStartupNode = async () => { 18 | await writeCompiledContent( 19 | join(ISITFAST_BASELINE_PATH, `${BASELINE_BENCHMARK_NAME}-startup.js`), 20 | ` 21 | const ${TEMPLATE_PERFORMANCE_TIMING} = process.hrtime.bigint(); 22 | 23 | import { Socket as ${TEMPLATE_SOCKET_CLASS} } from "node:net"; 24 | 25 | const ${TEMPLATE_BENCHMARK} = (value, blackbox) => { 26 | blackbox(value + value); 27 | }; 28 | 29 | const ${TEMPLATE_GENERATOR} = () => { 30 | return Math.round(Math.random() * 10); 31 | }; 32 | 33 | const ${TEMPLATE_SOCKET_INSTANCE} = new ${TEMPLATE_SOCKET_CLASS}({ fd: 3, readable: true, writable: true }); 34 | const ${TEMPLATE_BUFFER} = Buffer.alloc(${BUFFER_STARTUP_SIZE}); 35 | 36 | ${TEMPLATE_BUFFER}.writeUInt32LE(0, ${BUFFER_TYPE_INDEX}); 37 | ${TEMPLATE_BUFFER}.writeBigUInt64LE(${TEMPLATE_PERFORMANCE_TIMING}, ${BUFFER_DURATION_INDEX}); 38 | 39 | ${TEMPLATE_SOCKET_INSTANCE}.write(${TEMPLATE_BUFFER}); 40 | ` 41 | ); 42 | }; 43 | -------------------------------------------------------------------------------- /src/compile/custom-node-main.js: -------------------------------------------------------------------------------- 1 | import { 2 | BUFFER_MAIN_SIZE, 3 | BUFFER_TYPE_INDEX, 4 | BUFFER_CPU_BEFORE_INDEX, 5 | BUFFER_CPU_AFTER_INDEX, 6 | BUFFER_RAM_BEFORE_INDEX, 7 | BUFFER_RAM_AFTER_INDEX, 8 | TEMPLATE_SOCKET_CLASS, 9 | TEMPLATE_SOCKET_INSTANCE, 10 | TEMPLATE_SOCKET_ON_DATA, 11 | TEMPLATE_BUFFER, 12 | TEMPLATE_BENCHMARK, 13 | TEMPLATE_GENERATOR, 14 | TEMPLATE_TMP, 15 | TEMPLATE_BLACKBOX 16 | } from "../constants.js"; 17 | import { compileFiles } from "./utils.js"; 18 | 19 | export const customCompileMainNode = () => compileFiles("main", async ({ 20 | body, 21 | defaultValue, 22 | generator, 23 | benchmark, 24 | }) => { 25 | console.log("CUSTOM START - main"); 26 | 27 | const content = ` 28 | import { Socket as ${TEMPLATE_SOCKET_CLASS} } from "node:net"; 29 | 30 | ${body.code} 31 | 32 | ${benchmark.code} 33 | 34 | ${generator.code} 35 | 36 | ${defaultValue.code} 37 | 38 | const ${TEMPLATE_SOCKET_INSTANCE} = new ${TEMPLATE_SOCKET_CLASS}({ fd: 3, readable: true, writable: true }); 39 | const ${TEMPLATE_BUFFER} = Buffer.alloc(${BUFFER_MAIN_SIZE}); 40 | 41 | const ${TEMPLATE_BLACKBOX} = (v) => { 42 | ${TEMPLATE_TMP} = v; 43 | } 44 | 45 | const ${TEMPLATE_SOCKET_ON_DATA} = () => { 46 | const data = ${TEMPLATE_GENERATOR}(); 47 | 48 | ${TEMPLATE_BUFFER}.writeUInt32LE(1, ${BUFFER_TYPE_INDEX}); 49 | 50 | ${TEMPLATE_BUFFER}.writeBigUInt64LE(process.hrtime.bigint(), ${BUFFER_CPU_BEFORE_INDEX}); 51 | ${TEMPLATE_BUFFER}.writeUInt32LE(process.memoryUsage().heapUsed, ${BUFFER_RAM_BEFORE_INDEX}); 52 | 53 | ${TEMPLATE_BENCHMARK}(data, ${TEMPLATE_BLACKBOX}); 54 | 55 | ${TEMPLATE_BUFFER}.writeUInt32LE(process.memoryUsage().heapUsed, ${BUFFER_RAM_AFTER_INDEX}); 56 | ${TEMPLATE_BUFFER}.writeBigUInt64LE(process.hrtime.bigint(), ${BUFFER_CPU_AFTER_INDEX}); 57 | 58 | ${TEMPLATE_SOCKET_INSTANCE}.write(${TEMPLATE_BUFFER}); 59 | } 60 | 61 | ${TEMPLATE_SOCKET_INSTANCE}.on("data", ${TEMPLATE_SOCKET_ON_DATA}); 62 | 63 | ${TEMPLATE_BUFFER}.writeUInt8(0, ${BUFFER_TYPE_INDEX}); 64 | 65 | ${TEMPLATE_SOCKET_INSTANCE}.write(${TEMPLATE_BUFFER}); 66 | `; 67 | 68 | console.log("CUSTOM END - main"); 69 | 70 | return content; 71 | }); 72 | -------------------------------------------------------------------------------- /src/compile/custom-node-startup.js: -------------------------------------------------------------------------------- 1 | import { 2 | BUFFER_STARTUP_SIZE, 3 | BUFFER_TYPE_INDEX, 4 | BUFFER_DURATION_INDEX, 5 | TEMPLATE_PERFORMANCE_TIMING, 6 | TEMPLATE_SOCKET_CLASS, 7 | TEMPLATE_SOCKET_INSTANCE, 8 | TEMPLATE_BUFFER, 9 | } from "../constants.js"; 10 | import { compileFiles } from "./utils.js"; 11 | 12 | export const customCompileStartupNode = () => compileFiles("startup", async ({ 13 | body, 14 | benchmark, 15 | }) => { 16 | console.log("CUSTOM START - startup"); 17 | 18 | const content = ` 19 | const ${TEMPLATE_PERFORMANCE_TIMING} = process.hrtime.bigint(); 20 | 21 | import { Socket as ${TEMPLATE_SOCKET_CLASS} } from "node:net"; 22 | 23 | ${body.code} 24 | 25 | ${benchmark.code} 26 | 27 | const ${TEMPLATE_SOCKET_INSTANCE} = new ${TEMPLATE_SOCKET_CLASS}({ fd: 3, readable: true, writable: true }); 28 | const ${TEMPLATE_BUFFER} = Buffer.alloc(${BUFFER_STARTUP_SIZE}); 29 | 30 | ${TEMPLATE_BUFFER}.writeUInt32LE(0, ${BUFFER_TYPE_INDEX}); 31 | ${TEMPLATE_BUFFER}.writeBigUInt64LE(${TEMPLATE_PERFORMANCE_TIMING}, ${BUFFER_DURATION_INDEX}); 32 | 33 | ${TEMPLATE_SOCKET_INSTANCE}.write(${TEMPLATE_BUFFER}); 34 | `; 35 | 36 | console.log("CUSTOM END - startup"); 37 | 38 | return content; 39 | }); 40 | -------------------------------------------------------------------------------- /src/compile/index.js: -------------------------------------------------------------------------------- 1 | export { customCompileMainNode } from "./custom-node-main.js"; 2 | export { customCompileStartupNode } from "./custom-node-startup.js"; 3 | export { baselineCompileMainNode } from "./baseline-node-main.js"; 4 | export { baselineCompileStartupNode } from "./baseline-node-startup.js"; 5 | -------------------------------------------------------------------------------- /src/compile/utils.js: -------------------------------------------------------------------------------- 1 | import { readdir, writeFile } from "node:fs/promises"; 2 | import { join } from "node:path"; 3 | import { parseFile, transform } from "@swc/core"; 4 | import { minify } from "terser"; 5 | import { CONTEXT_PATH, ISITFAST_COMPILE_PATH, BENCHMARK_PREFIX, BENCHMARKS, SWC_OPTIONS, TEMPLATE_GENERATOR, TEMPLATE_BENCHMARK, ISITFAST_RESULTS_PATH, TEMPLATE_TMP } from "../constants.js"; 6 | import { module, variableDeclaration, variableDeclarator } from "../ast.js"; 7 | 8 | export const assert = (predicate, message) => { 9 | if(predicate) { 10 | throw new Error(message); 11 | } 12 | }; 13 | 14 | // TODO: recursively find files 15 | export const getFileList = () => readdir( 16 | CONTEXT_PATH, 17 | { withFileTypes: true } 18 | ); 19 | 20 | export const writeCompiledContent = async (benchmarkPath, content) => { 21 | console.log("WRITE START - ", benchmarkPath); 22 | 23 | const { code } = await minify(content, { 24 | compress: false, 25 | ecma: 2020, 26 | module: true, 27 | toplevel: true 28 | }); 29 | 30 | await writeFile(benchmarkPath, code); 31 | 32 | console.log("WRITE END - ", benchmarkPath); 33 | }; 34 | 35 | export const getAstFromFile = (file) => { 36 | return parseFile(join(file.path, file.name)); 37 | }; 38 | 39 | export const isBenchmarkFile = (file) => { 40 | return ( 41 | file.isFile() && 42 | file.name[0] !== "_" && 43 | // TODO: support typescript files 44 | file.name.split(".").at(-1) === "js" 45 | ); 46 | }; 47 | 48 | export const compileFiles = async (type, custom) => { 49 | console.log("COMPILE START - ", type); 50 | 51 | const files = await getFileList(); 52 | const promises = []; 53 | 54 | for (const file of files) { 55 | if (isBenchmarkFile(file)) { 56 | const ast = await getAstFromFile(file); 57 | const benchmarkNodes = []; 58 | const otherNodes = []; 59 | 60 | for (const node of ast.body) { 61 | if ( 62 | node.type === "ExportDeclaration" && 63 | node.declaration.type === "VariableDeclaration" && 64 | node.declaration.kind === "const" && 65 | node.declaration.declarations[0].type === "VariableDeclarator" && 66 | node.declaration.declarations[0].id.type === "Identifier" && 67 | node.declaration.declarations[0].id.value.startsWith(BENCHMARK_PREFIX) && 68 | node.declaration.declarations[0].init.type === "ObjectExpression" 69 | ) { 70 | benchmarkNodes.push(node); 71 | } else { 72 | otherNodes.push(node); 73 | } 74 | } 75 | 76 | for (const node of benchmarkNodes) { 77 | const variable = node.declaration.declarations[0].id.value; 78 | const definition = node.declaration.declarations[0].init; 79 | 80 | let generator_ast, benchmark_ast, default_ast; 81 | 82 | for (const prop of definition.properties) { 83 | assert(prop.key.type !== "Identifier", "Property key has to be an Identifier"); 84 | 85 | const propertyKey = prop.key.value; 86 | 87 | if (propertyKey === "$generator") { 88 | assert(prop.value.type !== "FunctionExpression", "$generator should be a function"); 89 | assert(prop.value.async, "$generator should not be async"); 90 | assert(prop.value.generator, "$generator should not be a generator"); 91 | 92 | generator_ast = prop.value; 93 | } else if (propertyKey === "$function") { 94 | assert(prop.value.type !== "FunctionExpression", "$function should be a function"); 95 | assert(prop.value.async, "$function should not be async"); 96 | assert(prop.value.generator, "$function should not be a generator"); 97 | 98 | benchmark_ast = prop.value; 99 | } else if (propertyKey === "$default") { 100 | default_ast = prop.value; 101 | } 102 | } 103 | 104 | assert(!generator_ast, "No $generator provided"); 105 | assert(!benchmark_ast, "No $function provided"); 106 | assert(!default_ast, "No $default provided"); 107 | 108 | const [ 109 | body, 110 | defaultValue, 111 | benchmark, 112 | generator 113 | ] = await Promise.all([ 114 | transform(module( 115 | otherNodes 116 | ), SWC_OPTIONS), 117 | transform(module([ 118 | variableDeclaration([ 119 | variableDeclarator(TEMPLATE_BENCHMARK, benchmark_ast) 120 | ]) 121 | ]), SWC_OPTIONS), 122 | transform(module([ 123 | variableDeclaration([ 124 | variableDeclarator(TEMPLATE_TMP, default_ast) 125 | ], "let") 126 | ]), SWC_OPTIONS), 127 | transform(module([ 128 | variableDeclaration([ 129 | variableDeclarator(TEMPLATE_GENERATOR, generator_ast) 130 | ]) 131 | ]), SWC_OPTIONS) 132 | ]); 133 | 134 | const compileName = `${file.name}-${variable}-${type}.mjs`; 135 | const compileDirectory = ISITFAST_COMPILE_PATH; 136 | 137 | const compile = { 138 | name: compileName, 139 | directory: compileDirectory, 140 | path: join(compileDirectory, compileName) 141 | }; 142 | 143 | promises.push(writeCompiledContent( 144 | compile.path, 145 | (await custom({ 146 | body, 147 | defaultValue, 148 | generator, 149 | benchmark 150 | })) 151 | )); 152 | 153 | const resultName = `${file.name}-${variable}-${type}.csv`; 154 | const resultDirectory = ISITFAST_RESULTS_PATH; 155 | 156 | const benchmarkName = `${file.name}-${variable}-${type}`; 157 | 158 | BENCHMARKS.set(benchmarkName, { 159 | type, 160 | name: benchmarkName, 161 | meta: { 162 | variable: variable, 163 | // TODO: add id, name, description 164 | }, 165 | source: { 166 | name: file.name, 167 | directory: file.path, 168 | path: join(file.path, file.name) 169 | }, 170 | compile, 171 | result: { 172 | created: false, 173 | run: 0, 174 | iteration: 0, 175 | name: resultName, 176 | directory: resultDirectory, 177 | path: join(resultDirectory, resultName) 178 | }, 179 | }); 180 | } 181 | } 182 | } 183 | 184 | await Promise.all(promises); 185 | 186 | console.log("COMPILE END - ", type); 187 | }; 188 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | import { homedir } from "os"; 2 | import { join } from "node:path"; 3 | import { createId } from '@paralleldrive/cuid2'; 4 | 5 | export const NS_IN_MS = 1e6; 6 | 7 | export const RUNS = 20; 8 | export const MAIN_SAMPLES = 5_000; 9 | export const STARTUP_SAMPLES = 100; 10 | export const BUFFER = Buffer.alloc(1); 11 | 12 | export const HOME = homedir(); 13 | export const HERE = process.cwd(); 14 | export const PATH = process.argv[2]; 15 | 16 | export const CONTEXT_PATH = PATH ? join(HERE, PATH): HERE; 17 | export const ISITFAST_PATH = join(HOME, ".isitfast"); 18 | export const ISITFAST_COMPILE_PATH = join(ISITFAST_PATH, "compile"); 19 | export const ISITFAST_RESULTS_PATH = join(ISITFAST_PATH, "results"); 20 | export const ISITFAST_BASELINE_PATH = join(ISITFAST_PATH, "baseline"); 21 | 22 | export const BASELINE_BENCHMARK_NAME = "benchmark"; 23 | 24 | export const BENCHMARK_PREFIX = "$"; 25 | 26 | export const BENCHMARKS = new Map(); 27 | 28 | export const SWC_OPTIONS = { 29 | jsc: { 30 | parser: { 31 | syntax: "ecmascript", 32 | }, 33 | target: "es2020", 34 | }, 35 | }; 36 | 37 | // Buffer size 38 | export const BUFFER_MAIN_SIZE = 32; 39 | export const BUFFER_STARTUP_SIZE = 16; 40 | 41 | // Buffer index - General 42 | export const BUFFER_TYPE_INDEX = 0; 43 | 44 | // Buffer index - Main 45 | export const BUFFER_CPU_BEFORE_INDEX = 4; 46 | export const BUFFER_CPU_AFTER_INDEX = 12; 47 | export const BUFFER_RAM_BEFORE_INDEX = 20; 48 | export const BUFFER_RAM_AFTER_INDEX = 24; 49 | 50 | // Buffere index - Startup 51 | export const BUFFER_DURATION_INDEX = 4; 52 | 53 | // Template - Startup 54 | export const TEMPLATE_PERFORMANCE_TIMING = `performance_timing___${createId()}`; 55 | 56 | // Template - Main 57 | export const TEMPLATE_SOCKET_CLASS = `socket_class___${createId()}`; 58 | export const TEMPLATE_SOCKET_INSTANCE = `socket_instance___${createId()}`; 59 | export const TEMPLATE_SOCKET_ON_DATA = `socket_on_data___${createId()}`; 60 | export const TEMPLATE_BUFFER = `buffer___${createId()}`; 61 | export const TEMPLATE_BENCHMARK = `benchmark___${createId()}`; 62 | export const TEMPLATE_GENERATOR = `generator___${createId()}`; 63 | export const TEMPLATE_TMP = `tmp___${createId()}`; 64 | export const TEMPLATE_BLACKBOX = `blackbox___${createId()}`; 65 | 66 | export const NODE_MAIN_COLUMNS = [ 67 | "run", 68 | "iteration", 69 | "cpu", 70 | "ram", 71 | ]; 72 | 73 | export const NODE_STARTUP_COLUMNS = [ 74 | "iteration", 75 | "duration", 76 | ]; 77 | 78 | export const CSV_COLUMN_MAP = { 79 | main: [ 80 | "run", 81 | "iteration", 82 | "cpu", 83 | "ram", 84 | ], 85 | startup: [ 86 | "iteration", 87 | "duration", 88 | ] 89 | }; 90 | -------------------------------------------------------------------------------- /src/csv.js: -------------------------------------------------------------------------------- 1 | export const row = (values) => `${values.join(",")}\n`; 2 | export const header = (values) => `${values.map((v) => `"${v}"`).join(",")}\n`; 3 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { mkdir, rm } from "node:fs/promises" 2 | import { 3 | ISITFAST_PATH, 4 | ISITFAST_COMPILE_PATH, 5 | ISITFAST_RESULTS_PATH, 6 | ISITFAST_BASELINE_PATH, 7 | BENCHMARKS, 8 | } from "./constants.js"; 9 | import { 10 | // baselineCompileMainNode, 11 | // baselineCompileStartupNode, 12 | customCompileMainNode, 13 | // customCompileStartupNode, 14 | } from "./compile/index.js"; 15 | import { init, next } from "./balancer.js"; 16 | 17 | (async () => { 18 | try { 19 | console.log("REMOVE .isitfast/compile"); 20 | await rm(ISITFAST_COMPILE_PATH, { recursive: true, force: true }); 21 | } catch { 22 | // .. 23 | } 24 | 25 | try { 26 | console.log("REMOVE .isitfast/compile"); 27 | await rm(ISITFAST_RESULTS_PATH, { recursive: true, force: true }); 28 | } catch { 29 | // .. 30 | } 31 | 32 | try { 33 | console.log("REMOVE .isitfast/baseline"); 34 | await rm(ISITFAST_BASELINE_PATH, { recursive: true, force: true }); 35 | } catch { 36 | // .. 37 | } 38 | 39 | try { 40 | console.log("CREATE .isitfast"); 41 | await mkdir(ISITFAST_PATH); 42 | } catch { 43 | // .. 44 | } 45 | 46 | console.log("CREATE .isitfast/compile"); 47 | console.log("CREATE .isitfast/results"); 48 | await Promise.all([ 49 | mkdir(ISITFAST_COMPILE_PATH), 50 | mkdir(ISITFAST_RESULTS_PATH), 51 | mkdir(ISITFAST_BASELINE_PATH), 52 | ]); 53 | 54 | console.log("COMPILE START"); 55 | await Promise.all([ 56 | // baselineCompileMainNode(), 57 | // baselineCompileStartupNode(), 58 | customCompileMainNode(), 59 | // customCompileStartupNode(), 60 | ]); 61 | console.log("COMPILE END"); 62 | 63 | console.log(BENCHMARKS); 64 | 65 | init(); 66 | next(); 67 | 68 | console.log("DONE"); 69 | })(); 70 | 71 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | export const randomItem = (active, items) => { 2 | const length = items.length; 3 | 4 | while (true) { 5 | const index = Math.floor(Math.random() * (length - 1)); 6 | const value = items[index]; 7 | 8 | if(value !== active || length === 1) { 9 | return value; 10 | } 11 | } 12 | }; 13 | 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "checkJs": true, 5 | "module": "NodeNext", 6 | "target": "ESNext", 7 | "moduleResolution": "nodenext", 8 | "esModuleInterop": true 9 | }, 10 | "include": ["src"], 11 | "exclude": ["node_modules"] 12 | } 13 | --------------------------------------------------------------------------------