On Github MortimerGoro / SuperSaiyanJS
function Test(a, b) { this.a = a; this.b = b; } Test.prototype = { hello: function() { console.log(mortimer); } } var test = new Test(1,2); test.hello();
Function scope
Prototype scope
Global scope
ReferenceError: mortimer is not defined
function Test(a, b) { this.a = a; this.b = b; } Test.prototype = { hello: function() { console.log(mortimer); } } var test = new Test(1,2); test.hello();
Function scope
Prototype scope
Global scope
document.getElementById!
var div = document.createElement("div"); div.id = "mortimer"; document.body.appendChild(div); test.hello(); //or window.mortimer
<div id="mortimer"></div>
<head><script src="jquery.js"></script></head> <body> <canvas id="mycanvas"></canvas> <script type="text/javascript"> var canvas = $('#mycanvas'); </script> </body>
<body> <canvas id="mycanvas"></canvas> <script type="text/javascript"> var canvas = mycanvas; </script> </body>
Optimized compiler creates hidden classes for objects at runtime
function Sprite(x, y, data) { this.x = x; this.y = y; this.data = data; } var sprite1 = new Sprite(100,200,0); var sprite2 = new Sprite(300,300,0);
sprite1.tag = 11; /*Warning! Another hidden class*/
var sprite3 = new Sprite(50,50, "id43"); /*Warning! Another hidden class*/
How do JS Engines represent values efficiently?
union { int64_t asInt64; double asDouble; struct { union { int32_t asInt32; JSString* str; JSObject* obj; } payload; int32_t tag; } asBits; }
31 bit integers can be "Immediates" (wrapped in the JSValue * pointer)
Prefer 31 bit integers: less memory & improved perfomance
How do JS Engines handle arrays efficiently?
<64K Arrays use contiguous storage (FAST)>64K Arrays use hash table storage (SLOW)
Numerical Arrays use optimized buffers
var array = new Array(16); a[0] = 11; //Allocation a[1] = 33; a[2] = 17;
a[3] = 3.1416; //Warning! Allocation and buffer change!
a[4] = true; //Warning! Allocation and buffer change!
[11, 33, 17, 3.1416, true]; //Single allocation
How does the Optimized compiler work?
Learns while the program runs
Inline caching: speed up runtime method bindings
function max(a, b) { return a > b ? a : b; } max(1, 2); max(300, 100); max(45, 30); //Optimized vtable (monomorphic)
max("a", "z") //Warning! Polymorphic vtable => slower
Inlining is the mother of all optimizations
Monomorphic functions are more easily inlined
Bailouts...
C++ vs JavaScript code sample
//matrix1.js // (...) function main() { var sum = 0; for (var i = 0; i < 10000; ++i) { var m = new Matrix(1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 10.10, 11.11, 12.12, 13.13, 14.14, 15.15, 16.16); for (var j = 0; j < 10000; j++) { sum+=determinant(m); } var p1 = new Point(1.1, 2.2, 3.3); for (var j = 0; j < 10000; ++j) { m.mapPoint(p1); } } } main();
C++
//matrix.cpp //(...) int main(int argc, const char * argv[]) { double sum = 0; for (int i = 0; i < 10000; ++i) { Matrix m(1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 10.10, 11.11, 12.12, 13.13, 14.14, 15.15, 16.16); for (int j = 0; j < 10000; ++j) { sum+=Matrix::determinant(m); } Point p1(1.1, 2.2, 3.3); for (int j = 0; j < 10000; j++) { m.mapPoint(p1); } sum+=p1.x; } return 0; }
V8 JavaScript shell (d8) is a very useful tool
$> time d8 matrix1.js real 0m42.106s
$> clang++ matrix.cpp $> time ./a.out real 0m6.323s
C++ is ~7 times faster than JS
Wait a moment...
$> clang++ matrix.cpp -O3 #Super Saiyan Level 3 flag $> time ./a.out real 0m0.110s
C++ is ~400 times faster than JS!
Let's Apply the lessons learned!
~200x slower?
~100x slower?
~10x slower?
~2x slower?
faster?
function determinant() { //(...) return a1 * (b2 * c3 - b3 * c2); // (...) //test try{ } catch(ex) { } }
Remove all the bailouts and...
$> time d8 matrix2.js real 0m15.628s
~150x
function Matrix(m11, m12, m13, m14, m21, m22, m23, /*(...)*/) { var m_matrix = [[],[],[],[]]; m_matrix[0][0] = m11; m_matrix[0][1] = m12; m_matrix[0][2] = m13; m_matrix[0][3] = m14; //(...) }
function Matrix(m11, m12, m13, m14, m21, m22, m23, /*(...)*/) { var m_matrix = new Array(16); m_matrix[0] = m11; m_matrix[1] = m12; m_matrix[2] = m13; m_matrix[3] = m14; //(...) }
$> time d8 matrix2.js real 0m13.967s
~140x
$> d8 matrix3.js --trace-inlining Did not inline determinant3x3 called from determinant(target text too big)
function determinant3x3(a1, a2, a3, b1, b2, b3, c1, c2, c3) { // Calculate the determinant of a 3x3 matrix // // | a1, b1, c1 | // | a2, b2, c2 | // | a3, b3, c3 | // (...) }
Remove the comments and...
$> time d8 matrix4.js real 0m4.491s
~50x
$> d8 matrix4.js --trace-inlining Did not inline determinant called from main (target AST is too large)
function determinant(matrix) { var m = matrix.m_matrix; var a1 = m[0]; var b1 = m[1]; var c1 = m[2]; var d1 = m[3]; //(...) return a1 * determinant3x3(b2, b3, b4, c2, c3, c4, d2, d3, d4) - b1 * determinant3x3(a2, a3, a4, c2, c3, c4, d2, d3, d4) + c1 * determinant3x3(a2, a3, a4, b2, b3, b4, d2, d3, d4) - d1 * determinant3x3(a2, a3, a4, b2, b3, b4, c2, c3, c4); }
return m[0] * determinant3x3(m[5], m[9], m[13], m[6], m[10], m[14], m[7], m[11], m[15]) - m[1] * determinant3x3(m[4], m[8], m[12], m[6], m[10], m[14], m[7], m[11], m[15]) + m[2] * determinant3x3(m[4], m[8], m[12], m[5], m[9], m[13], m[7], m[11], m[15]) - m[3] * determinant3x3(m[4], m[8], m[12], m[5], m[9], m[13], m[6], m[10], m[14]);
$> time d8 matrix5.js real 0m1.794s
~16x
$> d8 matrix5.js --trace-inlining | grep mapPoint $>
Matrix.prototype = { mapPoint: function(p) { var x = this.m_matrix[12] + p.x * this.m_matrix[0] + p.y * this.m_matrix[4] + p.z * this.m_matrix[8]; var y = this.m_matrix[13] + p.x * this.m_matrix[1] + p.y * this.m_matrix[5] + p.z * this.m_matrix[9]; //(...) } }
function mapPoint(m_matrix,p) { var x = m_matrix.a41 + p.x * m_matrix.a11 + p.y * m_matrix.a21 + p.z * m_matrix.a31; var y = m_matrix.a42 + p.x * m_matrix.a12 + p.y * m_matrix.a22 + p.z * m_matrix.a32; //(...) }
$> time d8 matrix6.js real 0m0.698s
~6x
for (var i= 0; i < sprites.length; ++i) { ctx.save(); sprites[i].render(); ctx.restore(); }
Canvas 2D context is huge...
for (var i= 0; i < sprites.length; ++i) { ctx.translate(x,y); //transform sprites[i].render(); ctx.translate(-x,-y); //inverse transform }
Limited API: Transformations
Tessellation is slow
//Cache rendered path to a canvas var canvas = document.createElement("canvas"); canvas.width = 500; canvas.height = 500; var ctx = canvas.getContext("2d"); ctx.beginPath(); ctx.moveTo(100,100); ctx.bezierCurveTo(20,100,200,100,200,20); ctx.bezierCurveTo(30,300,100,300,50,10); ctx.fill(); ctx.stroke(); mainctx.drawImage(canvas, 0, 0);
Always pack your textures
http://www.codeandweb.com/texturepacker
var canvas = document.createElement("canvas"); canvas.width = 500; canvas.height = 50; var ctx = canvas.getContext("2d"); ctx.font = "30px Saiyan-Sans"; ctx.fillText("Hello world!",0,0); var texture = gl.createTexture(); gl.bindTexture(texture); gl.texImage2D(canvas);
Better use sprited Fonts! http://71squared.com/glyphdesigner
//initialization gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.DYNAMIC_DRAW); //render tick gl.bindBuffer(gl.ARRAY_BUFFER, verticesBuffer); gl.bufferSubData(gl.ARRAY_BUFFER, 0, vertices);
Very Slow on mobile devices
Use vertexAttribPointer instead!
gl.bindBuffer(gl.ARRAY_BUFFER, 0); gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, vertices);
Makes the development easier, but...
Too many state changes
We've suffered it with three.js
About us
HTML5 has huge advantages...
...but it's horrible on mobile
Solution: create our own tech!
Goal: Feel native in every way possible
How does it work?
A big challenge... Are we crazy?
Yes, but we're from Bilbao...
... and we are Dragon Ball fans ;)
The first test
#include <JavaScriptCore/JavaScriptCore.h> int main(int argc, const char * argv[]) { JSContextRef ctx = JSGlobalContextCreate(NULL); const char * js = "console.log('hello world!');"; JSStringRef script = JSStringCreateWithUTF8CString(js); JSValueRef exception = NULL; JSValueRef result = JSEvaluateScript(ctx, script, NULL, NULL, 0, &exception); JSStringRelease(script); JSGlobalContextRelease(ctx); return 0; }
Challenge: Multiple JavaScript Engine support
#include "v8.h" using namespace v8; int main(int argc, const char * argv[]) { Isolate* isolate = Isolate::GetCurrent();. HandleScope handle_scope(isolate); Handle<Context> context = Context::New(isolate); Persistent<Context> persistent_context(isolate, context); Context::Scope context_scope(context); Handle<String> source = String::New('console.log('Hello world!')'); Handle<Script> script = Script::Compile(source); Handle<Value> result = script->Run(); persistent_context.Dispose(); return 0; }
Solution?
C++ Magic to solve the puzzle
//magic typedefs typedef v8::Persistent<v8::Context> * JSContextRef; typedef v8::Handle<v8::Value> JSValueRef; typedef v8::Handle<v8::Object> JSObjectRef;
//magic macros #define JSEntryPoint v8::Isolate::Scope isolateScope(getSharedIsolate()); \ v8::Locker v8Lock(getSharedIsolate()); \ v8::HandleScope handle_scope(getSharedIsolate()); #define JSContextScope(ctx) v8::Context::Scope context_scope(v8::Handle<v8::Context>::New(getSharedIsolate(),*ctx));
A really challenging puzzle (ex. 1000 fake functions)