HandleScope 背后的设计原理是什么?

2024-01-05

V8 需要声明一个 HandleScope,以便清理在范围内创建的任何本地句柄。我知道 HandleScope 将取消引用这些句柄以进行垃圾收集,但我感兴趣的是为什么每个 Local 类不像大多数内部 ref_ptr 类型助手那样自行取消引用。

我的想法是,HandleScope 可以通过一次转储大量句柄而不是像在 ref_ptr 类型作用域类中那样一个接一个地转储来更有效地完成此操作。


这是我的理解文档 https://github.com/v8/v8/wiki/Embedder%27s-Guide#handles-and-garbage-collectionhandles-inl.h https://github.com/v8/v8/blob/dab61bc310921d70e4d02275f7a09fa352466370/src/handles-inl.h源代码。我也可能是完全错误的,因为我不是 V8 开发人员,而且文档也很少。

垃圾收集器有时会将内容从一个内存位置移动到另一个内存位置,并且在一次此类扫描期间,还会检查哪些对象仍然可访问,哪些对象不可访问。与引用计数类型相比,例如std::shared_ptr,这能够检测和收集循环数据结构。为了使所有这一切发挥作用,V8 必须清楚哪些对象是可访问的。

另一方面,在某些计算的内部,对象被大量创建和删除。您不希望每个此类操作有太多开销。实现此目的的方法是创建句柄堆栈。该堆栈中列出的每个对象都可以从某些 C++ 计算中的某个句柄获得。除此之外,还有持久句柄,这可能需要更多的工作来设置,并且可以在 C++ 计算之外继续存在。

拥有一个引用堆栈需要您以类似堆栈的方式使用它。该堆栈中没有“无效”标记。堆栈中从底部到顶部的所有对象都是有效的对象引用。确保这一点的方法是LocalScope。它使事物保持分层。使用引用计数指针,您可以执行以下操作:

shared_ptr<Object>* f() {
    shared_ptr<Object> a(new Object(1));
    shared_ptr<Object>* b = new shared_ptr<Object>(new Object(2));
    return b;
}
void g() {
    shared_ptr<Object> c = *f();
}

这里首先创建了对象 1,then创建对象 2,然后函数返回,对象 1 被销毁,然后对象 2 被销毁。这里的关键点是,存在一个时间点,对象 1 无效,但对象 2 仍然有效。就是这样LocalScope旨在避免。

其他一些 GC 实现会检查 C 堆栈并查找在那里找到的指针。这很有可能出现误报,因为实际上是数据的东西可能会被误解为指针。对于可访问性来说,这似乎相当无害,但是当由于移动对象而重写指针时,这可能是致命的。它还有许多其他缺点,并且很大程度上依赖于该语言的低级实现的实际工作方式。 V8 通过将句柄堆栈与函数调用堆栈分开来避免这种情况,同时确保它们充分对齐以保证提到的层次结构要求。

提供另一种比较:一个对象仅被一个对象引用shared_ptr一旦其 C++ 块作用域结束,就变得可收集(并且实际上将被收集)。由 a 引用的对象v8::Handle当离开最近包含的封闭范围时将变得可收集HandleScope目的。因此程序员可以更好地控制堆栈操作的粒度。在性能很重要的紧密循环中,仅维护一个单一的可能会很有用HandleScope对于整个计算,这样您就不必经常访问句柄堆栈数据结构。另一方面,这样做将在整个计算期间保留所有对象,如果这是一个迭代许多值的循环,这确实会非常糟糕,因为所有对象都会保留到最后。但程序员拥有完全的控制权,可以以最合适的方式安排事情。

就我个人而言,我会确保构建一个HandleScope

  • 在可能从代码外部调用的每个函数的开头。这可以确保您的代码会自行清理。
  • 在每个循环的主体中,可能会看到超过三个左右的迭代,因此您只保留当前迭代中的变量。
  • 围绕每个代码块,后面跟着一些回调调用,因为这可以确保如果回调需要更多内存,您的东西可以得到清理。
  • 每当我觉得某些东西可能会产生大量中间数据时,这些数据应该尽快清理(或至少变得可收集)。

一般来说我不会创建HandleScope对于每个内部函数,如果我可以确定调用此函数的每个其他函数都已经设置了一个HandleScope。但这可能是一个品味问题。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

HandleScope 背后的设计原理是什么? 的相关文章

随机推荐