Question for v8 experts.
Recently, I've discovered the situation with polymorphism in v8. It is that polymorphism is well-optimized only up to the 4 object "shapes", after which the performance degrades significantly. Classes and object inheritance is ignored. This was a very frustrating discovery, because, with such limitations, well-structured code won't be performant. It seems it is a well-known problem since 2017 and it is very unlikely anything will change in near future.
So, I'm willing to implement better polymorphism in user-space: https://github.com/canonic-epicure/monopoly
This is not a novel problem, it is already solved in pretty much any other language out there, with vtables, code specialization etc. Any suggestions how to do it in JavaScript are very welcome.
The problem I'm currently trying to solve is to retrieve some meta information from the arbitrary object monomorphically. This meta information will be a vtable analog, containing the information for methods dispatch. This is to avoid an extra box, containing this information.
The meta information (some object that is shared by many other objects) in JS naturally maps to prototype, so the 1st step is to get the prototype of the object. This can be done monomorphically with Object.getPrototypeOf()
. But then, it seems whatever you try, you loose the monomorphicity.
For example, in the following code, access to the constructor of the object will be megamorphic:
class HasVTable {}
HasVTable.prototype.vtable = {}
class Class1 extends HasVTable {}
class Class2 extends HasVTable {}
class Class3 extends HasVTable {}
class Class4 extends HasVTable {}
class Class5 extends HasVTable {}
function access(obj) {
console.log(Object.getPrototypeOf(obj).constructor.name);
}
%OptimizeFunctionOnNextCall(access);
access(new Class1);
access(new Class2);
access(new Class3);
access(new Class4);
access(new Class5);
So question is, how to store some information in the prototype and then retrieve it, w/o loosing monomorphicity? Perhaps, some "well-known" symbols can help here? Or is there some other solution?
Thank you!
For example, I've just tried using the iterator symbol with no luck - access to proto
in iterator position is still megamorphic:
class HasVTable {}
class Class1 extends HasVTable {
*[Symbol.iterator] () {
yield 'Class1'
}
}
class Class2 extends HasVTable {
*[Symbol.iterator] () {
yield 'Class2'
}
}
class Class3 extends HasVTable {
*[Symbol.iterator] () {
yield 'Class3'
}
}
class Class4 extends HasVTable {
*[Symbol.iterator] () {
yield 'Class4'
}
}
class Class5 extends HasVTable {
*[Symbol.iterator] () {
yield 'Class5'
}
}
function access(obj) {
const proto = Object.getPrototypeOf(obj)
let res
for (res of proto) break
console.log(res)
}
%OptimizeFunctionOnNextCall(access);
access(new Class1);
access(new Class2);
access(new Class3);
access(new Class4);
access(new Class5);
UPDATE 2020/10/21
I use an excellent deoptigate
tool to track the code de-optimizations:
npx deoptigate --allow-natives-syntax -r esm src_js/draft3.js