@zeix/cause-effect 0.13.1 → 0.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,10 @@
1
- import { TestConfig } from "./framework-types";
2
- import { Computed, ReactiveFramework, Signal } from "./reactive-framework";
3
- import { Random } from "random";
1
+ import { TestConfig } from './framework-types'
2
+ import { Computed, ReactiveFramework, Signal } from './reactive-framework'
3
+ import { Random } from 'random'
4
4
 
5
5
  export interface Graph {
6
- sources: Signal<number>[];
7
- layers: Computed<number>[][];
6
+ sources: Signal<number>[]
7
+ layers: Computed<number>[][]
8
8
  }
9
9
 
10
10
  /**
@@ -17,25 +17,27 @@ export interface Graph {
17
17
  * @returns the graph
18
18
  */
19
19
  export function makeGraph(
20
- framework: ReactiveFramework,
21
- config: TestConfig,
22
- counter: Counter
20
+ framework: ReactiveFramework,
21
+ config: TestConfig,
22
+ counter: Counter,
23
23
  ): Graph {
24
- const { width, totalLayers, staticFraction, nSources } = config;
25
-
26
- return framework.withBuild(() => {
27
- const sources = new Array(width).fill(0).map((_, i) => framework.signal(i));
28
- const rows = makeDependentRows(
29
- sources,
30
- totalLayers - 1,
31
- counter,
32
- staticFraction,
33
- nSources,
34
- framework
35
- );
36
- const graph = { sources, layers: rows };
37
- return graph;
38
- });
24
+ const { width, totalLayers, staticFraction, nSources } = config
25
+
26
+ return framework.withBuild(() => {
27
+ const sources = new Array(width)
28
+ .fill(0)
29
+ .map((_, i) => framework.signal(i))
30
+ const rows = makeDependentRows(
31
+ sources,
32
+ totalLayers - 1,
33
+ counter,
34
+ staticFraction,
35
+ nSources,
36
+ framework,
37
+ )
38
+ const graph = { sources, layers: rows }
39
+ return graph
40
+ })
39
41
  }
40
42
 
41
43
  /**
@@ -44,146 +46,146 @@ export function makeGraph(
44
46
  * @return the sum of all leaf values
45
47
  */
46
48
  export function runGraph(
47
- graph: Graph,
48
- iterations: number,
49
- readFraction: number,
50
- framework: ReactiveFramework
49
+ graph: Graph,
50
+ iterations: number,
51
+ readFraction: number,
52
+ framework: ReactiveFramework,
51
53
  ): number {
52
- const rand = new Random("seed");
53
- const { sources, layers } = graph;
54
- const leaves = layers[layers.length - 1];
55
- const skipCount = Math.round(leaves.length * (1 - readFraction));
56
- const readLeaves = removeElems(leaves, skipCount, rand);
57
- const frameworkName = framework.name.toLowerCase();
58
- // const start = Date.now();
59
- let sum = 0;
60
-
61
- if (frameworkName === "s-js" || frameworkName === "solidjs") {
62
- // [S.js freeze](https://github.com/adamhaile/S#sdatavalue) doesn't allow different values to be set during a single batch, so special case it.
63
- for (let i = 0; i < iterations; i++) {
64
- framework.withBatch(() => {
65
- const sourceDex = i % sources.length;
66
- sources[sourceDex].write(i + sourceDex);
67
- });
68
-
69
- for (const leaf of readLeaves) {
70
- leaf.read();
71
- }
72
- }
73
-
74
- sum = readLeaves.reduce((total, leaf) => leaf.read() + total, 0);
75
- } else {
76
- framework.withBatch(() => {
77
- for (let i = 0; i < iterations; i++) {
78
- // Useful for debugging edge cases for some frameworks that experience
79
- // dramatic slow downs for certain test configurations. These are generally
80
- // due to `computed` effects not being cached efficiently, and as the number
81
- // of layers increases, the uncached `computed` effects are re-evaluated in
82
- // an `O(n^2)` manner where `n` is the number of layers.
83
- /* if (i % 100 === 0) {
54
+ const rand = new Random('seed')
55
+ const { sources, layers } = graph
56
+ const leaves = layers[layers.length - 1]
57
+ const skipCount = Math.round(leaves.length * (1 - readFraction))
58
+ const readLeaves = removeElems(leaves, skipCount, rand)
59
+ const frameworkName = framework.name.toLowerCase()
60
+ // const start = Date.now();
61
+ let sum = 0
62
+
63
+ if (frameworkName === 's-js' || frameworkName === 'solidjs') {
64
+ // [S.js freeze](https://github.com/adamhaile/S#sdatavalue) doesn't allow different values to be set during a single batch, so special case it.
65
+ for (let i = 0; i < iterations; i++) {
66
+ framework.withBatch(() => {
67
+ const sourceDex = i % sources.length
68
+ sources[sourceDex].write(i + sourceDex)
69
+ })
70
+
71
+ for (const leaf of readLeaves) {
72
+ leaf.read()
73
+ }
74
+ }
75
+
76
+ sum = readLeaves.reduce((total, leaf) => leaf.read() + total, 0)
77
+ } else {
78
+ framework.withBatch(() => {
79
+ for (let i = 0; i < iterations; i++) {
80
+ // Useful for debugging edge cases for some frameworks that experience
81
+ // dramatic slow downs for certain test configurations. These are generally
82
+ // due to `computed` effects not being cached efficiently, and as the number
83
+ // of layers increases, the uncached `computed` effects are re-evaluated in
84
+ // an `O(n^2)` manner where `n` is the number of layers.
85
+ /* if (i % 100 === 0) {
84
86
  console.log("iteration:", i, "delta:", Date.now() - start);
85
87
  } */
86
88
 
87
- const sourceDex = i % sources.length;
88
- sources[sourceDex].write(i + sourceDex);
89
+ const sourceDex = i % sources.length
90
+ sources[sourceDex].write(i + sourceDex)
89
91
 
90
- for (const leaf of readLeaves) {
91
- leaf.read();
92
- }
93
- }
94
-
95
- sum = readLeaves.reduce((total, leaf) => leaf.read() + total, 0);
96
- });
97
- }
92
+ for (const leaf of readLeaves) {
93
+ leaf.read()
94
+ }
95
+ }
98
96
 
99
- return sum;
97
+ sum = readLeaves.reduce((total, leaf) => leaf.read() + total, 0)
98
+ })
99
+ }
100
+
101
+ return sum
100
102
  }
101
103
 
102
104
  function removeElems<T>(src: T[], rmCount: number, rand: Random): T[] {
103
- const copy = src.slice();
104
- for (let i = 0; i < rmCount; i++) {
105
- const rmDex = rand.int(0, copy.length - 1);
106
- copy.splice(rmDex, 1);
107
- }
108
- return copy;
105
+ const copy = src.slice()
106
+ for (let i = 0; i < rmCount; i++) {
107
+ const rmDex = rand.int(0, copy.length - 1)
108
+ copy.splice(rmDex, 1)
109
+ }
110
+ return copy
109
111
  }
110
112
 
111
113
  export class Counter {
112
- count = 0;
114
+ count = 0
113
115
  }
114
116
 
115
117
  function makeDependentRows(
116
- sources: Computed<number>[],
117
- numRows: number,
118
- counter: Counter,
119
- staticFraction: number,
120
- nSources: number,
121
- framework: ReactiveFramework
118
+ sources: Computed<number>[],
119
+ numRows: number,
120
+ counter: Counter,
121
+ staticFraction: number,
122
+ nSources: number,
123
+ framework: ReactiveFramework,
122
124
  ): Computed<number>[][] {
123
- let prevRow = sources;
124
- const rand = new Random("seed");
125
- const rows = [];
126
- for (let l = 0; l < numRows; l++) {
127
- const row = makeRow(
128
- prevRow,
129
- counter,
130
- staticFraction,
131
- nSources,
132
- framework,
133
- l,
134
- rand
135
- );
136
- rows.push(row as never);
137
- prevRow = row;
138
- }
139
- return rows;
125
+ let prevRow = sources
126
+ const rand = new Random('seed')
127
+ const rows = []
128
+ for (let l = 0; l < numRows; l++) {
129
+ const row = makeRow(
130
+ prevRow,
131
+ counter,
132
+ staticFraction,
133
+ nSources,
134
+ framework,
135
+ l,
136
+ rand,
137
+ )
138
+ rows.push(row as never)
139
+ prevRow = row
140
+ }
141
+ return rows
140
142
  }
141
143
 
142
144
  function makeRow(
143
- sources: Computed<number>[],
144
- counter: Counter,
145
- staticFraction: number,
146
- nSources: number,
147
- framework: ReactiveFramework,
148
- _layer: number,
149
- random: Random
145
+ sources: Computed<number>[],
146
+ counter: Counter,
147
+ staticFraction: number,
148
+ nSources: number,
149
+ framework: ReactiveFramework,
150
+ _layer: number,
151
+ random: Random,
150
152
  ): Computed<number>[] {
151
- return sources.map((_, myDex) => {
152
- const mySources: Computed<number>[] = [];
153
- for (let sourceDex = 0; sourceDex < nSources; sourceDex++) {
154
- mySources.push(sources[(myDex + sourceDex) % sources.length]);
155
- }
156
-
157
- const staticNode = random.float() < staticFraction;
158
- if (staticNode) {
159
- // static node, always reference sources
160
- return framework.computed(() => {
161
- counter.count++;
162
-
163
- let sum = 0;
164
- for (const src of mySources) {
165
- sum += src.read();
166
- }
167
- return sum;
168
- });
169
- } else {
170
- // dynamic node, drops one of the sources depending on the value of the first element
171
- const first = mySources[0];
172
- const tail = mySources.slice(1);
173
- const node = framework.computed(() => {
174
- counter.count++;
175
- let sum = first.read();
176
- const shouldDrop = sum & 0x1;
177
- const dropDex = sum % tail.length;
178
-
179
- for (let i = 0; i < tail.length; i++) {
180
- if (shouldDrop && i === dropDex) continue;
181
- sum += tail[i].read();
182
- }
183
-
184
- return sum;
185
- });
186
- return node;
187
- }
188
- });
153
+ return sources.map((_, myDex) => {
154
+ const mySources: Computed<number>[] = []
155
+ for (let sourceDex = 0; sourceDex < nSources; sourceDex++) {
156
+ mySources.push(sources[(myDex + sourceDex) % sources.length])
157
+ }
158
+
159
+ const staticNode = random.float() < staticFraction
160
+ if (staticNode) {
161
+ // static node, always reference sources
162
+ return framework.computed(() => {
163
+ counter.count++
164
+
165
+ let sum = 0
166
+ for (const src of mySources) {
167
+ sum += src.read()
168
+ }
169
+ return sum
170
+ })
171
+ } else {
172
+ // dynamic node, drops one of the sources depending on the value of the first element
173
+ const first = mySources[0]
174
+ const tail = mySources.slice(1)
175
+ const node = framework.computed(() => {
176
+ counter.count++
177
+ let sum = first.read()
178
+ const shouldDrop = sum & 0x1
179
+ const dropDex = sum % tail.length
180
+
181
+ for (let i = 0; i < tail.length; i++) {
182
+ if (shouldDrop && i === dropDex) continue
183
+ sum += tail[i].read()
184
+ }
185
+
186
+ return sum
187
+ })
188
+ return node
189
+ }
190
+ })
189
191
  }
@@ -1,5 +1,5 @@
1
- import { TestResult } from "./perf-tests";
2
- import { ReactiveFramework } from "./reactive-framework";
1
+ import { TestResult } from './perf-tests'
2
+ import { ReactiveFramework } from './reactive-framework'
3
3
 
4
4
  /** Parameters for a running a performance benchmark test
5
5
  *
@@ -19,35 +19,35 @@ import { ReactiveFramework } from "./reactive-framework";
19
19
  * number of non-signal updated.
20
20
  */
21
21
  export interface TestConfig {
22
- /** friendly name for the test, should be unique */
23
- name?: string;
22
+ /** friendly name for the test, should be unique */
23
+ name?: string
24
24
 
25
- /** width of dependency graph to construct */
26
- width: number;
25
+ /** width of dependency graph to construct */
26
+ width: number
27
27
 
28
- /** depth of dependency graph to construct */
29
- totalLayers: number;
28
+ /** depth of dependency graph to construct */
29
+ totalLayers: number
30
30
 
31
- /** fraction of nodes that are static */ // TODO change to dynamicFraction
32
- staticFraction: number;
31
+ /** fraction of nodes that are static */ // TODO change to dynamicFraction
32
+ staticFraction: number
33
33
 
34
- /** construct a graph with number of sources in each node */
35
- nSources: number;
34
+ /** construct a graph with number of sources in each node */
35
+ nSources: number
36
36
 
37
- /** fraction of [0, 1] elements in the last layer from which to read values in each test iteration */
38
- readFraction: number;
37
+ /** fraction of [0, 1] elements in the last layer from which to read values in each test iteration */
38
+ readFraction: number
39
39
 
40
- /** number of test iterations */
41
- iterations: number;
40
+ /** number of test iterations */
41
+ iterations: number
42
42
 
43
- /** sum and count of all iterations, for verification */
44
- expected: Partial<TestResult>;
43
+ /** sum and count of all iterations, for verification */
44
+ expected: Partial<TestResult>
45
45
  }
46
46
 
47
47
  export interface FrameworkInfo {
48
- /** wrapper/adapter for a benchmarking a reactive framework */
49
- framework: ReactiveFramework;
48
+ /** wrapper/adapter for a benchmarking a reactive framework */
49
+ framework: ReactiveFramework
50
50
 
51
- /** verify the number of nodes executed matches the expected number */
52
- testPullCounts?: boolean;
51
+ /** verify the number of nodes executed matches the expected number */
52
+ testPullCounts?: boolean
53
53
  }
@@ -1,42 +1,42 @@
1
- import { FrameworkInfo, TestConfig } from "./framework-types";
1
+ import { FrameworkInfo, TestConfig } from './framework-types'
2
2
 
3
3
  export interface TestResult {
4
- sum: number;
5
- count: number;
4
+ sum: number
5
+ count: number
6
6
  }
7
7
 
8
8
  export interface TimingResult<T> {
9
- result: T;
10
- timing: TestTiming;
9
+ result: T
10
+ timing: TestTiming
11
11
  }
12
12
 
13
13
  export interface TestTiming {
14
- time: number;
14
+ time: number
15
15
  }
16
16
 
17
17
  export function verifyBenchResult(
18
- perfFramework: FrameworkInfo,
19
- config: TestConfig,
20
- timedResult: TimingResult<TestResult>
18
+ perfFramework: FrameworkInfo,
19
+ config: TestConfig,
20
+ timedResult: TimingResult<TestResult>,
21
21
  ): void {
22
- const { testPullCounts, framework } = perfFramework;
23
- const { expected } = config;
24
- const { result } = timedResult;
22
+ const { testPullCounts, framework } = perfFramework
23
+ const { expected } = config
24
+ const { result } = timedResult
25
25
 
26
- if (expected.sum) {
27
- console.assert(
28
- result.sum == expected.sum,
29
- `sum ${framework.name} ${config.name} result:${result.sum} expected:${expected.sum}`
30
- );
31
- }
32
- if (
33
- expected.count &&
34
- (config.readFraction === 1 || testPullCounts) &&
35
- testPullCounts !== false
36
- ) {
37
- console.assert(
38
- result.count === expected.count,
39
- `count ${framework.name} ${config.name} result:${result.count} expected:${expected.count}`
40
- );
41
- }
26
+ if (expected.sum) {
27
+ console.assert(
28
+ result.sum == expected.sum,
29
+ `sum ${framework.name} ${config.name} result:${result.sum} expected:${expected.sum}`,
30
+ )
31
+ }
32
+ if (
33
+ expected.count &&
34
+ (config.readFraction === 1 || testPullCounts) &&
35
+ testPullCounts !== false
36
+ ) {
37
+ console.assert(
38
+ result.count === expected.count,
39
+ `count ${framework.name} ${config.name} result:${result.count} expected:${expected.count}`,
40
+ )
41
+ }
42
42
  }
@@ -3,20 +3,19 @@
3
3
  * Implement this interface to add a new reactive framework to the test and performance test suite.
4
4
  */
5
5
  export interface ReactiveFramework {
6
- name: string;
7
- signal<T>(initialValue: T): Signal<T>;
8
- computed<T>(fn: () => T): Computed<T>;
9
- effect(fn: () => void): void;
10
- withBatch<T>(fn: () => T): void;
11
- withBuild<T>(fn: () => T): T;
6
+ name: string
7
+ signal<T>(initialValue: T): Signal<T>
8
+ computed<T>(fn: () => T): Computed<T>
9
+ effect(fn: () => void): void
10
+ withBatch<T>(fn: () => T): void
11
+ withBuild<T>(fn: () => T): T
12
12
  }
13
-
13
+
14
14
  export interface Signal<T> {
15
- read(): T;
16
- write(v: T): void;
15
+ read(): T
16
+ write(v: T): void
17
17
  }
18
-
18
+
19
19
  export interface Computed<T> {
20
- read(): T;
20
+ read(): T
21
21
  }
22
-
File without changes
File without changes
File without changes