¿Por qué las promesas de ES6 nativas son más lentas y requieren más memoria que bluebird?

195

En este punto de referencia , el conjunto tarda 4 veces más en completarse con las promesas de ES6 en comparación con las promesas de Bluebird, y utiliza 3,6 veces más memoria.

¿Cómo puede una biblioteca de JavaScript ser mucho más rápida y ligera que la implementación nativa de v8 escrita en C? Las promesas de Bluebird tienen exactamente la misma API que las promesas de ES6 nativas (además de un montón de métodos de utilidad adicionales).

¿La implementación nativa está mal escrita o hay algún otro aspecto que me falta?

callum
fuente
Tenga en cuenta que las implementaciones de JavaScript modernas están muy optimizadas e incluso pueden ejecutarse de forma nativa utilizando JIT .
1
Según This Benchmark , BlueBirdJS es en realidad más lento que Native Promises. Pero, PromiseMeSpeedJS en realidad los supera a ambos. Una de las muchas cosas que PromiseMeSpeedJS demuestra a través de esto es que uno de los principales culpables del rendimiento de las promesas es el uso excesivo abusivo del newoperador porque PromiseMeSpeedJS no lo utiliza new.
Jack Giffin
1
@JackGiffin Chrome 67: PromiseMeSpeedJS es un 46% más lento y Bluebird es un 61% más lento.
FINDarkside

Respuestas:

272

Bluebird autor aquí.

V8 promete que la implementación está escrita en JavaScript, no C. Todo JavaScript (incluido el propio V8) se compila en código nativo. Además, el JavaScript escrito por el usuario está optimizado, si es posible (y vale la pena), antes de compilarlo en código nativo. La implementación de las promesas es algo que no se beneficiaría mucho o no se escribiría en C, de hecho, solo lo haría más lento porque todo lo que está haciendo es manipular los objetos JavaScript y la comunicación.

La implementación de V8 simplemente no está tan optimizada como bluebird, por ejemplo, asigna matrices para controladores de promesas . Esto toma mucha memoria cuando cada promesa también tiene que asignar un par de matrices (el punto de referencia crea 80k promesas generales, por lo que se asignan 160k matrices no utilizadas). En realidad, el 99,99% de los casos de uso nunca ramifican una promesa más de una vez, por lo que la optimización para este caso común aumenta enormemente el uso de la memoria.

Incluso si V8 implementara las mismas optimizaciones que bluebird, todavía se vería obstaculizado por la especificación. El punto de referencia tiene que usar new Promise(un antipatrón en bluebird) ya que no hay otra forma de crear una promesa de raíz en ES6. new Promisees una forma extremadamente lenta de crear una promesa, primero la función ejecutora asigna un cierre, en segundo lugar se pasan 2 cierres separados como argumentos. Son 3 cierres asignados por promesa, pero un cierre ya es un objeto más costoso que una promesa optimizada.

Bluebird puede usarlo, lo promisifyque permite muchas optimizaciones y es una forma mucho más conveniente de consumir API de devolución de llamada y permite la conversión de módulos completos en módulos basados ​​en promesas en una línea ( promisifyAll(require('redis'));).

Esailija
fuente
10
"aún se ve obstaculizado por la especificación" - No estoy seguro de lo que eso significa. ¿Está diciendo que ES6 está siguiendo una especificación que es inherentemente lenta, y si ese es el caso, eso significa que bluebird no está siguiendo la misma especificación (y si ese es el caso, ¿está siguiendo una diferente y cuál)? ¿Y hay alguna razón por la cual ES6 no podría tener una mejor manera de crear una Promesa raíz además de new Promisemejorar la creación de instancias para que sea menos costosa (como no crear 3 cierres por instancia)?
Anthony
12
Eso no suena nada bien (para JS). Realmente no quiero usar una biblioteca Promise cuando hay una implementación interna. Esta es una situación más que desafortunada para todos si todo esto es cierto. Pero ya tengo problemas para ver el Promise-hype de todos modos, he escrito 100,000 aplicaciones LoC JS y todavía no veo ninguna necesidad real de esto, es una mejora muy pequeña si es para mí , principalmente en el manejo de errores, no mejora en el manejo de la devolución de llamada (nunca he estado en "infierno de devolución de llamada" con mi estilo de codificación).
Mörre
19
En ES6, ¿no puedes usar Promise.resolve()para crear una "promesa raíz"?
zetlen
10
@ MörreNoseshine (continuación) Años más tarde, los autores de ES6 vinieron y dijeron "hey, especifiquemos que los motores JS deben proporcionar una utilidad genérica Promises / A + conforme a la configuración, para que las personas siempre tengan una herramienta de promesa básica a mano ". Esta es una buena conveniencia (no tener que importar una biblioteca solo para hacer un rápido Promise.resolve()o lo que sea), pero es una implementación muy básica, ¡y su existencia no debería desanimarte con herramientas más serias relacionadas con promesas como bluebird!
callum
11
@ Javascript MörreNoseshine 100k LOC aplicación que probablemente nunca tuvo ninguna funcionalidad asíncrona. Buena suerte escribiendo un juego 100K LoC JS con una biblioteca mysql / redis sin bluebird.
NiCk Newman