web-dev-qa-db-ja.com

ネイティブES6がBluebirdよりも低速でメモリ消費量が多いのはなぜですか?

このベンチマーク では、スイートはBluebirdのプロミスと比較してES6のプロミスを完了するのに4倍の時間がかかり、3.6倍のメモリを使用します。

JavaScriptライブラリは、Cで記述されたv8のネイティブ実装よりもはるかに高速で軽量なのでしょうか。 Bluebird promiseは、ネイティブES6 promiseとまったく同じAPI(および追加のユーティリティメソッドの束)を備えています。

ネイティブ実装はひどく書かれていますか、それとも私が見逃している他のいくつかの側面がありますか?

198
callum

ブルーバードの作者はこちら。

V8は実装がJavaScriptで書かれていることを約束します Cではありません。すべてのJavaScript(V8自体を含む)はネイティブコードにコンパイルされます。さらに、ユーザーが記述したJavaScriptは、ネイティブコードにコンパイルされる前に、可能であれば(そしてその価値がある場合)最適化されます。 Promiseの実装は、Cで記述してもあまりメリットがないか、まったくメリットがありません。実際、JavaScriptオブジェクトと通信を操作しているだけなので、遅くなるだけです。

V8の実装は単にbluebirdほど最適化されていません。インスタンスに対して promiseのハンドラーに配列を割り当てます です。各プロミスがいくつかの配列を割り当てる必要がある場合、これは大量のメモリを必要とします(ベンチマークは、全体で8万のプロミスを作成するため、160の未使用の配列が割り当てられます)。実際には、99.99%のユースケースで2回以上プロミスが分岐することはないため、この一般的なケースを最適化すると、メモリ使用量が大幅に向上します。

V8がbluebirdと同じ最適化を実装したとしても、仕様によって妨げられます。 ES6でルートプロミスを作成する方法は他にないため、ベンチマークは_new Promise_(bluebirdのアンチパターン)を使用する必要があります。 _new Promise_は、Promiseを作成する非常に遅い方法です。最初にexecutor関数がクロージャーを割り当て、次に2つの個別のクロージャーを引数として渡します。これはプロミスごとに割り当てられる3つのクロージャですが、クロージャはすでに最適化されたプロミスよりも高価なオブジェクトです。

Bluebirdはpromisifyを使用できます。これにより、多くの最適化が可能になり、コールバックAPIを使用するはるかに便利な方法であり、モジュール全体を1行でプロミスベースのモジュールに変換できます(promisifyAll(require('redis'));)。

273
Esailija