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"
];
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 Env() {}
Env.prototype.print = function () {
}
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);
parser.index += numbc * 4;
var uvdata = new Array(numuv);
for (var i = 0; i < numuv; i++) {
uvdata[i] = parser.H();
}
var constants = new Array(numkgc + numkn);
// var childc = protoIndex;
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");
this.slots = new Array(framesize);
this.uvdata = uvdata;
this.constants = constants;
}
Proto.prototype.execute = function () {
this.pc = 0;
var ret;
while (!(ret = this[opcodes[this.bc[offset = this.pc * 4]]](offset))) {}
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));
this.pc++;
};
Proto.prototype.KSHORT = function (offset) {
var a = this.bc[offset + 1];
var d = this.bc.readInt16LE(offset + 2);
this.slots[a] = d;
this.pc++;
};
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];
this.pc++;
};
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;
this.pc++;
};
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];
}
this.pc++;
};
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]];
this.pc++;
};
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++;
}
else {
this.pc += 2;
}
};
Proto.prototype.JMP = function (offset) {
var d = this.bc.readUInt16LE(offset + 2) - 0x8000 + 1;
this.pc += d;
};
Proto.prototype.LOOP = function (offset) {
this.pc++;
};
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;
this.pc++;
};
Proto.prototype.RET0 = function (offset) {
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 [];
}
}
}
// 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 code = 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(code, _G);
var p2 = new Proto(code, _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 interpret real |
|
pending… |
switch interpret 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