"Super" hoisting
Formatted the code so that it can be read:
class thing {
constructor {}
doSomething(x, y) {
let a = x + y;
let b = ay;
let c = bx;
return (c);
}
}
class thing {
constructor {
let _doSomething_a = null;
let _doSomething_b = null;
let _doSomething_c = null;
}
doSomething(x, y) {
this._doSomething_a = x + y;
this._doSomething_b = ay;
this._doSomething_c = bx;
return (this._doSomething_c);
}
}
On May 12, 2016, at 9:39 AM, Brian Barnes <ggadwa at charter.net> wrote:
... If I call doSomething a lot of times, I get a lot of objects to be tracked and GC’d. Yes, I can rewrite that code to obviously eliminate them, but pretend there is actually something interesting happening here that requires those variables. And engines can be smart enough to mark them and deal with them with the function ends (I don’t know if this is done.)
What I’m thinking is some kind of class based hint that would “super” hoist all local function variables. It would be sugar but it would transform it into:
Local variables like a,b,c in your example are probably the least expensive construct in JS. In your example, there are no objects created by doSomething, not state that persists after doSomething returns, and no GC pressure. Anything you do WRT moving such local state into object properties is going to be more expensive.
Not by my testing, at least.
My original example is a simple one to explain what the procedure is. Here’s a more complex example, from my code, from my mesh classes. This function updates the vertices in the mesh based on the position of the bones of a skeleton, it gets called every frame for every model mesh that can be seen in the scene:
updateVertexesToPoseAndPosition(view,skeleton,angle,position)
{
var n,v;
var bone,parentBone;
// move all the vertexes
var vIdx=0;
var nIdx=0;
for (n=0;n!==this.vertexCount;n++) {
v=this.vertexList[n];
// bone movement
bone=skeleton.bones[v.boneIdx];
this.rotVector.setFromPoint(v.vectorFromBone);
this.rotVector.rotate(bone.curPoseAngle);
this.rotVector.x=bone.curPosePosition.x+this.rotVector.x;
this.rotVector.y=bone.curPosePosition.y+this.rotVector.y;
this.rotVector.z=bone.curPosePosition.z+this.rotVector.z;
this.rotNormal.setFromPoint(v.normal);
this.rotNormal.rotate(bone.curPoseAngle);
// average in any parent movement
if (v.parentBoneIdx!==-1) {
parentBone=skeleton.bones[v.parentBoneIdx];
this.parentRotVector.setFromPoint(v.vectorFromParentBone);
this.parentRotVector.rotate(parentBone.curPoseAngle);
this.parentRotVector.x=parentBone.curPosePosition.x+this.parentRotVector.x;
this.parentRotVector.y=parentBone.curPosePosition.y+this.parentRotVector.y;
this.parentRotVector.z=parentBone.curPosePosition.z+this.parentRotVector.z;
this.parentRotNormal.setFromPoint(v.normal);
this.parentRotNormal.rotate(parentBone.curPoseAngle);
this.rotVector.average(this.parentRotVector);
this.rotNormal.average(this.parentRotNormal);
}
// whole model movement
this.rotVector.rotate(angle);
this.drawVertices[vIdx++]=this.rotVector.x+position.x;
this.drawVertices[vIdx++]=this.rotVector.y+position.y;
this.drawVertices[vIdx++]=this.rotVector.z+position.z;
this.rotNormal.rotate(angle);
this.drawNormals[nIdx++]=this.rotNormal.x;
this.drawNormals[nIdx++]=this.rotNormal.y;
this.drawNormals[nIdx++]=this.rotNormal.z;
}
// set the buffers
var gl=view.gl;
gl.bindBuffer(gl.ARRAY_BUFFER,this.vertexPosBuffer);
gl.bufferData(gl.ARRAY_BUFFER,this.drawVertices,gl.DYNAMIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER,this.vertexNormalBuffer);
gl.bufferData(gl.ARRAY_BUFFER,this.drawNormals,gl.STATIC_DRAW);
}
Notice that rotVector, rotNormal, parentRotVector, and parentRotNormal, are all “this”, i.e., globals. They are only used in this function, no where else in the class. If I put them as local variables, I get enormous GC pauses. If I don’t, I get none (granted, I have to do this everywhere, so it’s just not this function.)
The example I gave existed only to show how it would work; not a real example. Here’s a real example, that shows obvious problems with local variables. Yes, these are objects.
Not that moving the objects would ONLY be something that happened at compile time and would be a hint; they wouldn’t be effected in the warm up runs. You’d almost have to say that same hint would force a compile (which has it’s own problems.)
[>] Brian
Sounds like a potential JS engine optimization, rather than a language feature per se.
It could be, but there is a difference between:
var x;
and:
this.x
Because you’ll get leakage from the last run, so it would probably be something that would need to be hinted, unless the engine was smart enough to reset everything it hoisted (which is doable) but not optimal, as you might always know that will be discarded (a really smart engine could probably figure this out.)
Again, all this is to try to eliminate GC as much as possible. It’s not a philosophical argument, GC has pros and cons, it’s just bad for performance sensitive stuff like games. I do this all by hand now, so a super hoist would just eliminate a lot of hard to read code I’ve written!
[>] Brian
Brian Barnes wrote:
It could be, but there is a difference between:
var x;
and:
this.x
You should probably do var x = XPool.get();
and at the end
XPool.put(x);
and do XPool
s for every possible X type which you want
to not GC. And then happily use local variable, as it should be. If
things are not very parallel / reentrant, the pools will likely only
contains zero / single element.
It's recurring theme - special syntax to allow/force engines to optimize some code pattern.
What do you want is in fact some kind of C. Or "Cython in Javascript". I think your needs can be satisfied by C/C++ addons in Node.js or WebAssembly in browser - it's better to use proper tool rather than trying to fit square (semi-manual memory management) in triangle hole (dynamic language with GC).
I do not understand how having local variable should change any GC pattern unless you are creating them in the for-loop body. Even though, Scalar Replacement optimizations (which are implemented in all engine?) should be able to remove these allocations once the optimizing JIT is reached, unless the "setFromPoint", "rotate", and "average" functions are not inlined.
Certainly, I’d love webassembly, but as of now, it’s not something that’s part of the spec, or something that I can use right now, and I also want to see how far I can push the language as an experiment.
I think there is a place for improving the language as is; it’s surprising how far javascript has evolved. Modules being implement, and then in the future hopefully types and a bit more control over the GC and I think the language will be good for most everything. How portable with about everything out there is the one thing that draws me to it.
[>] Brian
var x = XPool.get();
I think Brian is saying that local vars are GCed, so that solution doesn't work (creating many local variables at 60fps)
Or "Cython in Javascript". I think your needs can be satisfied by C/C++
addons in Node.js or WebAssembly in browser
But we want to stay in JavaScript because we like the language. Plus, writing a framework in JavaScript has the obvious advantage that a beginner web programmer can just start using it easily (HTML/JAvaScript is like the gateway drug of programming nowadays so there's certain advantages to writing a library in JS as opposed to in C or compiling to WebAssembly).
I do not understand how having local variable should change any GC pattern
unless you are creating them in the for-loop body.
Having locals in a for-loop is the same as having locals in a function that gets called at 60fps in an animation loop (which is the case).
On May 13, 2016, at 11:09 AM, Herby Vojčík <herby at mailbox.sk> wrote:
Brian Barnes wrote:
It could be, but there is a difference between:
var x;
and:
this.x
You should probably do
var x = XPool.get();
and at the endXPool.put(x);
and doXPool
s for every possible X type which you want to not GC. And then happily use local variable, as it should be. If things are not very parallel / reentrant, the pools will likely only contains zero / single element.
I already do this, I was just showing possible problems with engines doing this without a hint. Right now if I have anything that’s an object that only lives within a function, I move it to the class constructor, no need for the extra additions of a pool.
[>] Brian
Nicolas B. Pierron wrote:
I do not understand how having local variable should change any GC pattern unless you are creating them in the for-loop body.
If I understand correctly, the problem is in not reusing the objects. If
you do var x = new X()
in every call of the method, GC should take
care of every new X()
. OTOH, they are young object, and young space
should be this sort-of pool like quick thing.
But if creating them / deleting them is actually taking it's toll, having reused one object in this.x can save GC.
That's why I suggested that what is in way wanted here, is in fact pooling of X instances (and maybe not necessarily by instance of their client). Which can be inlined as well, as it is pretty easy:
var x = poolOfXs.pop() || new X();
to get an x, and
poolOfXs.push(x);
to put it back. This may actually save even more GC (as only as much instances are created as needed).
But again, I would still like to believe that garbage coilector is best pooler (though it needs to reinitialize the object each time, but OP told he was getting gc pauses).
I can only tell you what I’ve tested and observed, not sure of the inner workings of any javascript engine. Current alpha of my project is here:
www.klinksoftware.com/ws, www.klinksoftware.com/ws
Recommend latest firefox or chrome. Github link for the code is there. This is a everything autogenerated fps, entirely in javascript. Performance is great, but I was getting bad performance, and it’s all tracked down to object allocation. Because it’s 3D, I’ve made lots of classes — 3d points, vertices (includes the normal, the tangent, the bi-tangent, bone index), bones, skeletons, meshes, lights, maps, etc. To do a lot of the math I’m always making vectors and points. If I move all that allocation, and just re-use them in the functions, I pretty much eliminate GC pauses.
I think I keep confusing people by bad examples, it’s objects that are the problem, not primitives. I’ll be better in the future. I just try to carve out examples to their bare minimum. My bad.
[>] Brian
On Fri, May 13, 2016 at 4:13 PM, /#!/JoePea <joe at trusktr.io> wrote:
I do not understand how having local variable should change any GC pattern unless you are creating them in the for-loop body.
Having locals in a for-loop is the same as having locals in a function that gets called at 60fps in an animation loop (which is the case).
Scope wise, yes, but a different init value would be used every time.
var o = {}; for (var i = 0; i < 100; i++) { … }
is different than
for (var i = 0; i < 100; i++) { var o = {}; … }
On Fri, May 13, 2016 at 4:19 PM, Herby Vojčík <herby at mailbox.sk> wrote:
Nicolas B. Pierron wrote:
I do not understand how having local variable should change any GC pattern unless you are creating them in the for-loop body.
If I understand correctly, the problem is in not reusing the objects. If you do
var x = new X()
in every call of the method, GC should take care of everynew X()
. OTOH, they are young object, and young space should be this sort-of pool like quick thing.
Yes, the nursery should handle such cases by removing the young & dead objects.
That's why I suggested that what is in way wanted here, is in fact pooling of X instances (and maybe not necessarily by instance of their client). Which can be inlined as well, as it is pretty easy:
var x = poolOfXs.pop() || new X();
to get an x, and
poolOfXs.push(x);
to put it back. This may actually save even more GC (as only as much instances are created as needed).
One problem I see with manually implementing a pool of objects, is that the JS engine has no clue that your are making an allocator on your own.
Scalar Replacement should be able to remove "most" of these allocations, as long as there is a single allocation site per variable (at the moment in SM). On the other hand, this can only work with GC allocations which are not escaped.
The problem with a pool allocator, is that the optimizer has no "easy" way to know that you are effectively making an allocator. Thus, optimization such as scalar replacement have no way to know that they can skip the allocation, nor the computation of the properties.
On May 13, 2016, at 12:23 PM, Herby Vojčík <herby at mailbox.sk> wrote:
It's different. Does allocation or GC eats the performance?
That’s a good question. It’d be hard to tease out that data for runs of the source, but I suspect it’s GC, because it’s a pause, not an overall slowdown. And over-all pause would me the allocations are bad; a pause (every couple seconds) means the GC is bad.
It’s not doubt that GC is the big bottle neck for real time applications, like games. That’s been known for years. It’s one of the reason asm.js operates like it does, to basically recreate a alloc/free memory model to cancel the GC (outside of things like DOM, etc.)
[>] Brian
On May 13, 2016, at 12:13 PM, /#!/JoePea <joe at trusktr.io> wrote:
Or "Cython in Javascript". I think your needs can be satisfied by C/C++ addons in Node.js or WebAssembly in browser
But we want to stay in JavaScript because we like the language. Plus, writing a framework in JavaScript has the obvious advantage that a beginner web programmer can just start using it easily (HTML/JAvaScript is like the gateway drug of programming nowadays so there's certain advantages to writing a library in JS as opposed to in C or compiling to WebAssembly).
I don’t think there being an alternate is any reason to not improve something. I’m realistic here, I know there are concerns which are 1,000,000x more important than me, games or other time sensitive applications, and backwards compatibility and language complications and sometimes philosophy. Just a few things here and there, though, not necessarily any of the ideas I’ve come up with, can solve or mitigate this problem.
I never thought I’d see modules or classes! Everything is on the right path. Just being able to deal with the GC (and maybe types if I’m lucky) is the only big hurdle to the kind of thing myself and others are doing.
[>] Brian
I think the original problem that caused this discussion with GC could be solved with just a weak map:
// Original
class thing {
doSomething(x, y) {
let a = x + y;
let b = ay;
let c = bx;
return (c);
}
}
// With a weak map
const wm = new WeakMap()
class thing {
doSomething(x, y) {
let data = wm.get(this)
if (data === undefined) {
wm.set(this, data = {
a: x + y,
b: ay,
c: bx,
})
}
return data.c;
}
}
Maybe now that realtime graphically-intensive applications in the web are becoming a thing, there needs to be some sort of standardization on GC so that devs can make meaningful decisions on GC (rather than guessing how to best optimize for different engines and often writing ugly code because of it)? It just seems necessary...
It much easier to solve then that, just make a,b,c globals in the class constructor (again, let's consider than objects, not primitives.) If you look at my stuff, you'll see it's pretty free from GC, but that's because I pull a lot of tricks, which make for messy and hard to understand code.
The point is to find a way to do this without convoluted stuff that ends up making brittle or spaghetti code -- and is subject to sudden changes if someday an engine decides to do GC in a different way, being clever could suddenly turn against you.
[>] Brian
my stuff, you'll see it's pretty free from GC, but that's because I pull a lot of tricks, which make for messy and hard to understand code.
Exactly, which is why standards around GC might help; to prevent the messy brittle code that developers making graphically intense apps will end up writing. I've no idea what/how standards on GC would be like, but it just seems like they're needed. Some standardized GC would work automatically like now, but would also let devs take control when needed (without "tricks").
If we ever get access to GC primitives, I would like to make sure that it
is minimally specified. An example of this is that in the JVM standard,
java.lang.System.gc()
's behavior is completely implementation-defined.
Matter of fact, IIRC the standard explicitly mentions that doing nothing is
conforming.
Another idea I had, something I do all the time but would be interesting to see in a syntactical sugar approach. Again, almost all this is because I (and many others) are writing real time, GC-sensitive applications (in my case a auto-generated first person shooter, every bitmap, every surface, etc) and trying to find ways to defeat or minimize the GC, and to get a discussion going about this kind of usage so more important people understand it’s a problem and maybe others will have better ideas.
Take this class:
class thing { constructor { } doSomething(x,y) { let a=x+y; let b=ay; let c=bx; return(c); } }
If I call doSomething a lot of times, I get a lot of objects to be tracked and GC’d. Yes, I can rewrite that code to obviously eliminate them, but pretend there is actually something interesting happening here that requires those variables. And engines can be smart enough to mark them and deal with them with the function ends (I don’t know if this is done.)
What I’m thinking is some kind of class based hint that would “super” hoist all local function variables. It would be sugar but it would transform it into:
class thing { constructor { let _doSomething_a=null; let _doSomething_b=null; let _doSomething_c=null; } doSomething(x,y) { this._doSomething_a=x+y; this._doSomething_b=ay; this._doSomething_c=bx; return(this._doSomething_c); } }
I actually do this now, just by hand.
Thoughts?
[>] Brian