tag style page div attribute javascript v8 ecmascript-5 javascript-engine

javascript - style - ¿Por qué es nuevo lento?



title tag html5 (5)

El punto de referencia:

JsPerf

Los invariantes:

var f = function() { }; var g = function() { return this; }

Los exámenes:

Abajo en orden de velocidad esperada

  • new f;
  • g.call(Object.create(Object.prototype));
  • new (function() { })
  • (function() { return this; }).call(Object.create(Object.prototype));

Velocidad actual:

  1. new f;
  2. g.call(Object.create(Object.prototype));
  3. (function() { return this; }).call(Object.create(Object.prototype));
  4. new (function() { })

La pregunta:

  1. Cuando intercambias g para funciones anónimas en línea. ¿Por qué la new prueba (prueba 4.) es más lenta?

Actualizar:

Lo que específicamente hace que el new sea ​​más lento cuando f y g están en línea.

Me interesan las referencias a la especificación ES5 o las referencias al código fuente JagerMonkey o V8. (Siéntase libre de vincular el código fuente de JSC y Carakan también. Ah, y el equipo de IE puede filtrar la fuente de Chakra si lo desean).

Si vincula cualquier fuente de motor JS, por favor explíquelo.


Bueno, esas dos llamadas no hacen exactamente lo mismo. Considera este caso:

var Thing = function () { this.hasMass = true; }; Thing.prototype = { holy: ''object'', batman: ''!'' }; Thing.prototype.constructor = Thing; var Rock = function () { this.hard = ''very''; }; Rock.prototype = new Thing(); Rock.constructor = Rock; var newRock = new Rock(); var otherRock = Object.create(Object.prototype); Rock.call(otherRock); newRock.hard // => ''very'' otherRock.hard // => ''very'' newRock.hasMass // => true otherRock.hasMass // => undefined newRock.holy // => ''object'' otherRock.holy // => undefined newRock instanceof Thing // => true otherRock instanceof Thing // => false

Así que podemos ver que llamar a Rock.call(otherRock) no hace que otherRock herede del prototipo. Esto debe explicar al menos algo de la lentitud añadida. Aunque en mis pruebas, la new construcción es casi 30 veces más lenta, incluso en este simple ejemplo.


El problema es que puedes inspeccionar el código fuente actual de varios motores, pero no te ayudará mucho. No trates de ser más astuto que el compilador. Intentarán optimizar para el uso más común de todos modos. No creo que (function() { return this; }).call(Object.create(Object.prototype)) llamado 1,000 veces tiene un caso de uso real.

"Los programas deben escribirse para que la gente los lea, y solo de forma incidental para que las máquinas los ejecuten".

Abelson & Sussman, SICP, prefacio de la primera edición.


La principal diferencia entre el # 4 y todos los demás casos es que la primera vez que utiliza un cierre como constructor siempre es bastante costoso.

  1. Siempre se maneja en tiempo de ejecución V8 (no en el código generado) y la transición entre el código JS compilado y el tiempo de ejecución C ++ es bastante costosa. Las asignaciones subsiguientes generalmente se manejan en el código generado. Puede echar un vistazo a Generate_JSConstructStubHelper en builtins-ia32.cc y darse cuenta de que se trata de Runtime_NewObject cuando el cierre no tiene un mapa inicial. (consulte http://code.google.com/p/v8/source/browse/trunk/src/ia32/builtins-ia32.cc#138 )

  2. Cuando el cierre se utiliza como constructor por primera vez, V8 tiene que crear un nuevo mapa (también conocido como clase oculta) y asignarlo como un mapa inicial para ese cierre. Consulte http://code.google.com/p/v8/source/browse/trunk/src/heap.cc#3266 . Lo importante aquí es que los mapas se asignan en un espacio de memoria separado. Este espacio no puede ser limpiado por un recolector rápido de eliminación parcial. Cuando el espacio del mapa se desborda, V8 tiene que realizar un GC de marcaje completo relativamente caro.

Hay un par de otras cosas que suceden cuando se usa el cierre como constructor por primera vez, pero 1 y 2 son los principales contribuyentes a la lentitud del caso de prueba # 4.

Si comparamos las expresiones # 1 y # 4 entonces las diferencias son:

  • 1 no asigna un nuevo cierre cada vez;

  • 1 no ingresa el tiempo de ejecución cada vez: una vez que se cierra, la construcción del mapa inicial se maneja en la ruta rápida del código generado. El manejo de toda la construcción en el código generado es mucho más rápido que ir y venir entre el tiempo de ejecución y el código generado;

  • 1 no asigna un nuevo mapa inicial para cada nuevo cierre cada vez;

  • 1 no causa un barrido de marcas al desbordar el espacio del mapa (solo recuperaciones baratas).

Si comparamos # 3 y # 4 entonces las diferencias son:

  • 3 no asigna un nuevo mapa inicial para cada nuevo cierre cada vez;

  • 3 no causa un barrido de marcas por desbordamiento del espacio del mapa (solo recuperaciones baratas);

  • 4 hace menos en el lado JS (sin Function.prototype.call, no Object.create, no Object.prototype lookup, etc.) más en el lado C ++ (# 3 también ingresa en tiempo de ejecución cada vez que haces Object.create pero hace muy poco allí).

La línea de fondo aquí es que la primera vez que usa el cierre como constructor es costoso en comparación con las posteriores llamadas de construcción del mismo cierre porque V8 tiene que configurar algunas tuberías. Si descartamos inmediatamente el cierre, básicamente descartamos todo el trabajo que V8 ha hecho para acelerar las siguientes llamadas de constructor.


Supongo que las siguientes expansiones explican lo que está pasando en V8:

  1. t (exp1): t (creación de objetos)
  2. t (exp2): t (Creación de objetos por Object.create ())
  3. t (exp3): t (Creación de Objetos por Object.create ()) + t (Creación de Objetos de Función)
  4. t (exp4): t (Creación de objetos) + t (Creación de objetos de función) + t (Creación de objetos de clase) [En Chrome]

    • Para las Clases ocultas en Chrome, consulta: http://code.google.com/apis/v8/design.html .
    • Cuando se crea un nuevo objeto por Object.create, no se debe crear un nuevo objeto de clase. Ya existe uno que se usa para los literales de objetos y no se necesita una nueva clase.

new f;

  1. tomar la función local ''f'' (acceso por índice en el marco local) - barato.
  2. ejecute el bytecode BC_NEW_OBJECT (o algo así) - barato.
  3. Ejecutar la función - barato aquí.

Ahora esto:

g.call(Object.create(Object.prototype));

  1. Encuentra el Object global var - ¿es barato?
  2. Encontrar prototype propiedad en Objeto - regular
  3. Encontrar propiedad create in Object - regular
  4. Encuentra var g local; - barato
  5. Encontrar call propiedad - tan-tan
  6. Invocar la función de create - regular
  7. Función de call - regular

Y esto:

new (function() { })

  1. crear un nuevo objeto de función (esa función anónima) - relativamente caro.
  2. ejecutar bytecode BC_NEW_OBJECT - barato
  3. Ejecutar la función - barato aquí.

Como ves el caso # 1 es el que menos consume.