├── replit.nix
├── .gitpod.yml
├── index.html
├── index.ts
├── .replit
├── LICENSE
├── README.md
├── package.json
├── utils.ts
├── chart_template.svg
├── .gitignore
├── tsconfig.json
├── popular_chart_AMD_EPYC_7B13.svg
├── popular_chart_Apple_M1.svg
├── chart_gen.ts
├── all_chart_AMD_EPYC_7B13.svg
├── all_chart_Apple_M1.svg
├── dynamic_deps_bench.ts
└── computers_bench.ts
/replit.nix:
--------------------------------------------------------------------------------
1 | { pkgs }: {
2 | deps = [
3 | pkgs.yarn
4 | pkgs.esbuild
5 | pkgs.nodejs-16_x
6 |
7 | pkgs.nodePackages.typescript
8 | pkgs.nodePackages.typescript-language-server
9 | ];
10 | }
--------------------------------------------------------------------------------
/.gitpod.yml:
--------------------------------------------------------------------------------
1 | # This configuration file was automatically generated by Gitpod.
2 | # Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file)
3 | # and commit this file to your remote git repository to share the goodness with others.
4 |
5 | tasks:
6 | - init: npm install
7 | command: npm run start
8 |
9 |
10 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Bench
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/index.ts:
--------------------------------------------------------------------------------
1 | // import BenchWorker from './computers_bench?worker'
2 | // const worker = new BenchWorker()
3 |
4 | import { test } from './computers_bench'
5 |
6 | const { table } = console
7 | console.table = function (data) {
8 | const pre = document.createElement('pre')
9 | pre.innerHTML = JSON.stringify(data, null, 2)
10 | document.body.appendChild(pre)
11 | table.call(this, data)
12 | }
13 |
14 | test()
15 |
--------------------------------------------------------------------------------
/.replit:
--------------------------------------------------------------------------------
1 | run = "npm run start"
2 |
3 | [languages.typescript]
4 | pattern = "**/{*.ts,*.js,*.tsx,*.jsx}"
5 | syntax = "typescript"
6 |
7 | [languages.typescript.languageServer]
8 | start = [ "typescript-language-server", "--stdio" ]
9 |
10 | [packager]
11 | language = "nodejs"
12 |
13 | [packager.features]
14 | enabledForHosting = false
15 | packageSearch = true
16 | guessImports = true
17 |
18 | [env]
19 | XDG_CONFIG_HOME = "/home/runner/.config"
20 |
21 | [nix]
22 | channel = "stable-21_11"
23 |
24 | [gitHubImport]
25 | requiredFiles = [".replit", "replit.nix", ".config"]
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Arutyunyan Artyom
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 | This benchmark measured computation of complex computed reactive unit when it deep children change.
2 |
3 | The rules:
4 |
5 | - A user space computations should be dumb as possible, to prevent it impact to the measurement. Also, it should return a new value each time to force all dependencies recomputation. For these purposes we use integer summarization, as it light, pure and plain for JIT.
6 | - Each update for each library should **not** go one after the other to prevent unexpected JIT overoptimizations. It is not how real applications work. In the iteration loop we apply the update (iteration counter) only once to each library. To prevent performance influence between two nearby tests we shuffle the list of tests before each iteration.
7 | - To prevent GC influence there is a minimum timeout after each iteration. It is not very honest, but there is no clear way to measure the library overhead and it GC usage.
8 |
9 | The charts shows median value. If you will start the bench by yourself you could see average, minimum and maximum (5%) values in your console.
10 |
11 | The results above is a median value in percent from a fastest library for each iteration loop.
12 |
13 | > [Notes about Reatom performance](https://www.reatom.dev/#how-performant-reatom-is) 🤗
14 |
15 | > Run it localy to see detailed numbers (node 18 required).
16 |
17 | ## Results
18 |
19 | ### AMD_EPYC_7B13
20 |
21 | 
22 |
23 |
24 | all results
25 |
26 | 
27 |
28 |
29 |
30 |
31 |
32 | ### Apple_M1
33 |
34 | 
35 |
36 |
37 | all results
38 |
39 | 
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "reactive-computed-bench",
3 | "private": true,
4 | "description": "Benchmarks of complex computed update performance",
5 | "scripts": {
6 | "start": "tsx computers_bench.ts",
7 | "start:mem": "node --expose-gc --import=tsx computers_bench.ts",
8 | "site:dev": "vite dev",
9 | "site:build": "vite build",
10 | "update": "npx npm-check-updates -u"
11 | },
12 | "author": "artalar",
13 | "license": "MIT",
14 | "readme": "README.md",
15 | "repository": {
16 | "type": "git",
17 | "url": "git+ssh://git@github.com/artalar/reactive-computed-bench.git"
18 | },
19 | "engines": {
20 | "node": ">=18.0.0"
21 | },
22 | "bugs": {
23 | "url": "https://github.com/artalar/reactive-computed-bench/issues"
24 | },
25 | "homepage": "https://github.com/artalar/reactive-computed-bench/tree/main",
26 | "devDependencies": {
27 | "@artalar/act": "^3.2.1",
28 | "@frp-ts/core": "^1.0.0-beta.6",
29 | "@ngrx/store": "^17.1.1",
30 | "@preact/signals-core": "^1.6.0",
31 | "@reatom/core": "^3.7.0",
32 | "@types/node": "latest",
33 | "@webreflection/signal": "^2.1.1",
34 | "cellx": "^1.10.30",
35 | "effector": "^23.2.0",
36 | "jotai": "^2.7.1",
37 | "mobx": "^6.12.1",
38 | "mol_wire_lib": "^1.0.978",
39 | "nanostores": "^0.10.0",
40 | "react": "^18.2.0",
41 | "redux": "^5.0.1",
42 | "reselect": "^5.1.0",
43 | "s-js": "^0.4.9",
44 | "solid-js": "^1.8.16",
45 | "spred": "^0.34.0",
46 | "tsx": "^4.7.1",
47 | "typescript": "^5.4.3",
48 | "usignal": "^0.9.0",
49 | "uvu": "^0.5.6",
50 | "vite": "^5.2.5",
51 | "whatsup": "^2.6.0",
52 | "wonka": "^6.3.4"
53 | },
54 | "keywords": [
55 | "reactive",
56 | "reactivity",
57 | "computed",
58 | "bench",
59 | "perf"
60 | ]
61 | }
62 |
--------------------------------------------------------------------------------
/utils.ts:
--------------------------------------------------------------------------------
1 | export type Rec = Record
2 |
3 | export const POSITION_KEY = 'pos %'
4 |
5 | export function printLogs(results: Rec>) {
6 | const medFastest = Math.min(...Object.values(results).map(({ med }) => med))
7 |
8 | const tabledData = Object.entries(results)
9 | .sort(([, { med: a }], [, { med: b }]) => a - b)
10 | .reduce((acc, [name, { min, med, max }]) => {
11 | acc[name] = {
12 | [POSITION_KEY]: ((medFastest / med) * 100).toFixed(0),
13 | 'avg ms': med.toFixed(3),
14 | 'min ms': min.toFixed(5),
15 | 'med ms': med.toFixed(5),
16 | 'max ms': max.toFixed(5),
17 | }
18 | return acc
19 | }, {} as Rec)
20 |
21 | console.table(tabledData)
22 |
23 | return tabledData
24 | }
25 |
26 | export function formatPercent(n = 0) {
27 | return `${n < 1 ? ` ` : ``}${(n * 100).toFixed(0)}%`
28 | }
29 |
30 | export function formatLog(values: Array) {
31 | return {
32 | min: min(values),
33 | med: med(values),
34 | max: max(values),
35 | }
36 | }
37 |
38 | export function med(values: Array) {
39 | if (values.length === 0) return 0
40 |
41 | values = values.map((v) => +v)
42 |
43 | values.sort((a, b) => (a - b < 0 ? 1 : -1))
44 |
45 | var half = Math.floor(values.length / 2)
46 |
47 | if (values.length % 2) return values[half]!
48 |
49 | return (values[half - 1]! + values[half]!) / 2.0
50 | }
51 |
52 | export function min(values: Array) {
53 | if (values.length === 0) return 0
54 |
55 | values = values.map((v) => +v)
56 |
57 | values.sort((a, b) => (a - b < 0 ? -1 : 1))
58 |
59 | const limit = Math.floor(values.length / 20)
60 |
61 | return values[limit]!
62 | }
63 |
64 | export function max(values: Array) {
65 | if (values.length === 0) return 0
66 |
67 | values = values.map((v) => +v)
68 |
69 | values.sort((a, b) => (a - b < 0 ? -1 : 1))
70 |
71 | const limit = values.length - 1 - Math.floor(values.length / 20)
72 |
73 | return values[limit]!
74 | }
75 |
--------------------------------------------------------------------------------
/chart_template.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 |
78 | # Next.js build output
79 | .next
80 |
81 | # Nuxt.js build / generate output
82 | .nuxt
83 | dist
84 |
85 | # Gatsby files
86 | .cache/
87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
88 | # https://nextjs.org/blog/next-9-1#public-directory-support
89 | # public
90 |
91 | # vuepress build output
92 | .vuepress/dist
93 |
94 | # Serverless directories
95 | .serverless/
96 |
97 | # FuseBox cache
98 | .fusebox/
99 |
100 | # DynamoDB Local files
101 | .dynamodb/
102 |
103 | # TernJS port file
104 | .tern-port
105 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */
4 | /* Basic Options */
5 | // "incremental": true, /* Enable incremental compilation */
6 | "target": "ESNEXT", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
7 | "module": "ESNext", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
8 | // "lib": [], /* Specify library files to be included in the compilation. */
9 | // "allowJs": true, /* Allow javascript files to be compiled. */
10 | // "checkJs": true, /* Report errors in .js files. */
11 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
12 | // "declaration": true, /* Generates corresponding '.d.ts' file. */
13 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
14 | // "sourceMap": true, /* Generates corresponding '.map' file. */
15 | // "outFile": "./", /* Concatenate and emit output to single file. */
16 | // "outDir": "./", /* Redirect output structure to the directory. */
17 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
18 | // "composite": true, /* Enable project compilation */
19 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
20 | // "removeComments": true, /* Do not emit comments to output. */
21 | "noEmit": true, /* Do not emit outputs. */
22 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */
23 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
24 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
25 | /* Strict Type-Checking Options */
26 | "strict": true, /* Enable all strict type-checking options. */
27 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
28 | // "strictNullChecks": true, /* Enable strict null checks. */
29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */
30 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
31 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
33 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
34 | /* Additional Checks */
35 | // "noUnusedLocals": true, /* Report errors on unused locals. */
36 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
37 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
38 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
39 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
40 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */
41 | // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
42 | /* Module Resolution Options */
43 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
44 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
45 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
46 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
47 | "typeRoots": [
48 | "./node_modules/@types",
49 | ], /* List of folders to include type definitions from. */
50 | // "types": [], /* Type declaration files to be included in compilation. */
51 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
52 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
53 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
54 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
55 | /* Experimental Options */
56 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
57 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
58 | /* Advanced Options */
59 | "skipLibCheck": true, /* Skip type checking of declaration files. */
60 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
61 | },
62 | "exclude": [
63 | "node_modules",
64 | ".build"
65 | ]
66 | }
--------------------------------------------------------------------------------
/popular_chart_AMD_EPYC_7B13.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/popular_chart_Apple_M1.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/chart_gen.ts:
--------------------------------------------------------------------------------
1 | import { writeFile, readFile } from 'fs/promises'
2 | import { cpus } from 'os'
3 | import path from 'path'
4 | import { formatLog, Rec } from './utils'
5 |
6 | type LibName = string
7 | type LibValues = ReturnType
8 | type IterationName = number
9 | type IterationValues = Record
10 | type BenchResults = Record
11 | type Iterations = Array['length']
12 | type Point = [number, number]
13 | type ChartData = Array<{
14 | name: string
15 | version: string
16 | color: string
17 | min: Point[]
18 | med: Point[]
19 | max: Point[]
20 | medValue: number
21 | labelY: number
22 | }>
23 |
24 | const DOWNLOAD_LIMIT = 500
25 |
26 | const CPU = cpus()[0]?.model?.replace(/ /g, '_') ?? 'unknown_cpu'
27 | const POPULAR_CHART_PATH = `./popular_chart_${CPU}.svg`
28 | const ALL_CHART_PATH = `./all_chart_${CPU}.svg`
29 | const CHART_TEMPLATE = './chart_template.svg'
30 | const START_MARK = ''
31 | const END_MARK = ''
32 | const REGEX = new RegExp(`${START_MARK}(.*)${END_MARK}`, 'gms')
33 |
34 | const X_START = 100
35 | const X_STEP = 230
36 | const Y_START = 20
37 | const Y_RANGE = 500
38 | const LABEL_HEIGHT = 16
39 |
40 | const hsl = (i: number, length: number) => {
41 | const h = Math.round((i * 360) / length) % 360
42 | return `hsl(${h}, 90%, 45%)`
43 | }
44 |
45 | const PACKAGE_NAMES: Rec = {
46 | 'effector.fork': 'effector',
47 | frpts: '@frp-ts/core',
48 | mol: 'mol_wire_lib',
49 | preact: '@preact/signals-core',
50 | reatom: '@reatom/core',
51 | solid: 'solid-js',
52 | v4: 'v4',
53 | }
54 |
55 | export async function genChart(allResults: BenchResults) {
56 | const popularLibs = new Array()
57 | for (const libName of Object.keys(Object.values(allResults)[0]!)) {
58 | const moduleName = PACKAGE_NAMES[libName] ?? libName
59 | const moduleUrlName = moduleName.replace('/', '%2F')
60 | const downloadsUrl = `https://api.npmjs.org/versions/${moduleUrlName}/last-week`
61 | const downloads = await fetch(downloadsUrl)
62 | .then((r) => r.json())
63 | .then(({ downloads }: { downloads: Rec }) =>
64 | Object.values(downloads).reduce((acc, v) => acc + v, 0),
65 | )
66 | .catch((error) => {
67 | console.error(`Failed to fetch downloads for ${name}`)
68 | console.log(error)
69 | return 0
70 | })
71 | if (downloads > DOWNLOAD_LIMIT) popularLibs.push(libName)
72 | }
73 | const popularResults = Object.fromEntries(
74 | Object.entries(allResults).map(([iteration, iterationValues]) => [
75 | iteration,
76 | Object.fromEntries(
77 | Object.entries(iterationValues).filter(([libName]) =>
78 | popularLibs.includes(libName),
79 | ),
80 | ),
81 | ]),
82 | )
83 |
84 | const template = await readFile(CHART_TEMPLATE, 'utf8')
85 | const allData = await getChartData(allResults)
86 | const popularData = await getChartData(popularResults)
87 | const svgAll = allData.map(getLibSVG).join('')
88 | const svgPopular = popularData.map(getLibSVG).join('')
89 |
90 | await writeFile(
91 | ALL_CHART_PATH,
92 | template.replace(REGEX, START_MARK + svgAll + END_MARK),
93 | )
94 | await writeFile(
95 | POPULAR_CHART_PATH,
96 | template.replace(REGEX, START_MARK + svgPopular + END_MARK),
97 | )
98 |
99 | let readme = await readFile('./README.md', 'utf8')
100 | if (readme.includes(CPU)) {
101 | readme = readme.replace(
102 | new RegExp(`### ${CPU}(.|\n)*`),
103 | `### ${CPU}
104 |
105 | 
106 |
107 |
108 | all results
109 |
110 | 
111 |
112 |
113 |
114 | `,
115 | )
116 | } else {
117 | readme = readme.replace(
118 | '## Results',
119 | `## Results
120 |
121 | ### ${CPU}
122 |
123 | 
124 |
125 |
126 | all results
127 |
128 | 
129 |
130 |
131 |
132 | `,
133 | )
134 | }
135 |
136 | await writeFile('./README.md', readme)
137 | }
138 |
139 | async function getChartData(results: BenchResults): Promise {
140 | const fastest = {
141 | min: [] as Array,
142 | med: [] as Array,
143 | max: [] as Array,
144 | }
145 | const libsResults: Rec = {}
146 | const data: ChartData = []
147 |
148 | let i = -1
149 | for (const [iteration, iterationValues] of Object.entries(results)) {
150 | i++
151 |
152 | if (!fastest.min[i]) fastest.min[i] = Infinity
153 | if (!fastest.med[i]) fastest.med[i] = Infinity
154 | if (!fastest.max[i]) fastest.max[i] = Infinity
155 |
156 | for (const [lib, libValues] of Object.entries(iterationValues)) {
157 | const group = (libsResults[lib] ??= {
158 | min: [],
159 | med: [],
160 | max: [],
161 | })
162 |
163 | group.min.push(libValues.min)
164 | group.med.push(libValues.med)
165 | group.max.push(libValues.max)
166 |
167 | if (libValues.min < fastest.min[i]!) {
168 | fastest.min[i] = libValues.min
169 | }
170 |
171 | if (libValues.med < fastest.med[i]!) {
172 | fastest.med[i] = libValues.med
173 | }
174 |
175 | if (libValues.max < fastest.max[i]!) {
176 | fastest.max[i] = libValues.max
177 | }
178 | }
179 | }
180 |
181 | const libsNames = Object.keys(libsResults).sort()
182 |
183 | const toPoint = (v: number, i: number) => getPoint(v, fastest.med[i]!, i)
184 | for (const [lib, libResults] of Object.entries(libsResults)) {
185 | const moduleName = PACKAGE_NAMES[lib] || lib
186 | const color = hsl(libsNames.indexOf(lib), libsNames.length)
187 | const min = libResults.min.map(toPoint)
188 | const med = libResults.med.map(toPoint)
189 | const max = libResults.max.map(toPoint)
190 | const medValue = libResults.med[0]
191 | const modulePath = path.join(
192 | __dirname,
193 | 'node_modules',
194 | moduleName,
195 | 'package.json',
196 | )
197 | const file = await readFile(modulePath, 'utf8').catch(
198 | () => '{"version": "0.0.0"}',
199 | )
200 | const version = JSON.parse(file).version
201 |
202 | data.push({
203 | name: lib,
204 | version,
205 | color,
206 | min,
207 | med,
208 | max,
209 | medValue,
210 | labelY: 0,
211 | })
212 | }
213 |
214 | data.sort((a, b) => a.medValue - b.medValue)
215 |
216 | data.forEach((el, i, arr) => {
217 | el.labelY = el.med[0]![1]! + 3
218 |
219 | if (i && el.labelY - arr[i - 1]!.labelY < LABEL_HEIGHT) {
220 | el.labelY = arr[i - 1]!.labelY + LABEL_HEIGHT
221 | }
222 | })
223 |
224 | return data
225 | }
226 |
227 | function getPoint(value: number, minValue: number, step: number): Point {
228 | const x = X_START + X_STEP * step
229 | const y = Y_START + Y_RANGE * (1 - minValue / value)
230 |
231 | return [x, y]
232 | }
233 |
234 | function getLibSVG({
235 | name: lib,
236 | color,
237 | med,
238 | labelY,
239 | version,
240 | }: ChartData[number]) {
241 | return `
242 | ${lib} ${version}
248 |
254 | `
255 | }
256 |
--------------------------------------------------------------------------------
/all_chart_AMD_EPYC_7B13.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/all_chart_Apple_M1.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/dynamic_deps_bench.ts:
--------------------------------------------------------------------------------
1 | import { forEach } from 'wonka'
2 | import { printLogs, formatLog, POSITION_KEY } from './utils'
3 |
4 | async function testAggregateGrowing(count: number, method: 'push' | 'unshift') {
5 | const mol_wire_lib = await import('mol_wire_lib')
6 | const { $mol_wire_atom } = mol_wire_lib.default
7 |
8 | const { atom, createCtx } = await import('@reatom/core')
9 |
10 | const V4 = await import('../../reatom4/packages/core/build')
11 |
12 | const { observable, computed, autorun, configure } = await import('mobx')
13 | configure({ enforceActions: 'never' })
14 |
15 | const { act } = await import('@artalar/act')
16 |
17 | const molAtoms = [new $mol_wire_atom(`0`, (next: number = 0) => next)]
18 | const reAtoms = [atom(0, `${0}`)]
19 | const V4Atoms = [V4.atom(0, `${0}`)]
20 | const mobxAtoms = [observable.box(0, { name: `${0}` })]
21 | const actAtoms = [act(0)]
22 |
23 | const molAtom = new $mol_wire_atom(`sum`, () =>
24 | molAtoms.reduce((sum, atom) => sum + atom.sync(), 0),
25 | )
26 | const reAtom = atom(
27 | (ctx) => reAtoms.reduce((sum, atom) => sum + ctx.spy(atom), 0),
28 | `sum`,
29 | )
30 | const V4Atom = V4.atom(
31 | () => V4Atoms.reduce((sum, atom) => sum + atom(), 0),
32 | `sum`,
33 | )
34 | const mobxAtom = computed(
35 | () => mobxAtoms.reduce((sum, atom) => sum + atom.get(), 0),
36 | { name: `sum` },
37 | )
38 | const actAtom = act(() => actAtoms.reduce((sum, a) => sum + a(), 0))
39 |
40 | const ctx = createCtx()
41 | const V4Root = V4.AsyncContext.Snapshot.createRoot()
42 |
43 | ctx.subscribe(reAtom, () => {})
44 | V4.effect(() => V4Atom()).run(V4Root.frame)
45 | molAtom.sync()
46 | autorun(() => mobxAtom.get())
47 | actAtom.subscribe(() => {})
48 |
49 | const reatomLogs = new Array()
50 | const V4Logs = new Array()
51 | const molLogs = new Array()
52 | const mobxLogs = new Array()
53 | const actLogs = new Array()
54 | let i = 1
55 | while (i++ < count) {
56 | const startReatom = performance.now()
57 | reAtoms[method](atom(i, `${i}`))
58 | reAtoms.at(-2)!(ctx, i)
59 | reatomLogs.push(performance.now() - startReatom)
60 |
61 | const startMol = performance.now()
62 | molAtoms[method](new $mol_wire_atom(`${i}`, (next: number = i) => next))
63 | molAtoms.at(-2)!.put(i)
64 | molAtom.sync()
65 | molLogs.push(performance.now() - startMol)
66 |
67 | const startMobx = performance.now()
68 | mobxAtoms[method](observable.box(i, { name: `${i}` }))
69 | mobxAtoms.at(-2)!.set(i)
70 | mobxLogs.push(performance.now() - startMobx)
71 |
72 | const startV4 = performance.now()
73 | V4Root.run(() => {
74 | V4Atoms[method](V4.atom(i))
75 | V4Atoms.at(-2)!(i)
76 | V4.notify()
77 | })
78 | V4Logs.push(performance.now() - startV4)
79 |
80 | const startAct = performance.now()
81 | actAtoms[method](act(i))
82 | actAtoms.at(-2)!(i)
83 | act.notify()
84 | actLogs.push(performance.now() - startAct)
85 |
86 | await new Promise((resolve) => setTimeout(resolve, 0))
87 | }
88 |
89 | if (
90 | new Set([
91 | molAtom.sync(),
92 | ctx.get(reAtom),
93 | mobxAtom.get(),
94 | actAtom(),
95 | V4Root.run(V4Atom),
96 | ]).size > 1
97 | ) {
98 | throw new Error(
99 | 'Mismatch: ' +
100 | JSON.stringify({
101 | mol: molAtom.sync(),
102 | reatom: ctx.get(reAtom),
103 | mobx: mobxAtom.get(),
104 | act: actAtom(),
105 | V4: V4Root.run(V4Atom),
106 | }),
107 | )
108 | }
109 |
110 | console.log(
111 | `Median of sum calc of reactive nodes in list from 1 to ${count} (with "${method}")`,
112 | )
113 |
114 | return printLogs({
115 | reatom: formatLog(reatomLogs),
116 | $mol_wire: formatLog(molLogs),
117 | mobx: formatLog(mobxLogs),
118 | // act: formatLog(actLogs),
119 | V4: formatLog(V4Logs),
120 | })
121 | }
122 |
123 | async function testAggregateShrinking(count: number, method: 'pop' | 'shift') {
124 | const mol_wire_lib = await import('mol_wire_lib')
125 | const { $mol_wire_atom } = mol_wire_lib.default
126 |
127 | const { atom, createCtx } = await import('@reatom/core')
128 |
129 | const V4 = await import('../../reatom4/packages/core/build')
130 |
131 | const { observable, computed, autorun, configure } = await import('mobx')
132 | configure({ enforceActions: 'never' })
133 |
134 | const molAtoms = Array.from(
135 | { length: count },
136 | (_, i) => new $mol_wire_atom(`${i}`, (next: number = 1) => next),
137 | )
138 | const reAtoms = Array.from({ length: count }, (_, i) => atom(1, `${i}`))
139 | const V4Atoms = Array.from({ length: count }, (_, i) => V4.atom(1, `${i}`))
140 | const mobxAtoms = Array.from({ length: count }, (_, i) =>
141 | observable.box(1, { name: `${i}` }),
142 | )
143 |
144 | const molAtom = new $mol_wire_atom(`sum`, () =>
145 | molAtoms.reduce((sum, atom) => sum + atom.sync(), 0),
146 | )
147 | const reAtom = atom(
148 | (ctx) => reAtoms.reduce((sum, atom) => sum + ctx.spy(atom), 0),
149 | `sum`,
150 | )
151 | const V4Atom = V4.atom(
152 | () => V4Atoms.reduce((sum, atom) => sum + atom(), 0),
153 | `sum`,
154 | )
155 | const mobxAtom = computed(
156 | () => mobxAtoms.reduce((sum, atom) => sum + atom.get(), 0),
157 | { name: `sum` },
158 | )
159 |
160 | const ctx = createCtx()
161 | const V4Root = V4.AsyncContext.Snapshot.createRoot()
162 |
163 | ctx.subscribe(reAtom, () => {})
164 | V4.effect(() => V4Atom()).run(V4Root.frame)
165 | molAtom.sync()
166 | autorun(() => mobxAtom.get())
167 |
168 | const reatomLogs = new Array()
169 | const V4Logs = new Array()
170 | const molLogs = new Array()
171 | const mobxLogs = new Array()
172 | let i = 1
173 | while (i++ < count) {
174 | const startReatom = performance.now()
175 | reAtoms[method]()!(ctx, i)
176 | reatomLogs.push(performance.now() - startReatom)
177 |
178 | const startV4 = performance.now()
179 | V4Root.run(() => {
180 | V4Atoms[method]()!(i)
181 | V4.notify()
182 | })
183 | V4Logs.push(performance.now() - startV4)
184 |
185 | const startMol = performance.now()
186 | molAtoms[method]()!.put(i)
187 | molAtom.sync()
188 | molLogs.push(performance.now() - startMol)
189 |
190 | const startMobx = performance.now()
191 | mobxAtoms[method]()!.set(i)
192 | mobxLogs.push(performance.now() - startMobx)
193 |
194 | await new Promise((resolve) => setTimeout(resolve, 0))
195 | }
196 |
197 | if (
198 | new Set([
199 | molAtom.sync(),
200 | ctx.get(reAtom),
201 | V4Root.run(V4Atom),
202 | mobxAtom.get(),
203 | ]).size > 1
204 | ) {
205 | throw new Error(
206 | 'Mismatch: ' +
207 | JSON.stringify({
208 | mol: molAtom.sync(),
209 | reatom: ctx.get(reAtom),
210 | V4: V4Root.run(V4Atom),
211 | mobx: mobxAtom.get(),
212 | }),
213 | )
214 | }
215 |
216 | console.log(
217 | `Median of sum calc of reactive nodes in list from ${count} to 1 (with "${method}")`,
218 | )
219 |
220 | return printLogs({
221 | reatom: formatLog(reatomLogs),
222 | $mol_wire: formatLog(molLogs),
223 | mobx: formatLog(mobxLogs),
224 | // act: formatLog(actLogs),
225 | V4: formatLog(V4Logs),
226 | })
227 | }
228 |
229 | async function testParent(count: number) {
230 | const mol_wire_lib = await import('mol_wire_lib')
231 | const { $mol_wire_atom } = mol_wire_lib.default
232 |
233 | const { atom, createCtx } = await import('@reatom/core')
234 |
235 | const V4 = await import('../../reatom4/packages/core/build')
236 |
237 | const { observable, computed, autorun, configure } = await import('mobx')
238 | configure({ enforceActions: 'never' })
239 |
240 | const molAtom = new $mol_wire_atom(`0`, (next: number = 0) => next)
241 | const molAtoms = []
242 | const reAtom = atom(0, `${0}`)
243 | const V4Atom = V4.atom(0, `${0}`)
244 | const mobxAtom = observable.box(0, { name: `${0}` })
245 |
246 | const ctx = createCtx()
247 | const V4Root = V4.AsyncContext.Snapshot.createRoot()
248 |
249 | {
250 | let i = count
251 | while (i--) {
252 | const molPubAtom = new $mol_wire_atom(`${i}`, () => molAtom.sync())
253 | molPubAtom.sync()
254 | molAtoms.push(molPubAtom)
255 |
256 | ctx.subscribe(
257 | atom((ctx) => ctx.spy(reAtom)),
258 | () => {},
259 | )
260 |
261 | const V4DepAtom = V4.atom(() => V4Atom())
262 | V4.effect(() => V4DepAtom()).run(V4Root.frame)
263 |
264 | const mobxDepAtom = computed(() => mobxAtom.get())
265 | autorun(() => mobxDepAtom.get())
266 | }
267 | }
268 |
269 | const reatomLogs = new Array()
270 | const V4Logs = new Array()
271 | const molLogs = new Array()
272 | const mobxLogs = new Array()
273 | let i = count
274 | while (i--) {
275 | const startReatom = performance.now()
276 | reAtom(ctx, i)
277 | reatomLogs.push(performance.now() - startReatom)
278 |
279 | const startV4 = performance.now()
280 | V4Root.run(() => {
281 | V4Atom(i)
282 | V4.notify()
283 | })
284 | V4Logs.push(performance.now() - startV4)
285 |
286 | const startMol = performance.now()
287 | molAtom.put(i)
288 | molAtoms.forEach((atom) => atom.sync())
289 | molLogs.push(performance.now() - startMol)
290 |
291 | const startMobx = performance.now()
292 | mobxAtom.set(i)
293 | mobxLogs.push(performance.now() - startMobx)
294 |
295 | await new Promise((resolve) => setTimeout(resolve, 0))
296 | }
297 |
298 | if (
299 | new Set([
300 | molAtom.sync(),
301 | ctx.get(reAtom),
302 | V4Root.run(V4Atom),
303 | mobxAtom.get(),
304 | ]).size > 1
305 | ) {
306 | throw new Error(
307 | 'Mismatch: ' +
308 | JSON.stringify({
309 | mol: molAtom.sync(),
310 | reatom: ctx.get(reAtom),
311 | V4: V4Root.run(V4Atom),
312 | mobx: mobxAtom.get(),
313 | }),
314 | )
315 | }
316 |
317 | console.log(`Median of update 1 node with ${count} reactive children`)
318 |
319 | return printLogs({
320 | reatom: formatLog(reatomLogs),
321 | $mol_wire: formatLog(molLogs),
322 | mobx: formatLog(mobxLogs),
323 | // act: formatLog(actLogs),
324 | V4: formatLog(V4Logs),
325 | })
326 | }
327 |
328 | ;(async () => {
329 | // const subscribers = [2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]
330 | const subscribers = [1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 10]
331 |
332 | for (const i of subscribers) {
333 | var results = [
334 | await testAggregateGrowing(i, 'push'),
335 | await testAggregateGrowing(i, 'unshift'),
336 | await testAggregateShrinking(i, 'pop'),
337 | await testAggregateShrinking(i, 'shift'),
338 | // await testParent(i),
339 | ]
340 | }
341 |
342 | console.log('\nAVERAGE for', subscribers.join(','), 'subscribers')
343 |
344 | Object.keys(results![0])
345 | .map((name) => ({
346 | name,
347 | pos:
348 | results!.reduce((acc, log) => +log[name][POSITION_KEY] + acc, 0) /
349 | results!.length,
350 | }))
351 | .sort((a, b) => a.pos - b.pos)
352 | .forEach(({ name, pos }) => console.log(pos, name))
353 |
354 | process.exit()
355 | })()
356 |
--------------------------------------------------------------------------------
/computers_bench.ts:
--------------------------------------------------------------------------------
1 | // TODO move to test source
2 | import type { Source as WSource } from 'wonka'
3 | import { genChart } from './chart_gen'
4 |
5 | import { Rec, formatLog, printLogs } from './utils'
6 |
7 | type UpdateLeaf = (value: number) => void
8 |
9 | type Setup = (hooks: {
10 | listener: (computedValue: number) => void
11 | startCreation: () => void
12 | endCreation: () => void
13 | }) => Promise
14 |
15 | // There is a few tests skipped
16 | // coz I don't know how to turn off their batching
17 | // which delays computations
18 |
19 | const testComputers = setupComputersTest({
20 | // async native({ listener, startCreation, endCreation }) {
21 | // startCreation()
22 |
23 | // const entry = (i = 0) => i
24 | // const a = (i: number) => entry(i)
25 | // const b = (i: number) => a(i) + 1
26 | // const c = (i: number) => a(i) + 1
27 | // const d = (i: number) => b(i) + c(i)
28 | // const e = (i: number) => d(i) + 1
29 | // const f = (i: number) => d(i) + e(i)
30 | // const g = (i: number) => d(i) + e(i)
31 | // const h = (i: number) => f(i) + g(i)
32 |
33 | // endCreation()
34 |
35 | // return (i) => listener(h(i))
36 | // },
37 | // async selector({ listener, startCreation, endCreation }) {
38 | // const createSelector = (cb: (input: any) => any) => {
39 | // let input: any, res: any
40 | // return (i: any) => (i === input ? res : (res = cb((input = i))))
41 | // }
42 |
43 | // startCreation()
44 |
45 | // const entry = createSelector((i = 0) => i)
46 | // const a = createSelector((i: number) => entry(i))
47 | // const b = createSelector((i: number) => a(i) + 1)
48 | // const c = createSelector((i: number) => a(i) + 1)
49 | // const d = createSelector((i: number) => b(i) + c(i))
50 | // const e = createSelector((i: number) => d(i) + 1)
51 | // const f = createSelector((i: number) => d(i) + e(i))
52 | // const g = createSelector((i: number) => d(i) + e(i))
53 | // const h = createSelector((i: number) => f(i) + g(i))
54 |
55 | // endCreation()
56 |
57 | // return (i) => listener(h(i))
58 | // },
59 | async spred({ listener, startCreation, endCreation }) {
60 | const { writable, computed } = await import('spred')
61 |
62 | startCreation()
63 |
64 | const entry = writable(0)
65 | const a = computed(() => entry.get())
66 | const b = computed(() => a.get() + 1)
67 | const c = computed(() => a.get() + 1)
68 | const d = computed(() => b.get() + c.get())
69 | const e = computed(() => d.get() + 1)
70 | const f = computed(() => d.get() + e.get())
71 | const g = computed(() => d.get() + e.get())
72 | const h = computed(() => f.get() + g.get())
73 |
74 | h.subscribe(listener)
75 |
76 | endCreation()
77 |
78 | return (i) => entry.set(i)
79 | },
80 | async cellx({ listener, startCreation, endCreation }) {
81 | const { cellx, Cell } = await import('cellx')
82 |
83 | startCreation()
84 |
85 | const entry = cellx(0)
86 | const a = cellx(() => entry())
87 | const b = cellx(() => a() + 1)
88 | const c = cellx(() => a() + 1)
89 | const d = cellx(() => b() + c())
90 | const e = cellx(() => d() + 1)
91 | const f = cellx(() => d() + e())
92 | const g = cellx(() => d() + e())
93 | const h = cellx(() => f() + g())
94 |
95 | h.subscribe((err, v) => listener(h()))
96 |
97 | endCreation()
98 |
99 | return (i) => {
100 | entry(i)
101 | Cell.release()
102 | }
103 | },
104 | async effector({ listener, startCreation, endCreation }) {
105 | const { createEvent, createStore, combine } = await import('effector')
106 |
107 | startCreation()
108 |
109 | const entry = createEvent()
110 | const a = createStore(0).on(entry, (state, v) => v)
111 | const b = a.map((a) => a + 1)
112 | const c = a.map((a) => a + 1)
113 | const d = combine(b, c, (b, c) => b + c)
114 | const e = d.map((d) => d + 1)
115 | const f = combine(d, e, (d, e) => d + e)
116 | const g = combine(d, e, (d, e) => d + e)
117 | const h = combine(f, g, (h1, h2) => h1 + h2)
118 |
119 | h.subscribe(listener)
120 |
121 | endCreation()
122 |
123 | return (i) => entry(i)
124 | },
125 | async 'effector.fork'({ listener, startCreation, endCreation }) {
126 | const { createEvent, createStore, combine, allSettled, fork } =
127 | await import('effector')
128 |
129 | startCreation()
130 |
131 | const entry = createEvent()
132 | const a = createStore(0).on(entry, (state, v) => v)
133 | const b = a.map((a) => a + 1)
134 | const c = a.map((a) => a + 1)
135 | const d = combine(b, c, (b, c) => b + c)
136 | const e = d.map((d) => d + 1)
137 | const f = combine(d, e, (d, e) => d + e)
138 | const g = combine(d, e, (d, e) => d + e)
139 | const h = combine(f, g, (h1, h2) => h1 + h2)
140 |
141 | const scope = fork()
142 |
143 | endCreation()
144 |
145 | return (i) => {
146 | allSettled(entry, { scope, params: i })
147 | // this is not wrong
148 | // coz effector graph a hot always
149 | // and `getState` is doing nothing here
150 | // only internal state reading
151 | listener(scope.getState(h))
152 | }
153 | },
154 | async frpts({ listener, startCreation, endCreation }) {
155 | const { newAtom, combine } = await import('@frp-ts/core')
156 |
157 | startCreation()
158 |
159 | const entry = newAtom(0)
160 | const a = combine(entry, (v) => v)
161 | const b = combine(a, (a) => a + 1)
162 | const c = combine(a, (a) => a + 1)
163 | const d = combine(b, c, (b, c) => b + c)
164 | const e = combine(d, (d) => d + 1)
165 | const f = combine(d, e, (d, e) => d + e)
166 | const g = combine(d, e, (d, e) => d + e)
167 | const h = combine(f, g, (f, g) => f + g)
168 |
169 | h.subscribe({ next: () => listener(h.get()) })
170 |
171 | endCreation()
172 |
173 | return (i) => entry.set(i)
174 | },
175 | async jotai({ listener, startCreation, endCreation }) {
176 | const { atom, createStore } = await import('jotai')
177 |
178 | startCreation()
179 |
180 | const entry = atom(0)
181 | const a = atom((get) => get(entry))
182 | const b = atom((get) => get(a) + 1)
183 | const c = atom((get) => get(a) + 1)
184 | const d = atom((get) => get(b) + get(c))
185 | const e = atom((get) => get(d) + 1)
186 | const f = atom((get) => get(d) + get(e))
187 | const g = atom((get) => get(d) + get(e))
188 | const h = atom((get) => get(f) + get(g))
189 |
190 | const store = createStore()
191 | store.sub(h, () => listener(store.get(h)))
192 |
193 | endCreation()
194 |
195 | return (i) => store.set(entry, i)
196 | },
197 | async mobx({ listener, startCreation, endCreation }) {
198 | const { makeAutoObservable, autorun, configure } = await import('mobx')
199 |
200 | configure({ enforceActions: 'never' })
201 |
202 | startCreation()
203 |
204 | const proxy = makeAutoObservable({
205 | entry: 0,
206 | get a() {
207 | return this.entry
208 | },
209 | get b() {
210 | return this.a + 1
211 | },
212 | get c() {
213 | return this.a + 1
214 | },
215 | get d() {
216 | return this.b + this.c
217 | },
218 | get e() {
219 | return this.d + 1
220 | },
221 | get f() {
222 | return this.d + this.e
223 | },
224 | get g() {
225 | return this.d + this.e
226 | },
227 | get h() {
228 | return this.f + this.g
229 | },
230 | })
231 |
232 | autorun(() => listener(proxy.h))
233 |
234 | endCreation()
235 |
236 | return (i) => (proxy.entry = i)
237 | },
238 | async mol({ listener, startCreation, endCreation }) {
239 | const {
240 | default: { $mol_wire_atom: Atom },
241 | } = await import('mol_wire_lib')
242 |
243 | startCreation()
244 |
245 | const entry = new Atom('entry', (next: number = 0) => next)
246 | const a = new Atom('mA', () => entry.sync())
247 | const b = new Atom('mB', () => a.sync() + 1)
248 | const c = new Atom('mC', () => a.sync() + 1)
249 | const d = new Atom('mD', () => b.sync() + c.sync())
250 | const e = new Atom('mE', () => d.sync() + 1)
251 | const f = new Atom('mF', () => d.sync() + e.sync())
252 | const g = new Atom('mG', () => d.sync() + e.sync())
253 | const h = new Atom('mH', () => f.sync() + g.sync())
254 |
255 | listener(h.sync())
256 |
257 | endCreation()
258 |
259 | return (i) => {
260 | entry.put(i)
261 | // the batch doing the same https://github.com/hyoo-ru/mam_mol/blob/c9cf0faf966c8bb3d0e76339527ef03e03d273e8/wire/fiber/fiber.ts#L31
262 | listener(h.sync())
263 | }
264 | },
265 | // async '@krulod/wire'({ listener, startCreation, endCreation }) {
266 | // const { Atom } = await import('@krulod/wire')
267 |
268 | // startCreation()
269 |
270 | // const entry = new Atom('entry', () => 0)
271 | // const a = new Atom('mA', () => entry.pull())
272 | // const b = new Atom('mB', () => a.pull() + 1)
273 | // const c = new Atom('mC', () => a.pull() + 1)
274 | // const d = new Atom('mD', () => b.pull() + c.pull())
275 | // const e = new Atom('mE', () => d.pull() + 1)
276 | // const f = new Atom('mF', () => d.pull() + e.pull())
277 | // const g = new Atom('mG', () => d.pull() + e.pull())
278 | // const h = new Atom('mH', () => f.pull() + g.pull())
279 |
280 | // listener(h.pull())
281 |
282 | // endCreation()
283 |
284 | // return (i) => {
285 | // entry.put(i)
286 | // listener(h.pull())
287 | // }
288 | // },
289 | async nanostores({ listener, startCreation, endCreation }) {
290 | const { atom, computed } = await import('nanostores')
291 |
292 | startCreation()
293 |
294 | const entry = atom(0)
295 | const a = computed(entry, (entry) => entry)
296 | const b = computed(a, (a) => a + 1)
297 | const c = computed(a, (a) => a + 1)
298 | const d = computed([b, c], (b, c) => b + c)
299 | const e = computed(d, (d) => d + 1)
300 | const f = computed([d, e], (d, e) => d + e)
301 | const g = computed([d, e], (d, e) => d + e)
302 | const h = computed([f, g], (h1, h2) => h1 + h2)
303 |
304 | h.subscribe(listener)
305 |
306 | endCreation()
307 |
308 | return (i) => entry.set(i)
309 | },
310 | async preact({ listener, startCreation, endCreation }) {
311 | const { signal, computed, effect } = await import('@preact/signals-core')
312 |
313 | startCreation()
314 |
315 | const entry = signal(0)
316 | const a = computed(() => entry.value)
317 | const b = computed(() => a.value + 1)
318 | const c = computed(() => a.value + 1)
319 | const d = computed(() => b.value + c.value)
320 | const e = computed(() => d.value + 1)
321 | const f = computed(() => d.value + e.value)
322 | const g = computed(() => d.value + e.value)
323 | const h = computed(() => f.value + g.value)
324 |
325 | effect(() => listener(h.value))
326 |
327 | endCreation()
328 |
329 | return (i) => (entry.value = i)
330 | },
331 | // async 'reatom-v1'({ listener, startCreation, endCreation }) {
332 | // const { declareAction, declareAtom, map, combine, createStore } =
333 | // await import('reatom-v1')
334 |
335 | // startCreation()
336 |
337 | // const entry = declareAction()
338 | // const a = declareAtom(0, (on) => [on(entry, (state, v) => v)])
339 | // const b = map(a, (v) => v + 1)
340 | // const c = map(a, (v) => v + 1)
341 | // const d = map(combine([b, c]), ([v1, v2]) => v1 + v2)
342 | // const e = map(d, (v) => v + 1)
343 | // const f = map(combine([d, e]), ([v1, v2]) => v1 + v2)
344 | // const g = map(combine([d, e]), ([v1, v2]) => v1 + v2)
345 | // const h = map(combine([f, g]), ([v1, v2]) => v1 + v2)
346 |
347 | // const store = createStore()
348 | // store.subscribe(h, listener)
349 |
350 | // endCreation()
351 |
352 | // return (i) => store.dispatch(entry(i))
353 | // },
354 | // async 'reatom-v2'({ listener, startCreation, endCreation }) {
355 | // const { createAtom, createStore } = await import('reatom-v2')
356 |
357 | // startCreation()
358 |
359 | // const a = createAtom({ entry: (v: number) => v }, (track, state = 0) => {
360 | // track.onAction('entry', (v) => (state = v))
361 | // return state
362 | // })
363 | // const b = createAtom({ a }, (track) => track.get('a') + 1)
364 | // const c = createAtom({ a }, (track) => track.get('a') + 1)
365 | // const d = createAtom({ b, c }, (track) => track.get('b') + track.get('c'))
366 | // const e = createAtom({ d }, (track) => track.get('d') + 1)
367 | // const f = createAtom({ d, e }, (track) => track.get('d') + track.get('e'))
368 | // const g = createAtom({ d, e }, (track) => track.get('d') + track.get('e'))
369 | // const h = createAtom({ f, g }, (track) => track.get('f') + track.get('g'))
370 |
371 | // const store = createStore()
372 | // store.subscribe(h, listener)
373 |
374 | // endCreation()
375 |
376 | // return (i) => store.dispatch(a.entry(i))
377 | // },
378 | async reatom({ listener, startCreation, endCreation }) {
379 | const { atom, createCtx } = await import('@reatom/core')
380 |
381 | startCreation()
382 |
383 | const entry = atom(0)
384 | const a = atom((ctx) => ctx.spy(entry))
385 | const b = atom((ctx) => ctx.spy(a) + 1)
386 | const c = atom((ctx) => ctx.spy(a) + 1)
387 | const d = atom((ctx) => ctx.spy(b) + ctx.spy(c))
388 | const e = atom((ctx) => ctx.spy(d) + 1)
389 | const f = atom((ctx) => ctx.spy(d) + ctx.spy(e))
390 | const g = atom((ctx) => ctx.spy(d) + ctx.spy(e))
391 | const h = atom((ctx) => ctx.spy(f) + ctx.spy(g))
392 |
393 | const ctx = createCtx()
394 | ctx.subscribe(h, listener)
395 |
396 | endCreation()
397 |
398 | return (i) => entry(ctx, i)
399 | },
400 | // async v4({ listener, startCreation, endCreation }) {
401 | // const { atom, effect, AsyncContext, wrap, notify, clearDefaults } =
402 | // await import('../../reatom4/packages/core/build')
403 |
404 | // startCreation()
405 |
406 | // clearDefaults()
407 |
408 | // const entry = atom(0, 'entry')
409 | // const a = atom(() => entry(), 'a')
410 | // const b = atom(() => a() + 1, 'b')
411 | // const c = atom(() => a() + 1, 'c')
412 | // const d = atom(() => b() + c(), 'd')
413 | // const e = atom(() => d() + 1, 'e')
414 | // const f = atom(() => d() + e(), 'f')
415 | // const g = atom(() => d() + e(), 'g')
416 | // const h = atom(() => f() + g(), 'h')
417 |
418 | // const root = AsyncContext.Snapshot.createRoot().frame
419 | // effect(() => listener(h())).run(root)
420 |
421 | // endCreation()
422 |
423 | // return wrap((v) => {
424 | // entry(v)
425 | // notify()
426 | // }, root)
427 | // },
428 | async solid({ listener, startCreation, endCreation }) {
429 | const { createSignal, createMemo, createEffect } = await import(
430 | // FIXME
431 | // @ts-ignore
432 | 'solid-js/dist/solid.cjs'
433 | )
434 |
435 | startCreation()
436 |
437 | const [entry, update] = createSignal(0)
438 | const a = createMemo(() => entry())
439 | const b = createMemo(() => a() + 1)
440 | const c = createMemo(() => a() + 1)
441 | const d = createMemo(() => b() + c())
442 | const e = createMemo(() => d() + 1)
443 | const f = createMemo(() => d() + e())
444 | const g = createMemo(() => d() + e())
445 | const h = createMemo(() => f() + g())
446 |
447 | createEffect(() => listener(h()))
448 |
449 | endCreation()
450 |
451 | return (i) => update(i)
452 | },
453 | async 's-js'({ listener, startCreation, endCreation }) {
454 | const { default: S } = await import('s-js')
455 |
456 | startCreation()
457 |
458 | const entry = S.root(() => {
459 | const entry = S.data(0)
460 | const a = S(() => entry())
461 | const b = S(() => a() + 1)
462 | const c = S(() => a() + 1)
463 | const d = S(() => b() + c())
464 | const e = S(() => d() + 1)
465 | const f = S(() => d() + e())
466 | const g = S(() => d() + e())
467 | const h = S(() => f() + g())
468 |
469 | S(() => listener(h()))
470 |
471 | return entry
472 | })
473 |
474 | endCreation()
475 |
476 | return (i) => entry(i)
477 | },
478 | async usignal({ listener, startCreation, endCreation }) {
479 | const { signal, computed, effect } = await import('usignal')
480 |
481 | startCreation()
482 |
483 | const entry = signal(0)
484 | const a = computed(() => entry.value)
485 | const b = computed(() => a.value + 1)
486 | const c = computed(() => a.value + 1)
487 | const d = computed(() => b.value + c.value)
488 | const e = computed(() => d.value + 1)
489 | const f = computed(() => d.value + e.value)
490 | const g = computed(() => d.value + e.value)
491 | const h = computed(() => f.value + g.value)
492 |
493 | effect(() => listener(h.value))
494 |
495 | endCreation()
496 |
497 | return (i) => (entry.value = i)
498 | },
499 | async '@webreflection/signal'({ listener, startCreation, endCreation }) {
500 | const { signal, computed, effect } = await import('@webreflection/signal')
501 |
502 | startCreation()
503 |
504 | const entry = signal(0)
505 | const a = computed(() => entry.value)
506 | const b = computed(() => a.value + 1)
507 | const c = computed(() => a.value + 1)
508 | const d = computed(() => b.value + c.value)
509 | const e = computed(() => d.value + 1)
510 | const f = computed(() => d.value + e.value)
511 | const g = computed(() => d.value + e.value)
512 | const h = computed(() => f.value + g.value)
513 |
514 | effect(() => listener(h.value))
515 |
516 | endCreation()
517 |
518 | return (i) => (entry.value = i)
519 | },
520 | async whatsup({ listener, startCreation, endCreation }) {
521 | const { observable, computed, autorun } = await import('whatsup')
522 |
523 | startCreation()
524 |
525 | const entry = observable(0)
526 | const a = computed(() => entry())
527 | const b = computed(() => a() + 1)
528 | const c = computed(() => a() + 1)
529 | const d = computed(() => b() + c())
530 | const e = computed(() => d() + 1)
531 | const f = computed(() => d() + e())
532 | const g = computed(() => d() + e())
533 | const h = computed(() => f() + g())
534 |
535 | autorun(() => listener(h()))
536 |
537 | endCreation()
538 |
539 | return (i) => entry(i)
540 | },
541 | async wonka({ listener, startCreation, endCreation }) {
542 | const { makeSubject, pipe, map, subscribe, combine, sample } = await import(
543 | 'wonka'
544 | )
545 |
546 | const ccombine = (
547 | sourceA: WSource,
548 | sourceB: WSource,
549 | ): WSource<[A, B]> => {
550 | const source = combine(sourceA, sourceB)
551 | // return source
552 | return pipe(source, sample(source))
553 | }
554 |
555 | startCreation()
556 |
557 | const entry = makeSubject()
558 | const a = pipe(
559 | entry.source,
560 | map((v) => v),
561 | )
562 | const b = pipe(
563 | a,
564 | map((v) => v + 1),
565 | )
566 | const c = pipe(
567 | a,
568 | map((v) => v + 1),
569 | )
570 | const d = pipe(
571 | ccombine(b, c),
572 | map(([b, c]) => b + c),
573 | )
574 | const e = pipe(
575 | d,
576 | map((v) => v + 1),
577 | )
578 | const f = pipe(
579 | ccombine(d, e),
580 | map(([d, e]) => d + e),
581 | )
582 | const g = pipe(
583 | ccombine(d, e),
584 | map(([d, e]) => d + e),
585 | )
586 | const h = pipe(
587 | ccombine(f, g),
588 | map(([h1, h2]) => h1 + h2),
589 | )
590 | pipe(h, subscribe(listener))
591 |
592 | endCreation()
593 |
594 | return (i) => entry.next(i)
595 | },
596 | async '@artalar/act'({ listener, startCreation, endCreation }) {
597 | const { act } = await import('@artalar/act')
598 |
599 | startCreation()
600 |
601 | const entry = act(0)
602 | const a = act(() => entry())
603 | const b = act(() => a() + 1)
604 | const c = act(() => a() + 1)
605 | const d = act(() => b() + c())
606 | const e = act(() => d() + 1)
607 | const f = act(() => d() + e())
608 | const g = act(() => d() + e())
609 | const h = act(() => f() + g())
610 |
611 | h.subscribe(listener)
612 |
613 | endCreation()
614 |
615 | return (i) => {
616 | entry(i)
617 | act.notify()
618 | }
619 | },
620 | })
621 |
622 | function setupComputersTest(tests: Rec) {
623 | return async (iterations: number, creationTries: number) => {
624 | const testsList: Array<{
625 | ref: { value: number }
626 | update: UpdateLeaf
627 | name: string
628 | creationLogs: Array
629 | updateLogs: Array
630 | memLogs: Array
631 | }> = []
632 |
633 | for (const name in tests) {
634 | const ref = { value: 0 }
635 | const creationLogs: Array = []
636 | let update: UpdateLeaf
637 | let start = 0
638 | let end = 0
639 | let i = creationTries || 1
640 |
641 | while (i--) {
642 | update = await tests[name]!({
643 | listener: (value) => (ref.value = value),
644 | startCreation: () => (start = performance.now()),
645 | endCreation: () => (end = performance.now()),
646 | })
647 |
648 | creationLogs.push(end - start)
649 |
650 | // try to prevent optimization of code meaning throwing
651 | update(-1)
652 | }
653 |
654 | testsList.push({
655 | ref,
656 | update: update!,
657 | name,
658 | creationLogs,
659 | updateLogs: [],
660 | memLogs: [],
661 | })
662 | }
663 |
664 | if (creationTries > 0) {
665 | console.log(
666 | `Median of computers creation and linking from ${creationTries} iterations\n(UNSTABLE)`,
667 | )
668 |
669 | printLogs(
670 | Object.fromEntries(
671 | testsList.map(({ name, creationLogs }) => [
672 | name,
673 | formatLog(creationLogs),
674 | ]),
675 | ),
676 | )
677 | }
678 |
679 | let i = 0
680 | while (i++ < iterations) {
681 | testsList.sort(() => Math.random() - 0.5)
682 | for (const test of testsList) {
683 | globalThis.gc?.()
684 | globalThis.gc?.()
685 | let mem = globalThis.process?.memoryUsage?.().heapUsed
686 |
687 | const start = performance.now()
688 | test.update(i % 2)
689 | test.updateLogs.push(performance.now() - start)
690 |
691 | if (mem) test.memLogs.push(process.memoryUsage().heapUsed - mem)
692 | }
693 |
694 | if (new Set(testsList.map((test) => test.ref.value)).size !== 1) {
695 | console.log(`ERROR!`)
696 | console.error(`Results is not equal (iteration №${i})`)
697 | console.log(
698 | Object.fromEntries(
699 | testsList.map(({ name, ref }) => [name, ref.value]),
700 | ),
701 | )
702 | process.exit(1)
703 | }
704 |
705 | await new Promise((resolve) => setTimeout(resolve, 0))
706 | }
707 |
708 | console.log(`Median of update duration from ${iterations} iterations`)
709 |
710 | const results = testsList.reduce(
711 | (acc, { name, updateLogs }) => ((acc[name] = formatLog(updateLogs)), acc),
712 | {} as Rec,
713 | )
714 |
715 | printLogs(results)
716 |
717 | if (globalThis.gc) {
718 | console.log(`Median of "heapUsed" from ${iterations} iterations`)
719 | printLogs(
720 | testsList.reduce(
721 | (acc, { name, memLogs }) => ((acc[name] = formatLog(memLogs)), acc),
722 | {} as Rec,
723 | ),
724 | )
725 | }
726 |
727 | return results
728 | }
729 | }
730 |
731 | export async function test() {
732 | const results = {
733 | 10: await testComputers(10, 5),
734 | 100: await testComputers(100, 0),
735 | 1_000: await testComputers(1_000, 0),
736 | 10_000: await testComputers(10_000, 0),
737 | }
738 |
739 | await genChart(results)
740 | }
741 |
742 | if (globalThis.process) {
743 | import('perf_hooks')
744 | // @ts-expect-error
745 | .then(({ performance }) => (globalThis.performance = performance))
746 | .then((): any => (globalThis.gc ? testComputers(300, 0) : test()))
747 | .then(() => process.exit())
748 | }
749 |
--------------------------------------------------------------------------------