HandleScope 背后的设计原理是什么? [英] What is the design rationale behind HandleScope?

查看:30
本文介绍了HandleScope 背后的设计原理是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

V8 需要声明 HandleScope 以清除在作用域内创建的任何本地句柄.我知道 HandleScope 会为垃圾收集取消引用这些句柄,但我对为什么每个 Local 类不像大多数内部 ref_ptr 类型帮助程序那样自行取消引用感兴趣.

V8 requires a HandleScope to be declared in order to clean up any Local handles that were created within scope. I understand that HandleScope will dereference these handles for garbage collection, but I'm interested in why each Local class doesn't do the dereferencing themselves like most internal ref_ptr type helpers.

我的想法是 HandleScope 可以通过一次性转储大量句柄而不是像在 ref_ptr 类型的作用域类中那样一个一个转储来更有效地完成这项工作.

My thought is that HandleScope can do it more efficiently by dumping a large number of handles all at once rather than one by one as they would in a ref_ptr type scoped class.

推荐答案

这是我对文档的理解handles-inl.h 源代码.我也可能完全错了,因为我不是 V8 开发人员,而且文档很少.

Here is how I understand the documentation and the handles-inl.h source code. I, too, might be completely wrong since I'm not a V8 developer and documentation is scarce.

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

The garbage collector will, at times, move stuff from one memory location to another and, during one such sweep, also check which objects are still reachable and which are not. In contrast to reference-counting types like std::shared_ptr, this is able to detect and collect cyclic data structures. For all of this to work, V8 has to have a good idea about what objects are reachable.

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

On the other hand, objects are created and deleted quite a lot during the internals of some computation. You don't want too much overhead for each such operation. The way to achieve this is by creating a stack of handles. Each object listed in that stack is available from some handle in some C++ computation. In addition to this, there are persistent handles, which presumably take more work to set up and which can survive beyond C++ computations.

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

Having a stack of references requires that you use this in a stack-like way. There is no "invalid" mark in that stack. All the objects from bottom to top of the stack are valid object references. The way to ensure this is the LocalScope. It keeps things hierarchical. With reference counted pointers you can do something like this:

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,然后创建对象2,然后函数返回并销毁对象1,然后销毁对象2.这里的关键点是存在对象 1 无效但对象 2 仍然有效的时间点.这就是 LocalScope 旨在避免的.

Here the object 1 is created first, then the object 2 is created, then the function returns and object 1 is destroyed, then object 2 is destroyed. The key point here is that there is a point in time when object 1 is invalid but object 2 is still valid. That's what LocalScope aims to avoid.

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

Some other GC implementations examine the C stack and look for pointers they find there. This has a good chance of false positives, since stuff which is in fact data could be misinterpreted as a pointer. For reachability this might seem rather harmless, but when rewriting pointers since you're moving objects, this can be fatal. It has a number of other drawbacks, and relies a lot on how the low level implementation of the language actually works. V8 avoids that by keeping the handle stack separate from the function call stack, while at the same time ensuring that they are sufficiently aligned to guarantee the mentioned hierarchy requirements.

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

To offer yet another comparison: an object references by just one shared_ptr becomes collectible (and actually will be collected) once its C++ block scope ends. An object referenced by a v8::Handle will become collectible when leaving the nearest enclosing scope which did contain a HandleScope object. So programmers have more control over the granularity of stack operations. In a tight loop where performance is important, it might be useful to maintain just a single HandleScope for the whole computation, so that you won't have to access the handle stack data structure so often. On the other hand, doing so will keep all the objects around for the whole duration of the computation, which would be very bad indeed if this were a loop iterating over many values, since all of them would be kept around till the end. But the programmer has full control, and can arrange things in the most appropriate way.

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

Personally, I'd make sure to construct a HandleScope

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

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

In general I'd not create a HandleScope for every internal function if I can be sure that every other function calling this will already have set up a HandleScope. But that's probably a matter of taste.

这篇关于HandleScope 背后的设计原理是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆