From 32eea268559a1360cc00fcd5b180e74104e0c395 Mon Sep 17 00:00:00 2001
From: Marvin Borner
Date: Sat, 6 Apr 2024 19:45:26 +0200
Subject: Performance
---
bruijn/Screen.bruijn | 4 ++
index.html | 17 +++++-
script.js | 149 +++++++++++++++++++++++++++++++++++++--------------
style.css | 2 +-
worker.js | 14 +++++
5 files changed, 144 insertions(+), 42 deletions(-)
create mode 100644 worker.js
diff --git a/bruijn/Screen.bruijn b/bruijn/Screen.bruijn
index f132c66..465df25 100644
--- a/bruijn/Screen.bruijn
+++ b/bruijn/Screen.bruijn
@@ -74,3 +74,7 @@ br! [[1 [[[[[0 4 3 2 5]]]]]]]
map [&[[[[[0 (5 4) (5 3) (5 2) (5 1)]]]]]]
:test (map invert empty) (build w w w w)
+
+qsplit map [w? 0 (build w w w w) (build b b b b)]
+
+:test (qsplit (build b w b w)) (build (build b b b b) (build w w w w) (build b b b b) (build w w w w))
diff --git a/index.html b/index.html
index 51c033e..d1997cb 100644
--- a/index.html
+++ b/index.html
@@ -25,12 +25,20 @@
Load preset:
diff --git a/script.js b/script.js
index 9bab3c4..36fef75 100644
--- a/script.js
+++ b/script.js
@@ -16,40 +16,47 @@ const BLACK = 1;
const UNKNOWN = 2;
const canvas = window.canvas;
-const screen = canvas.getContext("2d");
const root = { x: [0, canvas.width], y: [0, canvas.height] };
+const offscreen = canvas.transferControlToOffscreen();
+const worker = new Worker("worker.js");
+worker.postMessage({ canvas: offscreen }, [offscreen]);
+
const drawAt = (x, y, color) => {
- screen.fillStyle =
- color == WHITE ? "white" : color == BLACK ? "black" : "grey";
- screen.fillRect(x[0], y[0], x[1], y[1]);
+ worker.postMessage([
+ color == WHITE ? "white" : color == BLACK ? "black" : "grey",
+ x[0],
+ y[0],
+ x[1] - x[0],
+ y[1] - y[0],
+ ]);
};
const drawTopLeft = (ctx, color) => {
- const newX = [ctx.x[0], ctx.x[1] / 2];
- const newY = [ctx.y[0], ctx.y[1] / 2];
+ const newX = [ctx.x[0], ctx.x[0] + (ctx.x[1] - ctx.x[0]) / 2];
+ const newY = [ctx.y[0], ctx.y[0] + (ctx.y[1] - ctx.y[0]) / 2];
drawAt(newX, newY, color);
return { x: newX, y: newY };
};
const drawTopRight = (ctx, color) => {
- const newX = [ctx.x[1] / 2, ctx.x[1]];
- const newY = [ctx.y[0], ctx.y[1] / 2];
+ const newX = [ctx.x[0] + (ctx.x[1] - ctx.x[0]) / 2, ctx.x[1]];
+ const newY = [ctx.y[0], ctx.y[0] + (ctx.y[1] - ctx.y[0]) / 2];
drawAt(newX, newY, color);
return { x: newX, y: newY };
};
const drawBottomLeft = (ctx, color) => {
- const newX = [ctx.x[0], ctx.x[1] / 2];
- const newY = [ctx.y[1] / 2, ctx.y[1]];
+ const newX = [ctx.x[0], ctx.x[0] + (ctx.x[1] - ctx.x[0]) / 2];
+ const newY = [ctx.y[0] + (ctx.y[1] - ctx.y[0]) / 2, ctx.y[1]];
drawAt(newX, newY, color);
return { x: newX, y: newY };
};
const drawBottomRight = (ctx, color) => {
- const newX = [ctx.x[1] / 2, ctx.x[1]];
- const newY = [ctx.y[1] / 2, ctx.y[1]];
+ const newX = [ctx.x[0] + (ctx.x[1] - ctx.x[0]) / 2, ctx.x[1]];
+ const newY = [ctx.y[0] + (ctx.y[1] - ctx.y[0]) / 2, ctx.y[1]];
drawAt(newX, newY, color);
return { x: newX, y: newY };
};
@@ -60,23 +67,44 @@ const drawBottomRight = (ctx, color) => {
// `return null` and `if (foo === null) return null` are the monads of JavaScript!!
// ---
+const allHashes = {};
+const hash = (s) => {
+ let h = 0;
+ for (let i = 0; i < s.length; i++) {
+ const chr = s.charCodeAt(i);
+ h = (h << 5) - h + chr;
+ h |= 0;
+ }
+ while (h in allHashes && allHashes[h] !== s) h += 1;
+ allHashes[h] = s;
+ return h;
+};
+
const abs = (body) => {
if (body === null) return null;
- return { type: "abs", body };
+ const t = { type: "abs", body };
+ t.hash = hash("abs" + body.hash);
+ return t;
};
const app = (left) => (right) => {
if (left === null || right === null) return null;
- return { type: "app", left, right };
+ const t = { type: "app", left, right };
+ t.hash = hash("app" + left.hash + right.hash);
+ return t;
};
const idx = (idx) => {
if (idx === null) return null;
- return { type: "idx", idx };
+ const t = { type: "idx", idx };
+ t.hash = hash("idx" + idx);
+ return t;
};
const def = (name) => {
- return { type: "def", name };
+ const t = { type: "def", name };
+ t.hash = hash("def" + name);
+ return t;
};
const show = (t) => {
@@ -121,11 +149,9 @@ const parseLam = (str) => {
switch (head) {
case "λ":
case "\\":
- const [body, _tail] = parseLam(tail);
- return [abs(body), _tail];
case "[": // bruijn
- const [bbody, _btail] = parseLam(tail);
- return [abs(bbody), _btail.slice(1)];
+ const [body, _tail] = parseLam(tail);
+ return [abs(body), head == "[" ? _tail.slice(1) : _tail];
case "(":
const [left, tail1] = parseLam(tail);
const [right, tail2] = parseLam(tail1.slice(1));
@@ -217,6 +243,7 @@ const parse = (str) => {
.trim()
.split(/\r?\n/)
.every((line) => {
+ if (line.startsWith("--") || line.length == 0) return true;
if (!line.includes("=")) {
t = resolveTerm(parseTerm(line), defs);
return false;
@@ -252,7 +279,7 @@ const seemsScreeny = (t) =>
t.body.left.left.left.left.idx === 0;
const clearScreen = () => {
- screen.clearRect(0, 0, canvas.width, canvas.height);
+ worker.postMessage("clear");
};
/* beta reduction */
@@ -275,40 +302,65 @@ const cancelReduction = () => {
return canceled;
};
+const incCache = {};
const inc = (i, t) => {
if (cancelReduction() || t === null) {
error("in inc");
return null;
}
+
+ if (t.hash in incCache && i in incCache[t.hash]) return incCache[t.hash][i];
+
+ let newT;
switch (t.type) {
case "idx":
- return idx(i <= t.idx ? t.idx + 1 : t.idx);
+ newT = idx(i <= t.idx ? t.idx + 1 : t.idx);
+ break;
case "app":
- return app(inc(i, t.left))(inc(i, t.right));
+ newT = app(inc(i, t.left))(inc(i, t.right));
+ break;
case "abs":
- return abs(inc(i + 1, t.body));
+ newT = abs(inc(i + 1, t.body));
+ break;
case "def":
error("unexpected def");
return null;
}
+
+ if (!(t.hash in incCache)) incCache[t.hash] = {};
+ incCache[t.hash][i] = newT;
+ return newT;
};
+const substCache = {};
const subst = (i, t, s) => {
if (cancelReduction() || t === null) {
error("in subst");
return null;
}
+
+ const h = hash("" + t.hash + s.hash);
+ if (h in substCache && i in substCache[h]) return substCache[h][i];
+
+ let newT;
switch (t.type) {
case "idx":
- return i == t.idx ? s : idx(t.idx > i ? t.idx - 1 : t.idx);
+ newT = i == t.idx ? s : idx(t.idx > i ? t.idx - 1 : t.idx);
+ break;
case "app":
- return app(subst(i, t.left, s))(subst(i, t.right, s));
+ newT = app(subst(i, t.left, s))(subst(i, t.right, s));
+ break;
case "abs":
- return abs(subst(i + 1, t.body, inc(0, s)));
+ newT = abs(subst(i + 1, t.body, inc(0, s)));
+ break;
case "def":
error("unexpected def");
return null;
}
+
+ if (!(h in substCache)) substCache[h] = {};
+ substCache[h][i] = newT;
+ return newT;
};
// guaranteed normal form
@@ -336,42 +388,53 @@ const gnf = (t) => {
};
// weak head normal form
+const whnfCache = {};
const whnf = (t) => {
if (cancelReduction() || t === null) {
error("in whnf");
return null;
}
+
+ if (t.hash in whnfCache) return whnfCache[t.hash];
+
+ let newT;
switch (t.type) {
case "app":
const _left = whnf(t.left);
if (_left === null) return null;
- return _left.type === "abs"
- ? whnf(subst(0, _left.body, t.right))
- : app(_left)(t.right);
+ newT =
+ _left.type === "abs"
+ ? whnf(subst(0, _left.body, t.right))
+ : app(_left)(t.right);
+ break;
case "def":
error("unexpected def");
return null;
default:
- return t;
+ newT = t;
+ break;
}
+
+ whnfCache[t.hash] = newT;
+ return newT;
};
// screen normal form
// one of [((((0 tl) tr) bl) br)], [[0]], [[1]]
+const snfCache = {};
const snf = (_t) => {
+ if (_t !== null && _t.hash in snfCache) return snfCache[_t.hash];
+
let t = whnf(_t);
if (t === null || t.type !== "abs") {
error("not a screen/pixel");
return null;
}
+
t = abs(whnf(t.body));
if (t.body.type === "abs") return gnf(t); // not a screen, probably a pixel
while (t !== null && !seemsScreeny(t)) {
- if (cancelReduction()) {
- error("in snf");
- return null;
- }
// TODO: a bit unsure about this
switch (t.type) {
case "app":
@@ -392,18 +455,25 @@ const snf = (_t) => {
return null;
}
}
+
+ snfCache[_t.hash] = t;
return t;
};
const reduce = (_t) => {
- console.log(show(_t));
const stack = [{ ctx: root, t: _t }];
- for (let i = 0; stack.length > 0 && i < 1024; i++) {
- let { ctx, t } = stack.pop();
+ for (let i = 0; stack.length > 0 && i < 4 ** 10; i++) {
+ // console.log(i, stack.length);
+ let [{ ctx, t }] = stack.splice(
+ Math.floor(Math.random() * stack.length),
+ 1,
+ );
+ // let { ctx, t } = stack.shift();
+ // let { ctx, t } = stack.pop();
if (toColor(t) !== UNKNOWN) continue;
// could loop in gnf, therefore limit depth
- MAX = 16384;
+ MAX = 10000000;
depth = 0;
canceled = false;
try {
@@ -427,6 +497,7 @@ const reduce = (_t) => {
const br = t.body.right;
stack.push({ ctx: drawBottomRight(ctx, toColor(br)), t: br });
} else {
+ // TODO: could we risk gnfing here?
drawAt(ctx.x, ctx.y, toColor(t));
}
}
diff --git a/style.css b/style.css
index ee80e76..5c6db87 100644
--- a/style.css
+++ b/style.css
@@ -34,7 +34,7 @@ input#slider {
textarea#term {
width: 100%;
- font-size: 2em;
+ font-size: 1.3em;
}
button#render {
diff --git a/worker.js b/worker.js
new file mode 100644
index 0000000..f89a699
--- /dev/null
+++ b/worker.js
@@ -0,0 +1,14 @@
+let canvas, screen;
+
+self.onmessage = (msg) => {
+ if (msg.data == "clear") {
+ screen.clearRect(0, 0, canvas.width, canvas.height);
+ } else if ("canvas" in msg.data) {
+ canvas = msg.data.canvas;
+ screen = canvas.getContext("2d");
+ } else {
+ [color, x, y, width, height] = msg.data;
+ screen.fillStyle = color;
+ screen.fillRect(x, y, width, height);
+ }
+};
--
cgit v1.2.3