Generate vs Interpret
JavaScript performance comparison
Info
This is to explore how much slow interpreting would be over generating js and evaling.
Preparation code
<script>
Benchmark.prototype.setup = function() {
function Buffer(arraybuffer, offset, length) {
var data;
if (arguments.length === 1) {
if (Array.isArray(arraybuffer)) {
data = arraybuffer;
arraybuffer = new ArrayBuffer(data.length);
var v = new Uint8Array(arraybuffer);
for (var i = 0, l = data.length; i < l; i++) {
v[i] = data[i];
}
}
else if (typeof arraybuffer === "number") {
arraybuffer = new ArrayBuffer(arraybuffer);
}
offset = 0;
length = arraybuffer.byteLength;
}
var buffer = new Uint8Array(arraybuffer, offset, length);
buffer.__proto__ = Buffer.prototype;
buffer.view = new DataView(arraybuffer, offset, length);
buffer.offset = offset;
buffer.length = length;
return buffer;
}
Buffer.prototype.__proto__ = Uint8Array.prototype;
Buffer.prototype.slice = function (start, end) {
return Buffer(this.buffer, start + this.offset, end - start);
};
Buffer.prototype.readUInt32LE = function (offset) {
return this.view.getUint32(offset, true);
};
Buffer.prototype.readUInt16LE = function (offset) {
return this.view.getUint16(offset, true);
};
Buffer.prototype.readInt16LE = function (offset) {
return this.view.getInt16(offset, true);
};
Buffer.prototype.readDoubleLE = function (offset) {
return this.view.getFloat64(offset, true);
};
Buffer.prototype.writeDoubleLE = function (value, offset) {
return this.view.setFloat64(offset, value, true);
};
Buffer.prototype.writeUInt32LE = function (value, offset) {
this.view.setUint32(offset, value, true);
};
Buffer.prototype.toString = function (encoding) {
if (!encoding) {
return String.fromCharCode.apply(null, this);
}
};
function fn0() {
var $, $$, $0, $1, $2, $3;
var state = "0";
for(;;) {
loop: switch(state) {
case "0":
$0 = [null,"This","is","a","stream?"];
$1 = 0x1;
$2 = this["print"];
$3 = "before";
$2($3);
case "5":
$2 = $0[$1];
if (!$2) { state = "e"; break loop; }
// LOOP
$2 = this["print"];
$3 = $0[$1];
$2($3);
$1 = $1 + 1;
state = "5"; break loop;
case "e":
$2 = this["print"];
$3 = "after";
$2($3);
return [];
}
}
}
// Used to consume bytes from a bytecode stream
function Parser(buffer) {
this.buffer = buffer;
this.index = 0;
this.mark = 0;
}
Parser.prototype.B = function () {
return this.buffer[this.index++];
};
// Consume 16 bit value from stream and move pointer
Parser.prototype.H = function () {
var value = this.buffer.readUInt16LE(this.index);
this.index += 2;
return value;
};
// Consume 32 bit value from stream and move pointer
Parser.prototype.W = function () {
var value = this.buffer.readUInt32LE(this.index);
this.index += 4;
return value;
};
// Decode ULEB128 from the stream
// http://en.wikipedia.org/wiki/LEB128
Parser.prototype.U = function () {
var value = 0;
var shift = 0;
do {
var byte = this.buffer[this.index++];
value |= (byte & 0x7f) << shift;
shift += 7;
} while (byte >= 0x80);
return value >>> 0;
};
// For mapping enum integer values to opcode names
var opcodes = [
"ISLT", "ISGE", "ISLE", "ISGT", "ISEQV", "ISNEV", "ISEQS", "ISNES", "ISEQN",
"ISNEN", "ISEQP", "ISNEP", "ISTC", "ISFC", "IST", "ISF", "MOV", "NOT", "UNM",
"LEN", "ADDVN", "SUBVN", "MULVN", "DIVVN", "MODVN", "ADDNV", "SUBNV", "MULNV",
"DIVNV", "MODNV", "ADDVV", "SUBVV", "MULVV", "DIVVV", "MODVV", "POW", "CAT",
"KSTR", "KCDATA", "KSHORT", "KNUM", "KPRI", "KNIL", "UGET", "USETV", "USETS",
"USETN", "USETP", "UCLO", "FNEW", "TNEW", "TDUP", "GGET", "GSET", "TGETV",
"TGETS", "TGETB", "TSETV", "TSETS", "TSETB", "TSETM", "CALLM", "CALL",
"CALLMT", "CALLT", "ITERC", "ITERN", "VARG", "ISNEXT", "RETM", "RET", "RET0",
"RET1", "FORI", "JFORI", "FORL", "IFORL", "JFORL", "ITERL", "IITERL",
"JITERL", "LOOP", "ILOOP", "JLOOP", "JMP", "FUNCF", "IFUNCF", "JFUNCF",
"FUNCV", "IFUNCV", "JFUNCV", "FUNCC", "FUNCCW"
];
// For mapping opcode names to parse instructions
var bcdef = {
ISLT: {ma: "var", md: "var", mt: "lt"},
ISGE: {ma: "var", md: "var", mt: "lt"},
ISLE: {ma: "var", md: "var", mt: "le"},
ISGT: {ma: "var", md: "var", mt: "le"},
ISEQV: {ma: "var", md: "var", mt: "eq"},
ISNEV: {ma: "var", md: "var", mt: "eq"},
ISEQS: {ma: "var", md: "str", mt: "eq"},
ISNES: {ma: "var", md: "str", mt: "eq"},
ISEQN: {ma: "var", md: "num", mt: "eq"},
ISNEN: {ma: "var", md: "num", mt: "eq"},
ISEQP: {ma: "var", md: "pri", mt: "eq"},
ISNEP: {ma: "var", md: "pri", mt: "eq"},
ISTC: {ma: "dst", md: "var"},
ISFC: {ma: "dst", md: "var"},
IST: {md: "var"},
ISF: {md: "var"},
MOV: {ma: "dst", md: "var"},
NOT: {ma: "dst", md: "var"},
UNM: {ma: "dst", md: "var", mt: "unm"},
LEN: {ma: "dst", md: "var", mt: "len"},
ADDVN: {ma: "dst", mb: "var", mc: "num", mt: "add"},
SUBVN: {ma: "dst", mb: "var", mc: "num", mt: "sub"},
MULVN: {ma: "dst", mb: "var", mc: "num", mt: "mul"},
DIVVN: {ma: "dst", mb: "var", mc: "num", mt: "div"},
MODVN: {ma: "dst", mb: "var", mc: "num", mt: "mod"},
ADDNV: {ma: "dst", mb: "var", mc: "num", mt: "add"},
SUBNV: {ma: "dst", mb: "var", mc: "num", mt: "sub"},
MULNV: {ma: "dst", mb: "var", mc: "num", mt: "mul"},
DIVNV: {ma: "dst", mb: "var", mc: "num", mt: "div"},
MODNV: {ma: "dst", mb: "var", mc: "num", mt: "mod"},
ADDVV: {ma: "dst", mb: "var", mc: "var", mt: "add"},
SUBVV: {ma: "dst", mb: "var", mc: "var", mt: "sub"},
MULVV: {ma: "dst", mb: "var", mc: "var", mt: "mul"},
DIVVV: {ma: "dst", mb: "var", mc: "var", mt: "div"},
MODVV: {ma: "dst", mb: "var", mc: "var", mt: "mod"},
POW: {ma: "dst", mb: "var", mc: "var", mt: "pow"},
CAT: {ma: "dst", mb: "rbase", mc: "rbase", mt: "concat"},
KSTR: {ma: "dst", md: "str"},
KCDATA: {ma: "dst", md: "cdata"},
KSHORT: {ma: "dst", md: "lits"},
KNUM: {ma: "dst", md: "num"},
KPRI: {ma: "dst", md: "pri"},
KNIL: {ma: "base", md: "base"},
UGET: {ma: "dst", md: "uv"},
USETV: {ma: "uv", md: "var"},
USETS: {ma: "uv", md: "str"},
USETN: {ma: "uv", md: "num"},
USETP: {ma: "uv", md: "pri"},
UCLO: {ma: "rbase", md: "jump"},
FNEW: {ma: "dst", md: "func", mt: "gc"},
TNEW: {ma: "dst", md: "lit", mt: "gc"},
TDUP: {ma: "dst", md: "tab", mt: "gc"},
GGET: {ma: "dst", md: "str", mt: "index"},
GSET: {ma: "var", md: "str", mt: "newindex"},
TGETV: {ma: "dst", mb: "var", mc: "var", mt: "index"},
TGETS: {ma: "dst", mb: "var", mc: "str", mt: "index"},
TGETB: {ma: "dst", mb: "var", mc: "lit", mt: "index"},
TSETV: {ma: "var", mb: "var", mc: "var", mt: "newindex"},
TSETS: {ma: "var", mb: "var", mc: "str", mt: "newindex"},
TSETB: {ma: "var", mb: "var", mc: "lit", mt: "newindex"},
TSETM: {ma: "base", md: "num", mt: "newindex"},
CALLM: {ma: "base", mb: "lit", mc: "lit", mt: "call"},
CALL: {ma: "base", mb: "lit", mc: "lit", mt: "call"},
CALLMT: {ma: "base", md: "lit", mt: "call"},
CALLT: {ma: "base", md: "lit", mt: "call"},
ITERC: {ma: "base", mb: "lit", mc: "lit", mt: "call"},
ITERN: {ma: "base", mb: "lit", mc: "lit", mt: "call"},
VARG: {ma: "base", mb: "lit", mc: "lit"},
ISNEXT: {ma: "base", md: "jump"},
RETM: {ma: "base", md: "lit"},
RET: {ma: "rbase", md: "lit"},
RET0: {ma: "rbase", md: "lit"},
RET1: {ma: "rbase", md: "lit"},
FORI: {ma: "base", md: "jump"},
JFORI: {ma: "base", md: "jump"},
FORL: {ma: "base", md: "jump"},
IFORL: {ma: "base", md: "jump"},
JFORL: {ma: "base", md: "lit"},
ITERL: {ma: "base", md: "jump"},
IITERL: {ma: "base", md: "jump"},
JITERL: {ma: "base", md: "lit"},
LOOP: {ma: "rbase", md: "jump"},
ILOOP: {ma: "rbase", md: "jump"},
JLOOP: {ma: "rbase", md: "lit"},
JMP: {ma: "rbase", md: "jump"},
FUNCF: {ma: "rbase"},
IFUNCF: {ma: "rbase"},
JFUNCF: {ma: "rbase", md: "lit"},
FUNCV: {ma: "rbase"},
IFUNCV: {ma: "rbase"},
JFUNCV: {ma: "rbase", md: "lit"},
FUNCC: {ma: "rbase"},
FUNCCW: {ma: "rbase"}
};
var kgctypes = ["CHILD", "TAB", "I64", "U64", "COMPLEX", "STR"];
var ktabtypes = ["NIL", "FALSE", "TRUE", "INT", "NUM", "STR"];
var kgcdecs = {
TAB: function (parser) {
var narray = parser.U();
var nhash = parser.U();
if (narray) {
if (nhash) {
throw new Error("TODO: implement mixed tables");
}
var tab = new Array(narray);
for (var i = 0; i < narray; i++) {
tab[i] = readktabk(parser);
}
return tab;
}
if (nhash) {
var tab = {};
for (var i = 0; i < nhash; i++) {
var key = readktabk(parser);
if (typeof key !== "string") throw new Error("TODO: Implement non-string keys");
tab[key] = readktabk(parser);
}
return tab
}
return {};
},
I64: function (parser) {
throw new Error("TODO: Implement I64 kgc decoder");
},
U64: function (parser) {
throw new Error("TODO: Implement U64 kgc decoder");
},
COMPLEX: function (parser) {
throw new Error("TODO: Implement COMPLEX kgc decoder");
},
STR: function (parser, len) {
len -= 5; // Offset for STR enum
var value = parser.buffer.slice(parser.index, parser.index + len).toString();
parser.index += len;
return value;
}
};
var ktabdecs = {
NIL: function (parser) {
return null;
},
FALSE: function (parser) {
return false;
},
TRUE: function (parser) {
return true;
},
INT: function (parser) {
return parser.U();
},
NUM: readknum,
STR: kgcdecs.STR
};
// Read a constant table key or value
function readktabk(parser) {
var ktabtype = parser.U();
return ktabdecs[ktabtypes[ktabtype] || "STR"](parser, ktabtype);
}
function readknum(parser) {
var isnum = parser.buffer[parser.index] & 1;
var lo = parser.U() >> 1;
if (isnum) {
var buf = new Buffer(8);
var hi = parser.U();
buf.writeUInt32LE(lo >>> 0, 0);
buf.writeUInt32LE(hi, 4);
var num = buf.readDoubleLE(0);
return num;
}
return lo;
}
function Proto(buffer, env) {
this.env = env || {};
var parser = new Parser(buffer);
// flagsB numparamsB framesizeB numuvB numkgcU numknU numbcU [debuglenU [firstlineU numlineU]]
var flags = parser.B();
var numparams = parser.B();
var framesize = parser.B();
var numuv = parser.B();
var numkgc = parser.U();
var numkn = parser.U();
var numbc = parser.U();
// bcinsW* uvdataH* kgc* knum*
this.bc = buffer.slice(parser.index, parser.index + numbc * 4);
var bcins = new Array(numbc);
for (var i = 0; i < numbc; i++) {
bcins[i] = parser.W();
}
var uvdata = new Array(numuv);
for (var i = 0; i < numuv; i++) {
uvdata[i] = parser.H();
}
var constants = new Array(numkgc + numkn);
var childc = 0;
for (var i = 0; i < numkgc; i++) {
var kgctype = parser.U();
var type = kgctypes[kgctype] || "STR";
if (type === "CHILD") {
constants[i + numkn] = --childc;
}
else {
constants[i + numkn] = kgcdecs[type](parser, kgctype);
}
}
for (var i = 0; i < numkn; i++) {
constants[i] = readknum(parser);
}
// Make sure we consumed all the bytes properly
if (parser.index !== buffer.length) throw new Error((buffer.length - parser.index) + " bytes leftover");
function parseArg(type, val, i) {
switch (type) {
case "lit": return val >>> 0;
case "lits": return val & 0x80 ? val - 0x100 : val;
case "pri": return val === 0 ? null : val === 1 ? false : true;
case "num": return constants[val];
case "str":
case "tab":
case "func":
case "cdata":
return constants[constants.length - val - 1];
case "uv": return uvdata[val];
case "jump":
return val- 0x8000;
default:
return val;
}
}
bcins = bcins.map(function (word) {
var opcode = opcodes[word & 0xff];
var def = bcdef[opcode];
var op = {
op: opcode,
};
if (def.ma) {
op.a = parseArg(def.ma, (word >>> 8) & 0xff, i);
}
if (def.mb) {
op.b = parseArg(def.mb, word >>> 24, i);
}
if (def.mc) {
op.c = parseArg(def.mc, (word >>> 16) & 0xff, i);
}
if (def.md) {
op.d = parseArg(def.md, word >>> 16, i);
}
return op;
});
this.bcins = bcins;
this.slots = new Array(framesize);
this.uvdata = uvdata;
this.constants = constants;
}
Proto.prototype.exec3 = function () {
var pc = 0;
var offset;
for(;;) {
var bc = this.bcins[pc++];
switch(bc.op) {
case "TDUP":
var p = bc.d, tab;
if (Array.isArray(p)) {
tab = new Array(p.length);
for (var i = 0, l = p.length; i < l; i++) {
tab[i] = p[i];
}
}
else {
tab = {};
var keys = Object.keys(p);
for (var i = 0, l = keys.length; i < l; i++) {
var key = keys[i];
tab[key] = p[key];
}
}
this.slots[bc.a] = tab;
continue;
case "KSHORT":
this.slots[bc.a] = bc.d;
continue;
case "GGET":
this.slots[bc.a] = this.env[bc.d];
continue;
case "KSTR":
this.slots[bc.a] = bc.d;
continue;
case "CALL":
var args = this.slots.slice(bc.a + 1, bc.a + bc.c);
var ret = this.slots[bc.a].apply(null, args);
for (var i = 0, l = bc.b - 1; i < l; i++) {
this.slots[bc.a + i] = ret[i];
}
continue;
case "TGETV":
this.slots[bc.a] = this.slots[bc.b][this.slots[bc.c]];
continue;
case "ISF":
var expr = this.slots[bc.d];
if (!(expr === null || expr == false || expr == undefined)) {
pc++;
}
continue;
case "JMP":
pc += bc.d;
continue;
case "LOOP":
continue;
case "ADDVN":
this.slots[bc.a] = this.slots[bc.b] + bc.c;
continue;
case "RET0":
return [];
}
}
}
Proto.prototype.exec2 = function () {
var pc = 0;
var offset;
for(;;pc++) {
switch(opcodes[this.bc[offset = pc * 4]]) {
case "TDUP":
var a = this.bc[offset + 1];
var d = this.bc.readUInt16LE(offset + 2);
d = this.constants[this.constants.length - d - 1];
this.slots[a] = JSON.parse(JSON.stringify(d));
continue;
case "KSHORT":
var a = this.bc[offset + 1];
var d = this.bc.readInt16LE(offset + 2);
this.slots[a] = d;
continue;
case "GGET":
var a = this.bc[offset + 1];
var d = this.bc.readUInt16LE(offset + 2);
d = this.constants[this.constants.length - d - 1];
this.slots[a] = this.env[d];
continue;
case "KSTR":
var a = this.bc[offset + 1];
var d = this.bc.readUInt16LE(offset + 2);
d = this.constants[this.constants.length - d - 1];
this.slots[a] = d;
continue;
case "CALL":
var a = this.bc[offset + 1];
var b = this.bc[offset + 3];
var c = this.bc[offset + 2];
var args = this.slots.slice(a + 1, a + c);
var ret = this.slots[a].apply(null, args);
for (var i = 0; i < b - 1; i++) {
this.slots[a + i] = ret[i];
}
continue;
case "TGETV":
var a = this.bc[offset + 1];
var b = this.bc[offset + 3];
var c = this.bc[offset + 2];
this.slots[a] = this.slots[b][this.slots[c]];
continue;
case "ISF":
var d = this.bc.readUInt16LE(offset + 2);
d = this.slots[d];
if (!(d === null || d == false || d == undefined)) {
pc++;
}
continue;
case "JMP":
var d = this.bc.readUInt16LE(offset + 2) - 0x8000;
pc += d;
continue;
case "LOOP":
continue;
case "ADDVN":
var a = this.bc[offset + 1];
var b = this.bc[offset + 3];
var c = this.bc[offset + 2];
c = this.constants[c];
this.slots[a] = this.slots[b] + c;
continue;
case "RET0":
return [];
}
}
}
Proto.prototype.execute = function () {
this.pc = 0;
var offset;
var ret;
while (!(ret = this[opcodes[this.bc[offset = this.pc * 4]]](offset))) { this.pc++; }
return ret;
}
Proto.prototype.TDUP = function (offset) {
var a = this.bc[offset + 1];
var d = this.bc.readUInt16LE(offset + 2);
d = this.constants[this.constants.length - d - 1];
this.slots[a] = JSON.parse(JSON.stringify(d));
};
Proto.prototype.KSHORT = function (offset) {
var a = this.bc[offset + 1];
var d = this.bc.readInt16LE(offset + 2);
this.slots[a] = d;
};
Proto.prototype.GGET = function (offset) {
var a = this.bc[offset + 1];
var d = this.bc.readUInt16LE(offset + 2);
d = this.constants[this.constants.length - d - 1];
this.slots[a] = this.env[d];
};
Proto.prototype.KSTR = function (offset) {
var a = this.bc[offset + 1];
var d = this.bc.readUInt16LE(offset + 2);
d = this.constants[this.constants.length - d - 1];
this.slots[a] = d;
};
Proto.prototype.CALL = function (offset) {
var a = this.bc[offset + 1];
var b = this.bc[offset + 3];
var c = this.bc[offset + 2];
var args = this.slots.slice(a + 1, a + c);
var ret = this.slots[a].apply(null, args);
for (var i = 0; i < b - 1; i++) {
this.slots[a + i] = ret[i];
}
};
Proto.prototype.TGETV = function (offset) {
var a = this.bc[offset + 1];
var b = this.bc[offset + 3];
var c = this.bc[offset + 2];
this.slots[a] = this.slots[b][this.slots[c]];
};
Proto.prototype.ISF = function (offset) {
var d = this.bc.readUInt16LE(offset + 2);
d = this.slots[d];
if (!(d === null || d == false || d == undefined)) {
this.pc++;
}
};
Proto.prototype.JMP = function (offset) {
var d = this.bc.readUInt16LE(offset + 2) - 0x8000;
this.pc += d;
};
Proto.prototype.LOOP = function (offset) {
};
Proto.prototype.ADDVN = function (offset) {
var a = this.bc[offset + 1];
var b = this.bc[offset + 3];
var c = this.bc[offset + 2];
c = this.constants[c];
this.slots[a] = this.slots[b] + c;
};
Proto.prototype.RET0 = function (offset) {
return [];
};
// Set up some global functions
var _G = {
print: function () {
console.log(Array.prototype.join.call(arguments, " "));
return [];
}
};
var _G2 = {
print: function () {
//console.log(Array.prototype.join.call(arguments, " "));
return [];
}
};
var program = new Buffer([
2,0,4,0,4,1,18, // HEADER
51,0,0,0, // TDUP 0 0
39,1,1,0, // KSHORT 1 1
52,2,1,0, // GGET 2 1 print
37,3,2,0, // KSTR 3 2 before
62,2,2,1, // CALL 2 1 2
54,2,1,0, // TGETV 2 0 1
15,0,2,0, // ISF 2
84,3,6,128, // JMP 3 +7
81,2,5,128, // LOOP 2 +6
52,2,1,0, // GGET 2 1 print
54,3,1,0, // TGETV 3 0 1
62,2,2,1, // CALL 2 1 2
20,1,0,1, // ADDVN 1 1 0 1
84,2,247,127, // JMP 2 -8
52,2,1,0, // GGET 2 1 print
37,3,3,0, // KSTR 3 3 after
62,2,2,1, // CALL 2 1 2
71,0,1,0, // RET0 0 1
10,97,102,116,101,114, // "after"
11,98,101,102,111,114,101, // "before"
10,112,114,105,110,116, // "print"
1,5,0,0,9,84,104,105,115,7,105,115,6,97,12,115,116,114,101,97,109,63,
2]
);
var p = new Proto(program, _G);
var p2 = new Proto(program, _G2);
};
</script>
Test runner
Warning! For accurate results, please disable Firebug before running the tests. (Why?)
Java applet disabled.
| Test | Ops/sec | |
|---|---|---|
Compiled real |
|
pending… |
Compiled raw |
|
pending… |
interpreted real |
|
pending… |
interpreted raw |
|
pending… |
switch buffer real |
|
pending… |
switch buffer raw |
|
pending… |
switch object real |
|
pending… |
switch object raw |
|
pending… |
Compare results of other browsers
Revisions
You can edit these tests or add even more tests to this page by appending /edit to the URL. Here’s a list of current revisions for this page:
- Revision 1: published by Tim Caswell
- Revision 2: published
- Revision 3: published by Tim Caswell and last updated
0 comments