Ответ 1
Вот как я понимаю документацию и handles-inl.h
исходный код. Я тоже могу быть совершенно неверным, так как я не разработчик V8, и документации мало.
Сборщик мусора время от времени перемещает вещи из одного места памяти в другое и, во время одной такой развертки, также проверяет, какие объекты еще доступны, а какие нет. В отличие от типов подсчета ссылок, таких как std::shared_ptr
, он способен обнаруживать и собирать циклические структуры данных. Чтобы все это работало, V8 должен иметь хорошее представление о том, какие объекты доступны.
С другой стороны, объекты создаются и удаляются довольно много во время внутренних вычислений. Вы не хотите слишком много накладных расходов для каждой такой операции. Способ достижения этого - создать стек ручек. Каждый объект, указанный в этом стеке, доступен из некоторого дескриптора в некоторых вычислениях на С++. В дополнение к этому существуют постоянные дескрипторы, которые, по-видимому, требуют больше работы для настройки и которые могут выжить за пределами вычислений на С++.
Наличие стека ссылок требует, чтобы вы использовали это в виде стека. В этом стеке нет метки "недопустимый". Все объекты снизу до вершины стека являются действительными ссылками на объекты. Способ обеспечить это 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, затем создается объект 2, затем функция возвращается и объект 1 уничтожается, затем объект 2 уничтожается. Ключевым моментом здесь является то, что есть момент времени, когда объект 1 недействителен, но объект 2 все еще действителен. То, к чему стремится LocalScope
.
Некоторые другие реализации GC исследуют стек C и ищут указатели, которые они там находят. Это имеет хорошие шансы на ложные срабатывания, поскольку материал, который фактически является данными, может быть неверно истолкован как указатель. Для достижимости это может показаться довольно безобидным, но при переписывании указателей с момента перемещения объектов это может быть фатальным. Он имеет ряд других недостатков и многое полагается на то, как работает на самом низком уровне язык. V8 избегает этого, сохраняя стеки дескриптора отдельно от стека вызовов функций, и в то же время гарантируя, что они достаточно выровнены, чтобы гарантировать указанные требования иерархии.
Предлагать еще одно сравнение: ссылки на объекты только одним shared_ptr
становятся коллекционируемыми (и фактически будут собраны), когда заканчивается область содержимого С++. Объект, на который ссылается v8::Handle
, становится собираемым при выходе из ближайшей охватывающей области, содержащей объект HandleScope
. Таким образом, программисты имеют больший контроль над детализацией операций стека. В узком цикле, где важна производительность, может быть полезно поддерживать только один HandleScope
для всего вычисления, так что вам не придется часто обращаться к структуре данных стека дескрипторов. С другой стороны, при этом все объекты будут храниться на протяжении всего времени вычисления, что было бы очень плохо, если бы это был цикл, повторяющийся по многим значениям, поскольку все они будут поддерживаться до конца. Но программист имеет полный контроль и может организовать вещи наиболее подходящим образом.
Лично я обязательно построю HandleScope
- В начале каждой функции, которая может быть вызвана извне вашего кода. Это гарантирует, что ваш код будет очищен после себя.
- В теле каждого цикла, который может видеть более трех или так итераций, чтобы вы сохраняли переменные только из текущей итерации.
- Вокруг каждого блока кода, за которым следует вызов обратного вызова, поскольку это гарантирует, что ваши вещи могут быть очищены, если обратный вызов требует больше памяти.
- Всякий раз, когда я чувствую, что что-то может произвести значительное количество промежуточных данных, которые должны быть очищены (или, по крайней мере, стать собираемыми) как можно скорее.
В общем, я бы не создал HandleScope
для каждой внутренней функции, если я могу быть уверен, что каждая другая функция, вызывающая это, уже установила HandleScope
. Но это, вероятно, вопрос вкуса.