从任意JavaScript对象单形检索一些元信息(v8)?

从任意JavaScript对象单形检索一些元信息(v8)?,javascript,performance,polymorphism,v8,monomorphism,Javascript,Performance,Polymorphism,V8,Monomorphism,专家的问题 最近,我发现了。这是因为多态性仅在4个对象“形状”中得到了很好的优化,之后性能会显著下降。类和对象继承被忽略。这是一个非常令人沮丧的发现,因为有了这些限制,结构良好的代码将无法执行。这似乎是一个巨大的挑战,在不久的将来,任何事情都不太可能改变 因此,我愿意在用户空间中实现更好的多态性: 这不是一个新问题,它已经在几乎所有其他语言中得到了解决,包括vtables、代码专门化等。任何关于如何使用JavaScript的建议都是非常受欢迎的 我目前试图解决的问题是从任意对象中以单形态检索一些

专家的问题

最近,我发现了。这是因为多态性仅在4个对象“形状”中得到了很好的优化,之后性能会显著下降。类和对象继承被忽略。这是一个非常令人沮丧的发现,因为有了这些限制,结构良好的代码将无法执行。这似乎是一个巨大的挑战,在不久的将来,任何事情都不太可能改变

因此,我愿意在用户空间中实现更好的多态性:

这不是一个新问题,它已经在几乎所有其他语言中得到了解决,包括vtables、代码专门化等。任何关于如何使用JavaScript的建议都是非常受欢迎的

我目前试图解决的问题是从任意对象中以单形态检索一些元信息。此元信息将是一个vtable模拟,包含用于方法分派的信息。这是为了避免出现包含此信息的额外框

JS中的元信息(一些被许多其他对象共享的对象)自然映射到prototype,因此第一步是获取对象的prototype。这可以通过
Object.getPrototypeOf()以单形态完成。但是,不管你尝试什么,你都会失去单态性

例如,在以下代码中,对对象构造函数的访问将是megamorphic:

类HasVTable{}
HasVTable.prototype.vtable={}
类Class1扩展了HasVTable{}
类2扩展了HasVTable{}
类Class3扩展了HasVTable{}
类Class4扩展了HasVTable{}
类Class5扩展了HasVTable{}
功能访问(obj){
log(Object.getPrototypeOf(obj.constructor.name);
}
%优化函数onnextcall(访问);
访问(新类别1);
访问(新类别2);
访问(新类别3);
访问(新类别4);
访问(新类别5);
所以问题是,在失去单态性的情况下,如何在原型中存储一些信息,然后检索它?也许,一些“著名”的符号可以在这里有所帮助?还是有其他解决办法

谢谢大家!


例如,我刚刚尝试使用迭代器符号,但运气不好-在迭代器位置访问
proto
仍然是一个巨态:

类HasVTable{}
类Class1扩展了HasVTable{
*[符号.迭代器](){
产量“1类”
}
}
类2扩展了HasVTable{
*[符号.迭代器](){
收益率'Class2'
}
}
类3扩展了HasVTable{
*[符号.迭代器](){
收益率“3类”
}
}
类Class4扩展了HasVTable{
*[符号.迭代器](){
收益率'Class4'
}
}
类5扩展了HasVTable{
*[符号.迭代器](){
收益率'Class5'
}
}
功能访问(obj){
const proto=Object.getPrototypeOf(obj)
让我们
对于(原版的res)break
console.log(res)
}
%优化函数onnextcall(访问);
访问(新类别1);
访问(新类别2);
访问(新类别3);
访问(新类别4);
访问(新类别5);

更新2020/10/21

我使用了一个优秀的工具来跟踪代码去优化:

npx deoptigate——允许本地语法-r esm src_js/draft3.js

多态性仅对4个对象“形状”进行了很好的优化,之后性能会显著下降

不完全是。“多态”方法对于数量较少的形状是快速的,但不能很好地扩展。“巨形”方法更适用于在同一地点看到的大量形状。阈值应该是3、4(目前的情况)、5、6还是其他,这在很大程度上取决于您正在查看的特定基准;我见过8的限制更好,但在其他情况下,4的限制更好。更高的值也会消耗更多的内存,这对于小型独立测试来说并不重要,但对于大型应用程序来说却是一个重要的考虑因素

如何在原型中存储一些信息,然后在失去单态性的情况下检索这些信息

如果您想要单态访问,请坚持使用一个对象形状。这就是它的全部内容;你只需要找出如何将这一原则应用于你的情况。您可能最终会使用对象对来表示每个高级对象:一个部分表示单态的、与类型无关的共享部分,一个部分(从第一个部分链接)表示特定于每个类的内容。大概是:

// Generic part of the object pair:
class HasVTable {
  constructor(vtable, name, object_data) {
    this.vtable = vtable;
    // This is just an example of the simplest possible way to implement
    // properties that are guaranteed to be present in all objects.
    // Totally optional; `vtable` and `object_data` are the ideas that matter.
    this.name = name;
    this.object_data = object_data;
  }

  // Note that `doStuff` might not exist on all "subclasses". Just don't
  // attempt to call it where it would be invalid. Or add a check, if you
  // want to pay a (small) performance cost for better robustness.
  doStuff(...) {
    // The fact that "doStuff" is the first method is hardcoded here and
    // elsewhere. That hardcoding is what makes vtables fast.
    return this.vtable[0](this.object_data, ...);

    // Alternatively, you could get fancy and use:
    return this.vtable[0].call(this.object_data, ...);
    // And then `Class1_doStuff` below could use `this` like normal.
    // I'm not sure which is faster, you'd have to measure.
  }
  getName() {
    // Fields that are guaranteed to be present on all objects can be
    // stored and retrieved directly:
    return this.name;
  }
}

// The following is the class-specific part of the object pair.
// I'm giving only Class1 here for brevity.
// This does *not* derive from anything, and is never constructed directly.
class Class1 {
  constructor(...) {
    this.class1_specific_field = "hello world";
  }
}

function Class1_doStuff(this_class1) {
  // Use `this_class1` instead of `this` in here.
  console.log(this_class1.class1_specific_field);
}

// Note that only one instance of this exists, it's shared by all class1 objects.
let Class1_vtable = [
  Class1_doStuff,  // doStuff is hardcoded to be the first method.
]

// Replaces `new Class1(...)` in traditional class-based code.
// Could also make this a static function: `Class1.New = function(...) {...}`.
function NewClass1(...) {
  return new HasVTable(Class1_vtable, "Class1", new Class1(...));
}
至少对于相当简单的对象层次结构来说,这种技术可以显著提高速度。因此,尝试这种方法可能会给你带来有益的结果。祝你好运

对于如何实现多重继承(正如github项目描述中所说的那样)或混合,我没有任何建议;快速多重继承是一个难题,特别是在动态语言中

说到动态语言:如果您假设代码可能会随机改变原型链,那么确保所有东西都不会崩溃也是非常困难的(我不知道您如何实现这一点)。顺便说一句,这就是JavaScript引擎不能在幕后执行这种转换的原因之一:它们必须100%符合规范,并且必须在各种情况下都能正常工作。当您构建自己的系统时,您可以选择施加您认为可以接受的某些限制(例如:禁止修改原型),或者您可以选择针对您认为重要的特定模式进行优化

第一步是获得对象的原型。[…]这是为了避免额外的盒子

原型也是一个“额外的盒子”,所以这里没有理由更喜欢原型(事实上,正如你已经注意到的,原型并不能实现你的目标)

%OptimizeFunctionOnNextCall(访问)

代码中的这一行完成了