Skip to content

Commit 67f47cb

Browse files
b-studiosLena Käufel
authored andcommitted
Improve performance of state (effekt-lang#907)
1 parent ee78f84 commit 67f47cb

File tree

2 files changed

+83
-73
lines changed

2 files changed

+83
-73
lines changed

effekt/shared/src/main/scala/effekt/generator/js/TransformerCps.scala

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -381,10 +381,7 @@ object TransformerCps extends Transformer {
381381

382382
// DEALLOC(ref); body
383383
case cps.Stmt.Dealloc(ref, body) =>
384-
Binding { k =>
385-
js.ExprStmt(js.Call(DEALLOC, nameRef(ref))) ::
386-
toJS(body).run(k)
387-
}
384+
toJS(body)
388385

389386
// const id = ref.value; body
390387
case cps.Stmt.Get(ref, id, body) =>
@@ -393,9 +390,9 @@ object TransformerCps extends Transformer {
393390
toJS(body).run(k)
394391
}
395392

396-
// ref.value = _value; body
393+
// ref.set(value); body
397394
case cps.Stmt.Put(ref, value, body) => Binding { k =>
398-
js.Assign(js.Member(nameRef(ref), JSName("value")), toJS(value)) ::
395+
js.ExprStmt(js.MethodCall(nameRef(ref), JSName("set"), toJS(value))) ::
399396
toJS(body).run(k)
400397
}
401398

libraries/js/effekt_runtime.js

Lines changed: 80 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,89 @@
1+
// Complexity of state:
2+
//
3+
// get: O(1)
4+
// set: O(1)
5+
// capture: O(1)
6+
// restore: O(|write operations since capture|)
7+
const Mem = null
8+
9+
function Arena() {
10+
const s = {
11+
root: { value: Mem },
12+
generation: 0,
13+
fresh: (v) => {
14+
const r = {
15+
value: v,
16+
generation: s.generation,
17+
store: s,
18+
set: (v) => {
19+
const s = r.store
20+
const r_gen = r.generation
21+
const s_gen = s.generation
22+
23+
if (r_gen == s_gen) {
24+
r.value = v;
25+
} else {
26+
const root = { value: Mem }
27+
// update store
28+
s.root.value = { ref: r, value: r.value, generation: r_gen, root: root }
29+
s.root = root
30+
r.value = v
31+
r.generation = s_gen
32+
}
33+
}
34+
};
35+
return r
36+
},
37+
// not implemented
38+
newRegion: () => s
39+
};
40+
return s
41+
}
142

2-
// Common Runtime
3-
// --------------
4-
function Cell(init, region) {
5-
const cell = {
6-
value: init,
7-
backup: function() {
8-
const _backup = cell.value;
9-
// restore function (has a STRONG reference to `this`)
10-
return () => { cell.value = _backup; return cell }
11-
}
43+
const global = {
44+
fresh: (v) => {
45+
const r = {
46+
value: v,
47+
set: (v) => { r.value = v }
48+
};
49+
return r
1250
}
13-
return cell;
1451
}
1552

16-
const global = {
17-
fresh: Cell
53+
function snapshot(s) {
54+
const snap = { store: s, root: s.root, generation: s.generation }
55+
s.generation = s.generation + 1
56+
return snap
1857
}
1958

20-
function Arena(_region) {
21-
const region = _region;
22-
return {
23-
fresh: function(init) {
24-
const cell = Cell(init);
25-
// region keeps track what to backup, but we do not need to backup unreachable cells
26-
region.push(cell) // new WeakRef(cell))
27-
return cell;
28-
},
29-
region: _region,
30-
newRegion: function() {
31-
// a region aggregates weak references
32-
const nested = Arena([])
33-
// this doesn't work yet, since Arena.backup doesn't return a thunk
34-
region.push(nested) //new WeakRef(nested))
35-
return nested;
36-
},
37-
backup: function() {
38-
const _backup = []
39-
let nextIndex = 0;
40-
for (const ref of region) {
41-
const cell = ref //.deref()
42-
// only backup live cells
43-
if (cell) {
44-
_backup[nextIndex] = cell.backup()
45-
nextIndex++
46-
}
47-
}
48-
function restore() {
49-
const region = []
50-
let nextIndex = 0;
51-
for (const restoreCell of _backup) {
52-
region[nextIndex] = restoreCell() // new WeakRef(restoreCell())
53-
nextIndex++
54-
}
55-
return Arena(region)
56-
}
57-
return restore;
58-
}
59-
}
59+
function reroot(n) {
60+
if (n.value === Mem) return;
61+
62+
const diff = n.value
63+
const r = diff.ref
64+
const v = diff.value
65+
const g = diff.generation
66+
const n2 = diff.root
67+
reroot(n2)
68+
n.value = Mem
69+
n2.value = { ref: r, value: r.value, generation: r.generation, root: n}
70+
r.value = v
71+
r.generation = g
6072
}
6173

74+
function restore(store, snap) {
75+
// linear in the number of modifications...
76+
reroot(snap.root)
77+
store.root = snap.root
78+
store.generation = snap.generation + 1
79+
}
6280

81+
// Common Runtime
82+
// --------------
6383
let _prompt = 1;
6484

6585
const TOPLEVEL_K = (x, ks) => { throw { computationIsDone: true, result: x } }
66-
const TOPLEVEL_KS = { prompt: 0, arena: Arena([]), rest: null }
86+
const TOPLEVEL_KS = { prompt: 0, arena: Arena(), rest: null }
6787

6888
function THUNK(f) {
6989
f.thunk = true
@@ -80,37 +100,29 @@ function CAPTURE(body) {
80100

81101
const RETURN = (x, ks) => ks.rest.stack(x, ks.rest)
82102

83-
// const r = ks.arena.newRegion(); body
84-
// const x = r.alloc(init); body
85-
86103
// HANDLE(ks, ks, (p, ks, k) => { STMT })
87104
function RESET(prog, ks, k) {
88105
const prompt = _prompt++;
89106
const rest = { stack: k, prompt: ks.prompt, arena: ks.arena, rest: ks.rest }
90107
return prog(prompt, { prompt, arena: Arena([]), rest }, RETURN)
91108
}
92109

93-
function DEALLOC(ks) {
94-
const arena = ks.arena
95-
if (!!arena) {
96-
arena.length = arena.length - 1
97-
}
98-
}
99-
100110
function SHIFT(p, body, ks, k) {
101111

102112
// TODO avoid constructing this object
103113
let meta = { stack: k, prompt: ks.prompt, arena: ks.arena, rest: ks.rest }
104114
let cont = null
105115

106116
while (!!meta && meta.prompt !== p) {
107-
cont = { stack: meta.stack, prompt: meta.prompt, backup: meta.arena.backup(), rest: cont }
117+
let store = meta.arena
118+
cont = { stack: meta.stack, prompt: meta.prompt, arena: store, backup: snapshot(store), rest: cont }
108119
meta = meta.rest
109120
}
110121
if (!meta) { throw `Prompt not found ${p}` }
111122

112123
// package the prompt itself
113-
cont = { stack: meta.stack, prompt: meta.prompt, backup: meta.arena.backup(), rest: cont }
124+
let store = meta.arena
125+
cont = { stack: meta.stack, prompt: meta.prompt, arena: store, backup: snapshot(store), rest: cont }
114126
meta = meta.rest
115127

116128
const k1 = meta.stack
@@ -123,7 +135,8 @@ function RESUME(cont, c, ks, k) {
123135
let meta = { stack: k, prompt: ks.prompt, arena: ks.arena, rest: ks.rest }
124136
let toRewind = cont
125137
while (!!toRewind) {
126-
meta = { stack: toRewind.stack, prompt: toRewind.prompt, arena: toRewind.backup(), rest: meta }
138+
restore(toRewind.arena, toRewind.backup)
139+
meta = { stack: toRewind.stack, prompt: toRewind.prompt, arena: toRewind.arena, rest: meta }
127140
toRewind = toRewind.rest
128141
}
129142

0 commit comments

Comments
 (0)