¿Por qué es new slow?


El punto de referencia:

JsPerf

Los invariantes:

var f = function() { };

var g = function() { return this; }

Las pruebas:

A continuación en orden de velocidad esperada

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

Velocidad real:

  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 cambias f y g por funciones anónimas en línea. ¿Por qué es el new (prueba 4.) prueba más lento?

Actualización:

Qué causa específicamente que new sea más lento cuando f y g están en línea.

Estoy interesado en referencias a la especificación ES5 o referencias al código fuente JagerMonkey o V8. (Siéntase libre de vincular el código fuente JSC y Carakan también. Ah y el equipo de IE puede filtrar la fuente Chakra si quieren).

Si vincula cualquier fuente del motor JS, explique se.

Author: Raynos, 2011-06-22

5 answers

El problema es que puede inspeccionar el código fuente actual de varios motores, pero no le ayudará mucho. No intentes 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 tenga un caso de uso real en absoluto.

"Los programas deben estar escritos para que la gente los lea, y solo incidentalmente para que las máquinas los ejecuten."

Abelson & Sussman, SICP, prefacio a la primera edición

 5
Author: galambalazs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-06-24 21:02:19

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

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

  2. Cuando closure se utiliza como constructor por primera vez, V8 tiene que crear un nuevo mapa (también conocido como clase oculta) y asignarlo como mapa inicial para ese cierre. Véase 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 parcial rápido scavenge Coleccionista. Cuando el espacio del mapa se desborda V8 tiene que realizar un barrido de marcas GC relativamente caro.

Hay un par de otras cosas que suceden cuando usas closure 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 introduce tiempo de ejecución cada vez: después el cierre obtiene la construcción inicial del mapa se maneja en la ruta rápida del código generado. Manejar 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 carrozas 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 al desbordar el espacio del mapa (solo carrozas baratas);
  • #4 hace menos en el lado JS (sin función.prototipo.llama, no Hay Problema.crear, sin Objeto.búsqueda de prototipos, etc.) más en el lado de C++ (#3 también ingresa el tiempo de ejecución cada vez que Objeta.crear, pero hace muy poco).

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

 15
Author: Vyacheslav Egorov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-10-01 09:40:21

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 Objetos.create())
  3. t(exp3) : t(Creación de Objetos por Objetos.create ()) + t(Function Object Creation)
  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 mira : http://code.google.com/apis/v8/design.html .
    • Cuando se crea un nuevo objeto por Objeto.crear no es necesario crear ningún objeto de clase nuevo. Ya hay uno que se usa para literales de objetos y no hay necesidad de una nueva Clase.
 3
Author: salman.mirghasemi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-06-24 14:23:34

Bueno, esas dos llamadas no hacen exactamente lo mismo. Considere 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 causa que otherRock herede del prototipo. Esto tiene que explicar al menos parte de la lentitud añadida. Aunque en mis pruebas, la construcción new es casi 30 veces más lenta, incluso en este simple ejemplo.

 0
Author: benekastah,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-06-23 15:41:21
new f;
  1. tome la función local ' f ' (acceso por índice en marco local) - barato.
  2. ejecutar bytecode BC_NEW_OBJECT (o algo así) - barato.
  3. Ejecutar la función - barato aquí.

Ahora esto:

g.call(Object.create(Object.prototype));
  1. Encontrar global var Object - barato?
  2. Buscar propiedad prototype en Object-so-so
  3. Buscar propiedad create en Object-so-so
  4. Encontrar local var g; - barato
  5. Find property call - so-so
  6. Invocar create function-so-so
  7. Invoke call function-so-so

Y esto:{[18]]}

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.

 0
Author: c-smile,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-06-24 21:26:34