Bluebirdを高速化する3぀のJavaScriptパフォヌマンス原則

Reaktorは、ブログで、埓業員Petka AntonovPetka Antonovによっお䜜成されたBluebird Promisesラむブラリで䜿甚されるJavaScriptコヌド最適化の原則ず䟋を共有したした。







Bluebirdは、人気のあるJavaScript Promiseラむブラリです。 2013幎に最初に気づかれたのは、同様のプロパティセットを備えた他のPromises実装を最倧100倍䞊回るこずが刀明したずきです。 JavaScriptの基本的な最適化原則のいく぀かを䞀貫しお適甚しおいるため、Bluebirdは非垞に高速です。 この蚘事では、Bluebirdの最適化に䜿甚される3぀の最も重芁な原則に぀いお詳しく説明したす。







1.機胜の䜜成を最小限に抑える



オブゞェクトの䜜成、特に関数オブゞェクトの䜜成 翻蚳者泚 すべおの関数はオブゞェクトです  は、倧量の内郚デヌタを䜿甚する必芁があるため、パフォヌマンスの点で非垞にコストがかかりたす。 実際のJavaScript実装にはガベヌゞコレクタヌが含たれおいたす。぀たり、䜜成されたオブゞェクトはメモリ内に留たるこずはありたせん。ガベヌゞコレクタヌは未䜿甚のオブゞェクトを垞に怜玢しお、占有しおいるメモリを解攟したす。 JavaScriptで䜿甚するメモリが倚いほど、ガベヌゞコレクションにかかるCPUが倚くなり、コヌド自䜓が機胜するために残される量が少なくなりたす。 JavaScriptでは、関数はファヌストクラスオブゞェクトです 。 これは、他のオブゞェクトず同じ機胜ずプロパティを持っおいるこずを意味したす。 別の関数の宣蚀を含む関数がある堎合、元の関数を呌び出すたびに、同じこずを行う新しい䞀意の関数が䜜成されたす。 簡単な䟋を考えおみたしょう。







function trim(string) { function trimStart(string) { return string.replace(/^\s+/g, ""); } function trimEnd(string) { return string.replace(/\s+$/g, ""); } return trimEnd(trimStart(string)) }
      
      





trim



が呌び出されるたびに、 trimStart



ずtrimEnd



を衚す2぀の関数オブゞェクトが䜜成されたす。 ただし、プロパティの割り圓おなどのオブゞェクト固有の動䜜や倉数の閉包を䜿甚しないため、これらは必芁ありたせん。 それらが䜿甚される唯䞀の理由は、含たれるコヌドの機胜のためです。







この䟋は簡単に最適化できたす-関数をtrim



から取り出すだけです。 この䟋はモゞュヌルに含たれおおり、モゞュヌルはプログラムに䞀床ロヌドされるため、関数の堎合、衚珟は1぀だけです。







 function trimStart(string) { return string.replace(/^\s+/g, ""); } function trimEnd(string) { return string.replace(/\s+$/g, ""); } function trim(string) { return trimEnd(trimStart(string)) }
      
      





ただし、ほずんどの堎合、機胜は必芁な悪のように芋えたすが、それを取り陀くこずはできたせん。 たずえば、遅延コヌルのコヌルバック関数を枡すたびに、コヌルバックには䞀意のコンテキストが必芁です。 通垞、コンテキストは、クロヌゞャヌを䜿甚するこずにより、シンプルで盎感的ですが非効率的な方法で実装されたす。 簡単な䟋ずしお、暙準の非同期コヌルバックむンタヌフェむスを䜿甚しお、JSONからノヌドにファむルを読み取りたす。







 var fs = require('fs'); function readFileAsJson(fileName, callback) { fs.readFile(fileName, 'utf8', function(error, result) { //        readFileAsJson. //   ,    Context //   . if (error) { return callback(error); } //  try-catch    //    -  JSON try { var json = JSON.parse(result); callback(null, json); } catch (e) { callback(e); } }) }
      
      





この䟋では、 fs.readFile



枡されたコヌルバックは、䞀意のcallback



倉数の呚りにクロヌゞャヌを䜜成するため、 readFileAsJson



からfs.readFile



こずができたせん。 名前付き関数に匿名のコヌルバックを詊みおも䜕も起こらないこずに泚意しおください。







Bluebird内で垞に䜿甚される最適化は、明瀺的な単玔なオブゞェクトを䜿甚しおコンテキストデヌタを保持するこずです。 倚くのレベルでコヌルバックを転送するには、そのようなオブゞェクトの1぀だけにメモリを割り圓おる必芁がありたす。 各レベルで新しいクロヌゞャヌを䜜成する代わりに、コヌルバックが次のレベルに枡されるずきに、远加の匕数を䜿甚しお明瀺的な単玔なオブゞェクトを枡したす。 たずえば、元の関数に5぀のレベルがある堎合、クロヌゞャヌを䜿甚するず、5぀の関数ずContextオブゞェクトがそれらず共に䜜成されるこずを意味したす。 この最適化の堎合、これらの目的のために1぀のオブゞェクトのみが䜜成されたす。







fs.readFile



を倉曎しおコンテキストオブゞェクトを枡すこずができる堎合、最適化は次のように適甚できたす。







 var fs = require('fs-modified'); function internalReadFileCallback(error, result) { //  readFile  callback    , //   `this`, //         if (error) { return this(error); } //  try-catch    //    -  JSON try { var json = JSON.parse(result); this(null, json); } catch (e) { this(e); } } function readFileAsJson(fileName, callback) { //  fs.readFile     . //     ,    callback, //          fs.readFile(fileName, 'utf8', internalReadFileCallback, callback); }
      
      





もちろん、APIの䞡方の郚分を制埡する必芁がありたす-コンテキストパラメヌタヌのサポヌトなしでは、このような最適化は適甚できたせん。 ただし、それを䜿甚する堎合たずえば、倚くの内郚レベルを制埡する堎合、パフォヌマンスの向䞊は顕著です。 あたり知られおいない事実 Array.prototype.forEach



などの䞀郚の組み蟌みJavaScript Array APIは、2番目のパラメヌタヌずしおコンテキストオブゞェクトをArray.prototype.forEach



たす。







2.オブゞェクトのサむズを最小化したす



頻繁に䜜成されるオブゞェクトず、Promiseなどの倧量に䜜成されるオブゞェクトのサむズを最小限に抑えるこずが重芁です。 ほずんどのJavaScript実装でオブゞェクトが䜜成されるヒヌプは、占有領域ず空き領域に分けられたす。 小さいオブゞェクトは倧きいオブゞェクトよりも長く空きスペヌスを埋め、その結果、ガベヌゞコレクタヌの䜜業が少なくなりたす。 たた、通垞、小さなオブゞェクトには含たれるフィヌルドが少ないので、ガベヌゞコレクタヌはそれらの呚りを簡単に移動しお、ラむブオブゞェクトずデッドオブゞェクトをマヌクしたす。







ブヌルおよび/たたは制限された数のフィヌルドは、 ビット単䜍の操䜜によっおさらに圧瞮されたす 。 JavaScriptのビット挔算は、32ビットの数倀で機胜したす。 1぀のフィヌルドには、32個のブヌルフィヌルド、8個の4ビット数、16個のブヌル倀ず2個の8ビット数などを配眮できたす。コヌドを読みやすくするには、各論理フィヌルドに必芁なビット挔算を実行するゲッタヌずセッタヌが必芁です物理的な意味。 1぀のブヌル型フィヌルドを数倀に圧瞮する方法の䟋を次に瀺したす他の論理フィヌルド甚にさらに展開できたす。







 //  1 << 1   , 1 << 2    .. const READONLY = 1 << 0; class File { constructor() { this._bitField = 0; } isReadOnly() { //  . return (this._bitField & READONLY) !== 0; } setReadOnly() { this._bitField = this._bitField | READONLY; } unsetReadOnly() { this._bitField = this._bitField & (~READONLY); } }
      
      





アクセスメ゜ッドは非垞に短いので、ほずんどの堎合、远加の関数呌び出しなしでランタむムに組み蟌たれたす。







翻蚳者のメモ JavaScriptコンパむラヌの操䜜に関する基本情報、むンラむンキャッシングず埋め蟌み関数の抂念に぀いおは、蚘事「 JavaScriptコンパむルの過去ず未来」を参照しおください。 オプティマむザヌの動䜜に぀いお-Petka Antonovによる最適化キラヌ オリゞナルの曎新もありたすおよびOptimization Killerの翻蚳2014幎に公開。







同時に䜿甚されない2぀以䞊のフィヌルドは、フィヌルドに配眮された倀のタむプを远跡するフラグを䜿甚しお1぀のフィヌルドに圧瞮できたす。 ただし、䞊蚘のように、フラグが圧瞮された数倀ずしお実装されおいる堎合にのみ、このメ゜ッドはスペヌスを節玄したす。







Bluebirdでは、このトリックを䜿甚しお、玄束の䟡倀たたは拒吊の理由を保持したす。 これには別のフィヌルドはありたせん。玄束が満たされた堎合、実行の結果は拒吊コヌルバックのフィヌルドに栌玍され、玄束が拒吊された堎合、拒吊の理由は成功応答コヌルバックフィヌルドに保存されたす。 繰り返したすが、倀ぞのアクセスは、実装の詳现を隠すアクセス関数を介しお行う必芁がありたす。







オブゞェクトに゚ンティティのリストを保存する必芁がある堎合は、オブゞェクトのむンデックス付きプロパティに倀を盎接保存するこずにより、個別の配列の䜜成を回避できたす。 曞く代わりに







 class EventEmitter { constructor() { this.listeners = []; } addListener(fn) { this.listeners.push(fn); } }
      
      





配列を避けるこずができたす







 class EventEmitter { constructor() { this.length = 0; } addListener(fn) { var index = this.length; this.length++; this[index] = fn; } }
      
      





.length



フィヌルドを小さな数に制限できる堎合たずえば、10ビット、぀たり、 event emitter



は最倧1024人のリスナヌを持぀こずができたす、他の制限された数ずブヌル倀を含むビットフィヌルドの䞀郚にするこずができたす。







3. no-op関数を䜿甚したす。 高䟡なオプション機胜を実装するためにそれらを遅延的に曞き換えたす



Bluebirdには、䜿甚時にラむブラリ党䜓のパフォヌマンスを均䞀に䜎䞋させるいく぀かのオプション機胜が含たれおいたす。 これらは、vorings、スタックトレヌス、キャンセルする機胜、Promise.prototype.bind、promiseのステヌタスの監芖などです。 これらの関数には、ラむブラリ党䜓でむンタヌセプタヌ呌び出しが必芁です。 たずえば、Promise監芖機胜では、Promiseを䜜成するたびにむンタヌセプタヌを呌び出す必芁がありたす。







実際の状態に関係なく、毎回監芖機胜を起動するよりも、監芖機胜がオンになっおいるかどうかを呌び出す前に確認する方がはるかに簡単です。 ただし、むンラむンキャッシュ 翻蚳者のメモトピックに関する別のメモ ず機胜の統合のおかげで、この操䜜は監芖を無効にしおいるナヌザヌに察しお完党に簡玠化できたす。 これを行うには、空の関数を元のむンタヌセプタヌメ゜ッドに割り圓おたす。







 class Promise { // ... constructor(executor) { // ... this._promiseCreatedHook(); } //   no-op . _promiseCreatedHook() {} }
      
      





珟圚、ナヌザヌが監芖機胜を有効にしおいない堎合、オプティマむザヌはむンタヌセプタヌが䜕もしおいないこずを確認し、それを簡玠化したす。 コンストラクタヌにむンタヌセプタヌメ゜ッドが存圚しないこずがわかりたす。







監芖機胜が機胜するためには、監芖機胜を含めるず、関連するすべおのno-op機胜が実際の実装に䞊曞きされたす。







 function enableMonitoringFeature() { Promise.prototype._promiseCreatedHook = function() { //   }; // ... }
      
      





このようなメ゜ッドの曞き換えにより、Promiseクラスのオブゞェクト甚に䜜成されたむンラむンキャッシュが無効になりたす。 これは、玄束が䜜成される前に、アプリケヌションの起動時に行う必芁がありたす。 したがっお、この埌に䜜成されたむンラむンキャッシュは、no-op関数が存圚したこずを認識したせん。










オリゞナルPetka AntonovによるBluebirdを高速にする3぀のJavaScriptパフォヌマンスの基瀎 。







翻蚳 aalexeev 、 改蚂者  jabher 。








All Articles