Skip to content

Instantly share code, notes, and snippets.

@bellbind
Last active July 1, 2017 07:11
Show Gist options
  • Save bellbind/69e33d31ffa074b4944040deefccedbc to your computer and use it in GitHub Desktop.
Save bellbind/69e33d31ffa074b4944040deefccedbc to your computer and use it in GitHub Desktop.
[webassembly]Compare pure ES/wasm/asm.js with Turing Pattern example
function asm(global, env, heap) {
"use asm";
var imul = global.Math.imul;
var buf = new global.Float64Array(heap);
function get(offs, w, x, y) {
offs = offs | 0;
w = w | 0;
x = x | 0;
y = y | 0;
var index = 0;
index = (imul((y + w >>> 0) % (w >>> 0) | 0, w) | 0) +
((x + w >>> 0) % (w >>> 0) | 0) | 0;
return +buf[(offs + (index << 3)) >> 3];
}
function conv(coffs, cr, soffs, w, h, doffs) {
coffs = coffs | 0;
cr = cr | 0;
soffs = soffs | 0;
w = w | 0;
h = h | 0;
doffs = doffs | 0;
var x = 0, y = 0, cx = 0, cy = 0, cw = 0;
var s = 0.0;
cw = (imul(cr, 2) | 0) + 1 | 0;
for (y = 0; (y | 0) < (h | 0); y = y + 1 | 0) {
for (x = 0; (x | 0) < (w | 0); x = x + 1 | 0) {
s = 0.0;
for (cy = -cr | 0; (cy | 0) <= (cr | 0); cy = cy + 1 | 0) {
for (cx = -cr | 0; (cx | 0) <= (cr | 0); cx = cx + 1 | 0) {
s = s + +get(coffs, cw, cx + cr | 0, cy + cr | 0) *
+get(soffs, w, x + cx | 0, y + cy | 0);
}
}
buf[(doffs + (((imul(y, w) | 0) + x) << 3)) >> 3] = +s;
}
}
}
return {conv: conv};
}
(module
(export "heap" (memory $0))
(export "conv" (func $conv))
(memory $0 1 256)
(func $get (param $offs i32) (param $w i32) (param $x i32) (param $y i32) (result f64)
(local $index i32)
(set_local $index
(i32.add
(i32.mul
(i32.rem_u
(i32.add
(get_local $y)
(get_local $w)
)
(get_local $w)
)
(get_local $w)
)
(i32.rem_u
(i32.add
(get_local $x)
(get_local $w)
)
(get_local $w)
)
)
)
(return
(f64.load
(i32.add
(get_local $offs)
(i32.shl
(get_local $index)
(i32.const 3)
)
)
)
)
)
(func $conv (param $coffs i32) (param $cr i32) (param $soffs i32) (param $w i32) (param $h i32) (param $doffs i32)
(local $x i32)
(local $y i32)
(local $cx i32)
(local $cy i32)
(local $cw i32)
(local $s f64)
(set_local $cw
(i32.add
(i32.mul
(get_local $cr)
(i32.const 2)
)
(i32.const 1)
)
)
(block
(set_local $y
(i32.const 0)
)
(loop $for-in
(block $for-out
(if
(i32.eqz
(i32.lt_s
(get_local $y)
(get_local $h)
)
)
(br $for-out)
)
(block
(set_local $x
(i32.const 0)
)
(loop $for-in1
(block $for-out0
(if
(i32.eqz
(i32.lt_s
(get_local $x)
(get_local $w)
)
)
(br $for-out0)
)
(block
(set_local $s
(f64.const 0)
)
(block
(set_local $cy
(i32.sub
(i32.const 0)
(get_local $cr)
)
)
(loop $for-in3
(block $for-out2
(if
(i32.eqz
(i32.le_s
(get_local $cy)
(get_local $cr)
)
)
(br $for-out2)
)
(block
(set_local $cx
(i32.sub
(i32.const 0)
(get_local $cr)
)
)
(loop $for-in5
(block $for-out4
(if
(i32.eqz
(i32.le_s
(get_local $cx)
(get_local $cr)
)
)
(br $for-out4)
)
(set_local $s
(f64.add
(get_local $s)
(f64.mul
(call $get
(get_local $coffs)
(get_local $cw)
(i32.add
(get_local $cx)
(get_local $cr)
)
(i32.add
(get_local $cy)
(get_local $cr)
)
)
(call $get
(get_local $soffs)
(get_local $w)
(i32.add
(get_local $x)
(get_local $cx)
)
(i32.add
(get_local $y)
(get_local $cy)
)
)
)
)
)
(set_local $cx
(i32.add
(get_local $cx)
(i32.const 1)
)
)
(br $for-in5)
)
)
)
(set_local $cy
(i32.add
(get_local $cy)
(i32.const 1)
)
)
(br $for-in3)
)
)
)
(f64.store
(i32.add
(get_local $doffs)
(i32.shl
(i32.add
(i32.mul
(get_local $y)
(get_local $w)
)
(get_local $x)
)
(i32.const 3)
)
)
(get_local $s)
)
)
(set_local $x
(i32.add
(get_local $x)
(i32.const 1)
)
)
(br $for-in1)
)
)
)
(set_local $y
(i32.add
(get_local $y)
(i32.const 1)
)
)
(br $for-in)
)
)
)
)
)
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<script src="script-asm.js" defer="defer"></script>
</head>
<body>
</body>
</html>
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<script src="script-es6.js" defer="defer"></script>
</head>
<body>
</body>
</html>
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<script src="script-wasm.js" defer="defer"></script>
</head>
<body>
</body>
</html>
"use strct";
// Turing Pattern Simulator program
const {
w = 200, s = 2, // w: cell size, s: rect size
ra = 3, ri = 7, //ra/ri: radius as activator/inhibitor
} = Function(`return {${location.hash.slice(1)}}`)();
function force(r) {
const w = r * 2 + 1;
//return [...Array(w * w)].map((_, i) => 1 / (w * w));
const conv = [...Array(w * w)].map((_, i) => {
const x = i % w - r, y = (i / w | 0) - r;
return Math.max((r + 1 / 2) - Math.hypot(x, y), 0) ** 2;
});
const total = conv.reduce((s, f) => s + f, 0);
return conv.map(f => f / total);
}
const convA = force(ra);
const convI = force(ri);
// cell automaton
function init() {
const cells = [...Array(w * w)].map((_, i) => i);
return {F: cells.map(i => Math.random())};
}
// visualize
function render(c2d, {F}) {
c2d.clearRect(0, 0, c2d.canvas.width, c2d.canvas.height);
F.forEach((v, i) => {
const x = i % w, y = i / w | 0;
c2d.fillStyle = `hsl(0, 0%, ${clamp(v, 0, 1) * 100}%)`;
c2d.fillRect(x * s, y * s, s, s);
});
}
const canvas = document.createElement("canvas");
canvas.width = canvas.height = w * s;
document.body.appendChild(canvas);
const c2d = canvas.getContext("2d");
// utils
function clamp(v, min, max) {
return Math.min(Math.max(min, v), max);
}
fetch("./conv.asm.js").then(res => res.text()).
then(src => Function(`return (${src})`)()).
then(asm => {
const atleast = (convA.length + convI.length + w * w * 3) << 3;
let size = 0x100000;
while (size < atleast) size += 0x100000;
const heap = {buffer: new ArrayBuffer(size)};
const {conv} = asm(top, {}, heap.buffer);
let offs = 0;
const cA = new Float64Array(heap.buffer, offs, convA.length);
cA.set(convA);
offs += cA.byteLength;
const cI = new Float64Array(heap.buffer, offs, convI.length);
cI.set(convI);
offs += cI.byteLength;
const sF = new Float64Array(heap.buffer, offs, w * w);
offs += sF.byteLength;
const dA = new Float64Array(heap.buffer, offs, w * w);
offs += dA.byteLength;
const dI = new Float64Array(heap.buffer, offs, w * w);
function next({F}) {
sF.set(F);
conv(cA.byteOffset, ra, sF.byteOffset, w, w, dA.byteOffset);
conv(cI.byteOffset, ri, sF.byteOffset, w, w, dI.byteOffset);
return {F: F.map((v, i) => v + dA[i] - dI[i])};
}
// main loop
function loop(c2d, channel) {
render(c2d, channel);
console.time("next");
const n = next(channel);
console.timeEnd("next");
requestAnimationFrame(_ => loop(c2d, n));
}
loop(c2d, init());
});
"use strct";
// Turing Pattern Simulator program
const {
w = 200, s = 2, // w: cell size, s: rect size
ra = 3, ri = 7, //ra/ri: radius as activator/inhibitor
} = Function(`return {${location.hash.slice(1)}}`)();
// convolution for cell array
function get(ch, x, y) {
return ch[((y + w) % w) * w + ((x + w) % w)];
//return ch[clamp(y, 0, w - 1) * w + clamp(x, 0, w - 1)];
}
function conv(f) {
return (v, i, ch) => {
const x = i % w, y = i / w | 0;
return f.reduce((s, c) => s + c.f * get(ch, x + c.x, y + c.y), 0);
};
}
function force(r) {
const w = r * 2 + 1;
//return [...Array(w * w)].map(
// (_, i) => ({x: i % w - r, y: (i / w | 0) - r, f: 1 / (w * w)}));
const conv = [...Array(w * w)].map((_, i) => {
const x = i % w - r, y = (i / w | 0) - r;
const f = Math.max((r + 1 / 2) - Math.hypot(x, y), 0) ** 2;
return {x, y, f};
}).filter(({f}) => f > 0);
const total = conv.reduce((s, {f}) => s + f, 0);
return conv.map(({x, y, f}) => ({x, y, f: f / total}));
}
const convA = conv(force(ra));
const convI = conv(force(ri));
// cell automaton
function init() {
const cells = [...Array(w * w)].map((_, i) => i);
return {F: cells.map(i => Math.random())};
}
function next({F}) {
const A = F.map(convA), I = F.map(convI);
return {F: F.map((v, i) => v + A[i] - I[i])};
}
// visualize
function render(c2d, {F}) {
c2d.clearRect(0, 0, c2d.canvas.width, c2d.canvas.height);
F.forEach((v, i) => {
const x = i % w, y = i / w | 0;
c2d.fillStyle = `hsl(0, 0%, ${clamp(v, 0, 1) * 100}%)`;
c2d.fillRect(x * s, y * s, s, s);
});
}
const canvas = document.createElement("canvas");
canvas.width = canvas.height = w * s;
document.body.appendChild(canvas);
const c2d = canvas.getContext("2d");
loop(c2d, init());
// utils
function clamp(v, min, max) {
return Math.min(Math.max(min, v), max);
}
// main loop
function loop(c2d, channel) {
render(c2d, channel);
console.time("next");
const n = next(channel);
console.timeEnd("next");
requestAnimationFrame(_ => loop(c2d, n));
}
"use strct";
// Turing Pattern Simulator program
const {
w = 200, s = 2, // w: cell size, s: rect size
ra = 3, ri = 7, //ra/ri: radius as activator/inhibitor
} = Function(`return {${location.hash.slice(1)}}`)();
function force(r) {
const w = r * 2 + 1;
const conv = [...Array(w * w)].map((_, i) => {
const x = i % w - r, y = (i / w | 0) - r;
return Math.max((r + 1 / 2) - Math.hypot(x, y), 0) ** 2;
});
const total = conv.reduce((s, f) => s + f, 0);
return conv.map(f => f / total);
}
const convA = force(ra);
const convI = force(ri);
// cell automaton
function init() {
const cells = [...Array(w * w)].map((_, i) => i);
return {F: cells.map(i => Math.random())};
}
// visualize
function render(c2d, {F}) {
c2d.clearRect(0, 0, c2d.canvas.width, c2d.canvas.height);
F.forEach((v, i) => {
const x = i % w, y = i / w | 0;
c2d.fillStyle = `hsl(0, 0%, ${clamp(v, 0, 1) * 100}%)`;
c2d.fillRect(x * s, y * s, s, s);
});
}
const canvas = document.createElement("canvas");
canvas.width = canvas.height = w * s;
document.body.appendChild(canvas);
const c2d = canvas.getContext("2d");
// utils
function clamp(v, min, max) {
return Math.min(Math.max(min, v), max);
}
fetch("./conv.wasm").then(res => res.arrayBuffer()).
then(buf => WebAssembly.instantiate(buf, {})).
then(({instance}) => {
const {conv, heap} = instance.exports;
const size = (convA.length + convI.length + w * w * 3) << 3;
while (heap.buffer.byteLength < size) heap.grow(1);
let offs = 0;
const cA = new Float64Array(heap.buffer, offs, convA.length);
cA.set(convA);
offs += cA.byteLength;
const cI = new Float64Array(heap.buffer, offs, convI.length);
cI.set(convI);
offs += cI.byteLength;
const sF = new Float64Array(heap.buffer, offs, w * w);
offs += sF.byteLength;
const dA = new Float64Array(heap.buffer, offs, w * w);
offs += dA.byteLength;
const dI = new Float64Array(heap.buffer, offs, w * w);
function next({F}) {
sF.set(F);
conv(cA.byteOffset, ra, sF.byteOffset, w, w, dA.byteOffset);
conv(cI.byteOffset, ri, sF.byteOffset, w, w, dI.byteOffset);
return {F: F.map((v, i) => v + dA[i] - dI[i])};
}
// main loop
function loop(c2d, channel) {
render(c2d, channel);
console.time("next");
const n = next(channel);
console.timeEnd("next");
requestAnimationFrame(_ => loop(c2d, n));
}
loop(c2d, init());
});
@bellbind
Copy link
Author

bellbind commented Jun 30, 2017

"binaryen" 33 toolset(asm2wasm, wasm-as) used as:

  • asm2wasm conv.asm.js > conv-asm.wast
  • wasm-as conv.wast > conv.wasm

NOTE: The conv.wast is based on the asm2wasm conv.asm.jsoutput.

  • remove all import, and add export memory
  • remove generated $i32u-rem func, and replace its call part with i32.rem_u operation in $get body.

@bellbind
Copy link
Author

bellbind commented Jun 30, 2017

Demo links: benchmark printed on Web Console

Benchmark Results:

  • firefox-54.0.1 and chrome-59.0.3071.115 on macOS 10.12.5: MacBook Pro (13-inch, Late 2016, Two Thunderbolt 3 ports)
firfox-54 chrome-59
ES6 70ms 240ms
wasm 160ms 170ms
asm.js 160ms 400ms

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment