Каково обоснование дизайна HandleScope? - программирование
Подтвердить что ты не робот

Каково обоснование дизайна HandleScope?

V8 требует объявления HandleScope для очистки любых локальных дескрипторов, созданных в пределах области видимости. Я понимаю, что HandleScope будет разыменовывать эти дескрипторы для сбора мусора, но меня интересует, почему каждый локальный класс не выполняет разыменование себя, как большинство внутренних помощников ref_ptr.

Моя мысль заключается в том, что HandleScope может сделать это более эффективно, сбросив большое количество дескрипторов одновременно, а не один за другим, как в классе класса ref_ptr.

4b9b3361

Ответ 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. Но это, вероятно, вопрос вкуса.

Ответ 2

Отказ от ответственности: это может быть не официальный ответ, а больше конъюнктура с моей стороны; но документация v8 вряд ли полезный по этой теме. Поэтому я могу ошибаться.

С моей точки зрения, в разработке различных приложений на основе v8. Его средство обрабатывает разницу между средой С++ и javaScript.

Представьте себе следующую последовательность, в которой указатель самопересечения может разорвать систему.

  • JavaScript вызывает функцию, запущенную на С++ v8: позволяет helloWorld()
  • Функция С++ создает дескриптор значения v8:: "hello world = x"
  • С++ возвращает значение виртуальной машине v8
  • Функция С++ выполняет обычную очистку ресурсов, включая разыменование дескрипторов
  • Другая функция/процесс С++, перезаписывает освобожденное пространство памяти
  • V8 читает дескриптор: и данные уже не те же "hell! @(#..."

И это просто поверхность сложной несогласованности между двумя; Поэтому для решения различных проблем подключения виртуальной машины Java (виртуальной машины) к интерфейсу С++, я считаю, что команда разработчиков решила упростить эту проблему с помощью следующего...

  • Все дескрипторы переменных должны быть сохранены в "ведрах", например, HandleScopes, чтобы создавать/скомпилировать/запускать/уничтожать их соответствующий код С++, если это необходимо.
  • Дополнительно все дескрипторы функций относятся к относятся только к статическим функциям С++ (я знаю, что это раздражает), что обеспечивает "существование", вызова функции независимо от конструкторов/деструкторов.

Подумайте об этом с точки зрения развития, в которой он отмечает очень сильное различие между командой разработки JavaScript VM и командой интеграции С++ (команда разработчиков Chrome?). Разрешить обеим сторонам работать, не мешая друг другу.

Наконец, это также может быть просто для упрощения эмуляции нескольких VM: поскольку v8 первоначально предназначался для google chrome. Следовательно, простое создание и уничтожение HandleScope всякий раз, когда мы открываем/закрываем вкладку, делает для гораздо более легкого управления GC, особенно в тех случаях, когда у вас много виртуальных машин (каждая вкладка в хроме).