関数キャッシュ

10412 ワード

関数キャッシュとは
この概念を理解するために、天気appを開発していると仮定します.最初はどうすればいいか分かりませんが、npmパッケージにgetChanceOfRainの方法があります.
import { getChangeOfRain } from 'magic-weather-calculator';

function showWeatherReport() {
  let result = getChangeOfRain();    //     
  console.log('The change of rain tomorrow is: ', result);
}

ただ、これで問題が発生します.何をしても、この方法を呼び出すだけで100ミリ秒かかります.そのため、あるユーザーが「天気を表示」ボタンをクリックすると、appをクリックするたびにしばらく応答しません.
showWeatherReport(); //     
showWeatherReport(); //     
showWeatherReport(); //     

これは理性的ではない.実際の開発では、結果を知っていれば、一度に計算することはできません.前回の結果を再利用するのが一番いい選択です.これが関数キャッシュです.関数キャッシュとは、関数の決済結果をキャッシュすることであり、一度に関数を呼び出す必要がない.
次の例では、memoizedGetChangeOfRain()を呼び出します.このメソッドでは、getChangeOfRain()メソッドを毎回呼び出すことなく、結果が得られたかどうかを確認します.
import { getChangeOfRain } from 'magic-weather-calculator';

let isCalculated = false;
let lastResult;

//       
function momoizedGetChangeOfRain() {
  if (isCalculated) {
    //         
    return lastResult;
  }
  
  //         
  let result = getChangeOfRain();
  
  lastResult = result;
  isCalculated = true;
  
  return result;
}

function showWeatherReport() {
  let result = momoizedGetChangeOfRain();
  console.log('The chance of rain tomottow is:', result);
}

複数回呼び出されたshowWeatherReport()は、最初に計算されるだけで、他は最初の計算の結果を返します.
showWeatherReport(); // (!)   
showWeatherReport(); //       
showWeatherReport(); // Uses the calculated result
showWeatherReport(); // Uses the calculated result

これが関数キャッシュです.関数がキャッシュされていると言ったら、javascript言語で何をしたのかではありません.結果が変わらないことを知っている場合は、不要な呼び出しを避けます.
関数キャッシュとパラメータ
一般的な関数キャッシュモード:
  • 結果があるかどうかを確認する
  • もしそうであれば、この結果
  • を返す.
  • がない場合、計算結果は後で
  • に戻る.
    しかし,実際の開発ではいくつかの状況を考慮する必要がある.たとえば、getChangeOfRain()メソッドは、都市パラメータを受信します.
    function showWeatherReport(city) {
      let result = getChanceOfRain(city); // Pass the city
      console.log("The chance of rain tomorrow is:", result);
    }

    この関数を単純に前のようにキャッシュすると、バグが発生します.
    showWeatherReport('Tokyo');  // (!) Triggers the calculation
    showWeatherReport('London'); // Uses the calculated answer

    見つけたか?東京とロンドンの天気はとても違うので、前の計算結果を直接使うことはできません.つまり、関数キャッシュを使用するときはパラメータを考慮する必要があります.
    方法1:前回の結果を保存
    最も簡単な方法は、結果とこの結果に依存するパラメータをキャッシュすることです.つまり、
    import { getChanceOfRain } from 'magic-weather-calculator';
    
    let lastCity;
    let lastResult;
    
    function memoizedGetChanceOfRain(city) {
      if (city === lastCity) { //     !
        //            
        return lastResult;
      }
      
      //      ,           
      let result = getChanceOfRain(city);
      
      //        .
      lastCity = city;
      lastResult = result;
      return result;
    }
    
    function showWeatherReport(city) {
      //           
      let result = memoizedGetChanceOfRain(city);
      console.log("The chance of rain tomorrow is:", result);
    }

    この例と最初の例の少しの違いに注意してください.前回の計算結果を直接返すのではなく、city === lastCityを比較します.途中で都市が変わったら、結果を再計算しなければなりません.
    showWeatherReport('Tokyo');  // (!)   
    showWeatherReport('Tokyo');  //       
    showWeatherReport('Tokyo');  //       
    showWeatherReport('London'); // (!)     
    showWeatherReport('London'); //       

    このように最初の例のバグを修正したが,必ずしも最善の解決策ではない.呼び出すたびにパラメータが異なると、上記の解決策は役に立たない.
    showWeatherReport('Tokyo');  // (!)     
    showWeatherReport('London'); // (!)     
    showWeatherReport('Tokyo');  // (!)     
    showWeatherReport('London'); // (!)     
    showWeatherReport('Tokyo');  // (!)     

    関数キャッシュを使用するときは、本当に役に立つかどうかを確認します.
    メソッド2:複数の結果を保持
    もう一つできることは、複数の結果を保持することです.パラメータごとにlastTokyoResult, lastLondonResultなどの変数を定義できますが.Mapを使用すると、より良い方法に見えます.
    let resultsPerCity = new Map();
    
    function memoizedGetChangeOfRain(city) {
      if (resultsPerCity.has(city)) {
        //          
        return resultsPerCity.get(city);
      }
      
      //          
      let result = getChangeOfRain(city);
      
      //          
      resultsPerCity.set(city, result);
      
      return result;
    }
    
    function showWeatherReport(city) {
      let result = memoizedGetChangeOfRain(city);
      console.log('The chance of rain tomorrow is:', result);
    }

    全体の方法と私たちに適した使用例.都市データを初めて取得したときにしか計算されないからです.同じ都市を使用してデータを取得すると、すでにMapに保存されているデータが返されます.
    showWeatherReport('Tokyo');  // (!)     
    showWeatherReport('London'); // (!)     
    showWeatherReport('Tokyo');  //       
    showWeatherReport('London'); //       
    showWeatherReport('Tokyo');  //       
    showWeatherReport('Paris');  // (!)     

    しかし、このような方法にも欠点がないわけではない.特に我々の都市パラメータが増加している場合、Mapに保存されているデータは増加し続けます.
    したがって、この方法は、パフォーマンスの向上と同時にメモリを無節制に消費する.最悪の場合、ブラウザtabがクラッシュします.
    その他の方法
    「前の結果のみ保存」と「すべての結果を保存」の間には、他にも多くの方法があります.たとえば、最近使用した最後のN個の結果、つまり私がよく知っているLRU、または「最近最小使用」キャッシュを保存します.これらはいずれもMapの外に他の論理を追加する方法である.ブラウザがキャッシュが期限切れになった後に削除するように、過去のデータを削除することもできます.パラメータがオブジェクト(上記の例ではない文字列)である場合、WeakMapの代わりにMapを使用することができます.現代的なブラウザはすべてサポートされています.WeakMapを使用する利点は、keyのオブジェクトが存在しない場合にキー値ペアをすべて削除することです.関数キャッシュは非常に柔軟なテクノロジーであり、状況に応じて異なるポリシーを使用することができます.
    関数キャッシュと関数純度
    関数キャッシュは常に安全ではないことを知っています.getChangeOfRain()メソッドは、1つの都市をパラメータとして受け入れず、ユーザ入力を直接受信すると仮定する.
    function getChangeOfRain() {
      //      
      let city = prompt('Where do you live?');
      
      //     
    }
    
    //      
    function showWeatherReport() {
      let result = getChangeOfRain();
      console.log('The chance of rain tomorrow is:', result);
    }
    showWeatherReport()メソッドが呼び出されるたびに、入力ボックスが表示されます.私たちは異なる都市を入力することができて、consoleの中で異なる結果を見ることができます.しかし、getChanceOfRain()メソッドをキャッシュすると、入力ボックスが1つしか見えません!異なる都市を入力できません.
    したがって、関数キャッシュは、その関数が純粋な関数である場合にのみ安全です.すなわち,パラメータのみを読み出し,外部と対話しない.純粋な関数で、1回呼び出すか、使用する前のキャッシュ結果はどうでもいいです.
    これも複雑なアルゴリズムの中で、計算したコードと何をするコードだけを分離した理由です.純粋な計算方法は、複数回の呼び出しを回避するために安全なキャッシュを行うことができる.何をするかという方法では、同じ処理はできません.
    //             ,          
    //             。
    function getChanceOfRain(city) {
      // ...    ...
    }
    
    //              ,       
    function showWeatherReport() {
      //        
      let city = prompt('Where do you live?');
      let result = getChanceOfRain(city);
      console.log("The chance of rain tomorrow is:", result);
    }
    getChanceOfRain()を安全に関数キャッシュできるようになりました.--入力ボックスをポップアップするのではなく、cityをパラメータとして受け入れるからです.すなわち、純粋な関数です.showWeatherReport()が呼び出されるたびに、入力ボックスが表示されます.しかし,結果が得られた後の対応する計算は避けられる.
    関数キャッシュの再利用
    多くのメソッドをキャッシュする場合は、各メソッドにキャッシュを書くのは少し繰り返します.これは自動化できます.一つの方法でできます.
    最初の例を示します.
    let isCalculated = false;
    let lastResult;
    
    function memoizedGetChanceOfRain() {
      if (isCalculated) {
        return lastResult;
      }
      
      let result = getChanceOfRain();
      lastResult = result;
      isCalculated = true;
      
      return result;
    }

    その後、これらの手順をmemoizeという方法に入れました.
    function memoize() {
      let isCalculated = false;
      let lastResult;
      
      function memoizedGetChanceOfRain() {
        if (isCalculated) {
          return lastResult;
        }
        
        let result = getChanceOfRain();
        lastResult = result;
        isCalculated = true;
        return result;
      }
    }

    雨が降る確率を計算するだけでなく、この方法をもっと役に立ちます.メソッドパラメータをfnと追加します
    function memoize(fn) { //   fn  
      let isCalculated = false;
      let lastResult;
      
      function memoizedGetChanceOfRain() {
        if (isCalculated) {
          return lastResult;
        }
        let result = fn(); //          
        lastResult = result;
        isCalculated = true;
        return result;
      }
    }

    最後にmemoizedGetChanceOfRain()memoizedFnに名前を変更し、戻ります.
    function memoize(fn) {
      let isCalculated = false;
      let lastResult;
      
      return function memoizedFn() {
        if (isCalculated) {
          return lastResult;
        }
        
        let result = fn();
        lastResult = result;
        isCalculated = true;
        return result;
      }
    }

    再利用可能なキャッシュ関数を得た.
    最初の例は次のように変更できます.
    import { getChanceOfRain } from 'magic-weather-calculator';
    
    let memoizedGetChanceOfRain = memoize(getChanceOfRain);
    
    function showWeatherReport() {
      let result = memoizedGetChanceOfRain();
      console.log('The chance of rain tomorrow is:', result);
    }
    isCalculatedおよびlastResultはまだ存在するが、memoizeの方法内にある.つまり彼らは閉鎖の一部だ.memoizeメソッドは、毎回独立してキャッシュされます.
    import { getChanceOfRain, getNextEarthquake, getCosmicRaysProbability } from 'magic-weather-calculator';
    
    let momoizedGetChanceOfRain = memoize(getChanceOfRain);
    let memoizedGetNextEarthquake = memoize(getNextEarthquake);
    let memoizedGetCosmicRaysProbability = memoize(getCosmicRaysProbability);

    ここでmemoizeの目的は、メソッドのキャッシュバージョンを生成することである.これで、毎回そんなに繰り返しコードを書かないでください.
    レビュー
    今すぐ振り返ることができます.関数キャッシュは、プログラムの実行を高速化する方法です.計算(純粋な関数)のみを行うコードがある場合、このコードは、関数キャッシュによって同じ結果を回避することによって、不要な繰返し計算を実行することができます.
    最後のN個の結果をキャッシュしたり、すべての結果をキャッシュしたりすることができます.これらは実際の状況に応じて取捨選択する必要があります.
    あなた自身がmemoizeの方法を実現するのは難しくありません.同僚にもこのことを手伝ってくれるバッグがあります.ここではLodashの実装があります.
    最も核心的な部分は基本的にこのようなものです.
    import { getChanceOfRain } from 'magic-weather-calculator';
    
    function showWeatherReport() {
      let result = getChanceOfRain();
      console.log('The chance of rain tomorrow is:', result);
    }

    次のようになります.
    import { getChanceOfRain } from 'magic-weather-calculator';
    
    let isCalculated = false;
    let lastResult;
    
    function memoizedGetChanceOfRain() {
      if (isCalculated) {
        return lastResult;
      }
      let result = getChanceOfRain();
      lastResult = result;
      isCalculated = true;
      return result;
    }
    
    function showWeatherReport() {
      let result = memoizedGetChanceOfRain();
      console.log("The chance of rain tomorrow is:", result);
    }

    関数キャッシュを合理的に使用すると、実際のパフォーマンスが向上します.もちろん、複雑さや潜在的なバグに注意してください.
    コメント
    原文はここにあります:https://whatthefork.is/memoiz...