JavaScriptの検索の最適化


Originally Posted on Skiplist blog
Skiplist JavaScriptはずっと下です.
それはゾンビのウイルスのようです.その言語はすべてを引き継いだ.そして、私の手は噛む.私は私の内側のJavaScriptキティを受け入れるか、私は常に恐れているか、私はチャンスがある間、それをチョップする必要がありますか?
今週私はメモリキャッシュのルックアップを最適化しました.顧客データセットは予想より大きかった.その結果、メモリキャッシュのデータ構造をリファクタにしなければならなかった.
キャッシュの初期のバージョンはTDDを通してプログラムされて、駆動されました.私は、私がそうすることができるとき、私の内側の機敏な技術コーチを受け入れるのが好きです.それは偶然の一致であるかもしれません、しかし、ルックアップが一定時間で起こるように、実装詳細をリファクタリングするのは簡単でした.最適化トリックの技術的な詳細については後述する.
以下の例のソースを見つけることができますhere in GitHub .

宣言型プログラミング
命令は、方法を指示します.宣言コードは何を指示します.
例を見て三人の年齢を集めましょう.
const people = [
  {id: 1, name: "Jason", age: 38},
  {id: 2, name: "Justin", age: 34},
  {id: 3, name: "Josh", age: 33}
]

// imperative
const ages = []
for(let person of people) {
    ages.push(person.age);
}

// declarative 
const ages = people.map(person => person.age)
JavaScriptには、いくつかの組み込みヘルパー関数があります.
  • map()
  • reduce()
  • filter()
  • forEach()
  • find()
  • 言い換えれば、宣言的なコードは表現的で、エレガントで、機能的です."clean ソーセージがどのように作られているか気にしないで、私は同意します、あなたはそれをとてもよりよく楽しむことができます!

    findを使用して値を検索する
    何百万のエントリのリストにIDで人を見ている同様のシナリオについてはどうですか?
    const idToFind = 1000000
    person = people.find(person => person.id === idToFind);
    
    上記のステートメントはクリーンで、IDが100000の最初の人を見つけます.対照的に、同じ線形探索への命令的アプローチは、コードのおよそ1ダースのより多くの線である.シンプルです素晴らしい.シンプルでクリーンです.しかし、大文字表記(Big O Notation である.linear search 文字通り悪い.私たちは清潔さのためにパフォーマンスを犠牲にします.そして、それは私が時間的に99.8 %を選択するトレードオフですプログラミング
    キーが一意であり、我々のデータセットが管理可能なサイズの場合、我々は人々の人々のリストをIDによって人々の地図に回すことによってパフォーマンスを向上させることができ、次に、ID上のハッシュルックアップ、O(1)を実行することができます!我々は、より悪い、1回のO(n)配置ステップ、そして、o(1)が各々の記録の上でルックアップしている.

    コード例
    グッドスチュアートsoftware craftsmanship , 失敗から始めましょうJavaScript Unit Test を指定する.
    const assert = require('assert');
    const Search = require("./search");
    
    describe('Search', function () {
      const people = [];
    
      before(() => {
        people.push({id: 1, name: "Jason", age: 38});
        people.push({id: 2, name: "Justin", age: 34});
        people.push({id: 3, name: "Josh", age: 33});
      });
    
      it('should return the correct element', function () {
        const expectedName = "Justin";
        const search = new Search(people);
    
        const person = search.find(2);
    
        assert.equal(expectedName, person.name);
      });
    });
    

    命令
    この点で、我々は赤いテストをします.我々の最初のアプローチを実装しましょう.
    class Search {
      constructor(people) {
        this.people = people;
      }
    
      find(id) {
        for(let person of this.people) {
          if(person.id === id) {
            return person;
          }
        }
      }
    }
    
    module.exports = Search;
    
    パフォーマンスを評価するためにテストハーネスを設定します.
    // performance output:
    // >>> Average time for find for 3 records: 0.001 ms
    // >>> Total time for find for 3 records: 2 ms
    // >>> Average time for find for 1000000 records: 2.617 ms
    // >>> Total time for find for 1000000 records: 2906 ms
    

    宣言
    我々は、行動とパフォーマンステストハーネスをアサートする緑色のテストを持って、我々はキャビンについて移動することができますfind 方法)命令型から宣言的な形式への移行:
    // ...
    
      find(id) {
        return this.people.find(person => person.id === id);
      }
    
    // ...
    
    // performance output:
    // >>> Average time for find for 3 records: 0.001 ms
    // >>> Total time for find for 3 records: 2 ms
    // >>> Average time for find for 1000000 records: 2.356 ms
    // >>> Total time for find for 1000000 records: 2690 ms
    
    100万件の記録の検索時間は比較的変わりません.命令から宣言への移動で、コードの清潔度を得る.今すぐ“コードは、”何かのような方法では、別のデータ構造では、地図のようにスワッピングをコンセプトに簡単です.我々は認知負荷の減少を有する.

    最適化
    最後に、私たちが大きなコレクションの入れ子になったループの中でこの探索を行っていたとしたらどうでしょう.数百と50ミリレコードの検索では、各顧客の経験を簡単に低下することができます.それでは、地図を使って例を見てみましょう.安array in JavaScript は連想配列ですので、簡単にできますmap オブジェクトのキーとしてのID.
    class Search {
      constructor(people) {
        const peopleMap = [];
        people.forEach(person => peopleMap[person.id] = person);
        this.people = peopleMap
      }
    
      find(id) {
        return this.people[id]
      }
    }
    
    module.exports = Search;
    
    // performance output:
    // Average time for find for 3 records: 0.001 ms
    // Total time for find for 3 records: 2 ms
    // Average time for find for 1000000 records: 0 ms
    // Total time for find for 1000000 records: 302 ms
    

    結論
    JavaScriptの私の問題は、私がそれが好きでないということではないと思います.閉じるこの動画はお気に入りから削除されています.私はプリブラウザの標準化(IE 6〜2005のW/ActiveX)JavaScriptのWeb開発のメモリで怖いです.私は開発コミュニティ内の現在の位置を尊重し、ソリューションのあらゆる層で共通のプラットフォームオプションを見つけるのを楽しみにしています.