A Memoize használata a JavaScript funkció eredményeinek gyorsítótárazásához és a kód felgyorsításához

A függvények a programozás szerves részét képezik. Segítenek a modularitás és az újrafelhasználhatóság hozzáadásában a kódunkba .

Elég gyakori, hogy programunkat darabokra osztjuk olyan funkciók használatával, amelyeket később felhívhatunk néhány hasznos művelet végrehajtására.

Néha drágábbá válhat egy függvény többszöri meghívása (például egy szám faktoriálisának kiszámításához használt függvény). De van olyan módszer, amellyel optimalizálhatjuk az ilyen funkciókat, és sokkal gyorsabban végrehajthatjuk őket: gyorsítótárazás .

Tegyük fel például, hogy functionvissza kell adnunk egy szám faktoriálját:

function factorial(n) { // Calculations: n * (n-1) * (n-2) * ... (2) * (1) return factorial }

Remek, most keressük meg factorial(50). A számítógép elvégzi a számításokat és visszaadja nekünk a végső választ, édes!

Ha ez megtörtént, keressük meg factorial(51). A számítógép ismét számos számítást végez, és megkapja az eredményt, de talán észrevette, hogy már számos olyan lépést megismételünk, amelyeket elkerülhetett volna. Optimalizált módon:

factorial(51) = factorial(50) * 51

De functiona számításokat a semmiből hajtjuk végre, amikor csak hívják:

factorial(51) = 51 * 50 * 49 * ... * 2 * 1

Nem lenne jó, ha valamilyen módon a factorialfunkciónk emlékezni tudna az előző számításaiból származó értékekre és felhasználná őket a végrehajtás felgyorsítására?

Jön a memoizálás , egy mód arra, functionhogy emlékezzünk (gyorsítótárba) az eredményeket. Most, hogy alaposan megértette, mit próbálunk elérni, íme egy hivatalos meghatározás:

A memória egy optimalizálási technika, amelyet elsősorban a számítógépes programok felgyorsítására használnak a drága függvényhívások eredményeinek tárolásával és a gyorsítótárazott eredmény visszaadásával, amikor ugyanazok a bemenetek ismét előfordulnak

A memorizálás egyszerű kifejezéssel azt jelenti, hogy memorizál vagy memóriában tárol. A memóriában szereplő függvény általában gyorsabb, mert ha a függvényt az előző értékkel később hívják meg, akkor a függvény végrehajtása helyett az eredményt a gyorsítótárból kapnánk.

Így nézhet ki egy egyszerű memória funkció (és itt van egy CodePen, ha kölcsönhatásba akar lépni vele) :

// a simple function to add something const add = (n) => (n + 10); add(9); // a simple memoized function to add something const memoizedAdd = () => { let cache = {}; return (n) => { if (n in cache) { console.log('Fetching from cache'); return cache[n]; } else { console.log('Calculating result'); let result = n + 10; cache[n] = result; return result; } } } // returned function from memoizedAdd const newAdd = memoizedAdd(); console.log(newAdd(9)); // calculated console.log(newAdd(9)); // cached

Memorizálás elvihetők

Néhány elvitel a fenti kódból:

  • memoizedAdda-t ad vissza, functionamelyre később hivatkozunk. Ez azért lehetséges, mert a JavaScript-ben a függvények első osztályú objektumok, amelyek lehetővé teszik, hogy magasabb rendű függvényként használjuk őket, és egy másik függvényt adjunk vissza.
  • cacheemlékszik az értékeire, mivel a visszaküldött függvény bezáródik rajta.
  • Elengedhetetlen, hogy az emlékezetes funkció tiszta legyen. A tiszta függvény ugyanazt a kimenetet adja vissza egy adott bemenethez, nem számít, hányszor hívják meg, ami cachea vártnak megfelelő munkát eredményezi.

Saját memoizefunkció írása

Az előző kód jól működik, de mi lenne, ha bármelyik funkciót emlékezetes funkcióvá szeretnénk alakítani?

Így írhatja meg saját memoize funkcióját (codepen):

// a simple pure function to get a value adding 10 const add = (n) => (n + 10); console.log('Simple call', add(3)); // a simple memoize function that takes in a function // and returns a memoized function const memoize = (fn) => { let cache = {}; return (...args) => { let n = args[0]; // just taking one argument here if (n in cache) { console.log('Fetching from cache'); return cache[n]; } else { console.log('Calculating result'); let result = fn(n); cache[n] = result; return result; } } } // creating a memoized function for the 'add' pure function const memoizedAdd = memoize(add); console.log(memoizedAdd(3)); // calculated console.log(memoizedAdd(3)); // cached console.log(memoizedAdd(4)); // calculated console.log(memoizedAdd(4)); // cached

Most nagyszerű! Ez az egyszerű memoizefüggvény minden egyszerűt functionmemoár megfelelővé csomagol . A kód jól működik az egyszerű funkcióknál, és könnyen módosítható, hogy tetszőleges számú számot kezeljen az argumentsÖn igényeinek megfelelően. Egy másik alternatíva néhány de facto könyvtár használata, például:

  • Lodash's _.memoize(func, [resolver])
  • ES7 @memoizedekorátorok a deckótól

Rekurzív funkciók megjegyzése

Ha megpróbál rekurzív függvényt átadni a Lodash memoizefeletti vagy _.memoizeonnan érkező függvénynek, az eredmények nem lesznek a vártnak megfelelőek, mivel a későbbi hívások rekurzív függvénye a memóriás funkció helyett önmagát hívja fel, így nem használja a cache.

Csak győződjön meg arról, hogy a rekurzív függvény meghívja az emlékeztető funkciót. Így módosíthatja a tankönyv tényleges példáját (codepen):

// same memoize function from before const memoize = (fn) => { let cache = {}; return (...args) => { let n = args[0]; if (n in cache) { console.log('Fetching from cache', n); return cache[n]; } else { console.log('Calculating result', n); let result = fn(n); cache[n] = result; return result; } } } const factorial = memoize( (x) => { if (x === 0) { return 1; } else { return x * factorial(x - 1); } } ); console.log(factorial(5)); // calculated console.log(factorial(6)); // calculated for 6 and cached for 5

Néhány megjegyzendő pont ebből a kódból:

  • A factorialfüggvény rekurzívan hívja önmagának memoizált változatát.
  • A memóriában tárolt függvény gyorsítótárba helyezi a korábbi faktoriálok értékeit, ami jelentősen javítja a számításokat, mivel újból felhasználhatók factorial(6) = 6 * factorial(5)

A memória megegyezik a gyorsítótárral?

Igen, valahogy. A memória valójában a gyorsítótár speciális típusa. Míg a gyorsítótárazás általában bármilyen tárolási technikára utalhat (például a HTTP gyorsítótárazás) a jövőbeni felhasználáshoz, a memóriaírás kifejezetten magában foglalja az a visszatérési értékeinek gyorsítótárazásátfunction .

Mikor kell megjegyezni a funkciókat

Bár úgy tűnhet, hogy az emlékeztető minden funkcióval használható, valójában korlátozott használati esetekkel rendelkezik:

  • A függvény memóriájához annak tisztának kell lennie, hogy a visszatérési értékek minden alkalommal ugyanazok a bemenetek legyenek
  • A memorizálás kompromisszum a megnövelt hely és a hozzáadott sebesség között, és így csak a korlátozott bemeneti tartományú funkcióknál fontos, így a gyorsítótárazott értékek gyakrabban használhatók
  • Úgy tűnhet, hogy emlékeznie kell az API-hívásokra, de ez nem szükséges, mert a böngésző automatikusan tárolja őket az Ön számára. További részletek: HTTP gyorsítótár
  • A memoizált funkciók esetében a legjobb felhasználási mód nehéz számítógépes funkciókra vonatkozik, amelyek jelentősen javíthatják a teljesítményt (a faktoriális és a fibonacci nem igazán jó példák a valós világra)
  • Ha a React / Redux-ot kedveli, megnézheti az újraválasztást, amely egy jegyzetelt választót használ annak biztosítására, hogy a számítások csak akkor történjenek, amikor változás történik az állapotfa kapcsolódó részében.

További irodalom

The following links can be useful if you would like to know more about some of the topics from this article in more detail:

  • Higher order functions in JavaScript
  • Closures in JavaScript
  • Pure functions
  • Lodash’s _.memoize docs and source code
  • More memoization examples here and here
  • reactjs/reselect

I hope this article was useful for you, and you’ve gained a better understanding of memoization in JavaScript :)

You may follow me on twitter for latest updates. I've also started posting more recent posts on my personal blog.