Así es como entiendo the documentation y el código fuente handles-inl.h
. Yo también podría estar completamente equivocado ya que no soy un desarrollador de V8 y la documentación es escasa.
El recolector de basura, a veces, moverá cosas de una ubicación de memoria a otra y, durante un barrido de este tipo, también verificará qué objetos son todavía alcanzables y cuáles no. A diferencia de los tipos de recuento de referencias como std::shared_ptr
, este es capaz de detectar y recopilar estructuras de datos cíclicos. Para que todo esto funcione, V8 tiene que tener una buena idea sobre a qué objetos se puede acceder.
Por otro lado, los objetos se crean y eliminan bastante durante la parte interna de algunos cálculos. No quiere demasiada sobrecarga para cada operación. La forma de lograr esto es creando una pila de controladores. Cada objeto enumerado en esa pila está disponible desde algún mango en algún cálculo de C++. Además de esto, existen identificadores persistentes, que presumiblemente requieren más trabajo de configuración y que pueden sobrevivir más allá de los cálculos C++.
Tener una pila de referencias requiere que la use de forma similar a una pila. No hay una marca "inválida" en esa pila. Todos los objetos de abajo a arriba de la pila son referencias de objeto válidas. La forma de garantizar esto es el LocalScope
. Mantiene las cosas jerárquicas. Con referencia contó punteros que puede hacer algo como esto:
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();
}
Aquí el objeto 1 se crea por primera vez, continuación el objeto 2 se crea, a continuación, devuelve la función y objeto 1 se destruye, entonces el objeto 2 se destruye . El punto clave aquí es que hay un punto en el tiempo cuando el objeto 1 no es válido, pero el objeto 2 todavía es válido. Eso es lo que LocalScope
pretende evitar.
Algunas otras implementaciones de GC examinan la pila C y buscan punteros que encuentran allí. Esto tiene una buena posibilidad de falsos positivos, ya que las cosas que de hecho son datos podrían malinterpretarse como un puntero. Para la accesibilidad, esto puede parecer bastante inofensivo, pero cuando reescribes los punteros ya que estás moviendo objetos, esto puede ser fatal. Tiene una serie de otros inconvenientes, y depende mucho de cómo funciona la implementación de bajo nivel del lenguaje. V8 evita eso al mantener la pila de asas separada de la pila de llamadas de función, mientras que al mismo tiempo garantiza que estén lo suficientemente alineadas para garantizar los requisitos de jerarquía mencionados.
Para ofrecer una comparación más: un objeto de referencias por una sola shared_ptr
se vuelve coleccionable (y en realidad se recogerá) una vez que finaliza su ámbito de bloque de C++. Un objeto referenciado por v8::Handle
será coleccionable cuando salga del alcance circundante más cercano que contenía un objeto HandleScope
. Entonces los programadores tienen más control sobre la granularidad de las operaciones de pila.En un ciclo cerrado donde el rendimiento es importante, puede ser útil mantener solo un HandleScope
para todo el cálculo, de modo que no tenga que acceder a la estructura de datos de la pila de control tan a menudo. Por otro lado, al hacerlo mantendrá todos los objetos alrededor durante toda la duración del cálculo, lo que sería muy malo si se tratara de un bucle que iterara sobre muchos valores, ya que todos se mantendrían hasta el final. Pero el programador tiene control total y puede organizar las cosas de la manera más adecuada.
En lo personal, me aseguraría de construir un HandleScope
- Al comienzo de cada función que se podría llamar desde fuera de su código. Esto asegura que su código se limpiará después de sí mismo.
- En el cuerpo de cada bucle que podría ver más de tres iteraciones, para que solo conserve las variables de la iteración actual.
- Alrededor de cada bloque de código seguido de alguna invocación de devolución de llamada, ya que esto garantiza que sus cosas se puedan limpiar si la devolución de llamada requiere más memoria.
- Siempre que sienta que algo puede producir cantidades considerables de datos intermedios que deberían limpiarse (o al menos volverse coleccionables) tan pronto como sea posible.
En general no me gustaría crear un HandleScope
para cada función interna si puedo estar seguro de que todas las demás funciones llamar a esto ya se habrá establecido un HandleScope
. Pero eso es probablemente una cuestión de gusto.
¿No está funcionando cada pestaña en cromo en un proceso separado? – Pete
@Pete HandleScopes se puede (opcionalmente) ejecutar en sus respectivos hilos (proceso aka). Además, dado que cada uno de ellos tiene sus propios espacios de variables, no es necesario garantizar que sus variables sean "thread-safe" entre sí – PicoCreator