2012-03-01 18 views
16

V8 requiere que se declare un HandleScope para limpiar los identificadores locales que se crearon dentro del alcance. Entiendo que HandleScope desreferenciará estos identificadores para la recolección de elementos no utilizados, pero me interesa saber por qué cada clase Local no realiza la eliminación de referencias como la mayoría de los tipos internos de ayuda ref_ptr.¿Cuál es el razonamiento de diseño detrás de HandleScope?

Mi idea es que HandleScope puede hacerlo de manera más eficiente tirando una gran cantidad de identificadores a la vez en vez de uno por uno, como lo harían en una clase de ámbito de tipo ref_ptr.

Respuesta

3

Descargo de responsabilidad: Puede que esta no sea una respuesta oficial, más una coyuntura de mi parte; pero la documentación del v8 es apenas útil en este tema. Entonces puedo estar probado que estoy equivocado.

Según tengo entendido, en el desarrollo de varias aplicaciones basadas en v8. Es un medio de que maneja la diferencia entre el entorno C++ y javaScript.

Imagine la secuencia siguiente, que un puntero de autoreferenciación puede dañar el sistema.

  1. JavaScript llama a un C++ envuelta función v8: Digamos que helloWorld()
  2. función
  3. C++ crea un V8 :: mango de valor "hola mundo = x"
  4. C++ devuelve el valor de la máquina virtual v8
  5. función
  6. C++ hace su habitual limpieza de los recursos, incluyendo desreferencia de asas
  7. Otra función de C++/proceso, sobrescribe el espacio de memoria liberada
  8. V8 lee el mango, y los datos ya no es el mismo

Y eso es sólo la superficie de la inconsistencia complicada entre los dos; "infierno @ (# ...!" Por lo tanto, para hacer frente a los diversos problemas de la conexión de la JavaScript VM (Virtual Machine) a la código C++ interfaz, creo que el equipo de desarrollo, decidido simplificar el problema a través de la siguiente ...

  • todos variable maneja, se van a almacenar en "cubos" aka HandleScopes, para ser incorporado /compilado/run/destruida por su respectiva código C++, cuando sea necesario.
  • Además todas las funciones se encarga de, son a sólo se refieren a funciones estáticas C++ (sé que esto es irritante), lo que garantiza la "existencia" de la llamada a la función, independientemente de constructores/destructor.

Piense en ello desde el punto de vista del desarrollo, en el que se marca un muy fuerte distinción entre el equipo de desarrollo de JavaScript VM, y el equipo de C++ de integración (Chrome equipo de desarrollo?). Permitiendo que ambos lados trabajen sin interferir entre ellos.

Por último, también podría ser una simple simplicidad, para emular varias máquinas virtuales: como v8 fue originalmente para google chrome. Por lo tanto, una simple creación y destrucción de HandleScope cada vez que abrimos/cerramos una pestaña, hace que la gestión de GC sea mucho más fácil, especialmente en los casos en los que tiene muchas VM ejecutándose (cada pestaña en cromo).

+0

¿No está funcionando cada pestaña en cromo en un proceso separado? – Pete

+0

@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

5

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.

+0

Esta es una respuesta incomparable. Gracias por este análisis – joshperry

Cuestiones relacionadas