Kuinka käyttää Memoizea välimuistiin JavaScriptiä varten ja nopeuttaa koodia

Toiminnot ovat olennainen osa ohjelmointia. Ne auttavat lisäämään modulaarisuutta ja uudelleenkäytettävyyttä koodissamme.

On melko yleistä jakaa ohjelmamme paloiksi käyttämällä toimintoja, joihin voimme myöhemmin soittaa suorittamaan hyödyllisiä toimintoja.

Joskus funktiosta voi tulla kallista kutsua useita kertoja (esimerkiksi funktio, jolla lasketaan luvun faktori). Mutta on tapa, jolla voimme optimoida tällaiset toiminnot ja saada ne suorittamaan paljon nopeammin: välimuisti .

Oletetaan esimerkiksi, että meillä on functionpalautettava luvun kerroin:

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

Hienoa, nyt löydetään factorial(50). Tietokone suorittaa laskutoimitukset ja palauttaa meille lopullisen vastauksen.

Kun se on tehty, löydetään factorial(51). Tietokone suorittaa jälleen useita laskutoimituksia ja saa meille tuloksen, mutta olet ehkä huomannut, että toistamme jo useita vaiheita, jotka olisi voitu välttää. Optimoitu tapa olisi:

factorial(51) = factorial(50) * 51

Mutta functionsuoritamme laskelmat tyhjästä joka kerta, kun sitä kutsutaan:

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

Eikö olisikin hienoa, jos funktiomme factorialvoisi jotenkin muistaa edellisten laskelmien arvot ja käyttää niitä nopeuttamaan suoritusta?

Tulee muistiointi , tapa functionmuistaa (välimuisti) tulokset. Nyt kun sinulla on perustiedot siitä, mitä yritämme saavuttaa, tässä on virallinen määritelmä:

Muistiinpano on optimointitekniikka, jota käytetään ensisijaisesti tietokoneohjelmien nopeuttamiseen tallentamalla kalliiden toimintokutsujen tulokset ja palauttamalla välimuistissa oleva tulos, kun samat tulot toistuvat uudelleen

Muistaminen yksinkertaisella tavalla tarkoittaa muistamista tai tallentamista muistiin. Muistiin merkitty funktio on yleensä nopeampi, koska jos funktiota kutsutaan myöhemmin edellisillä arvoilla, funktion suorittamisen sijaan haemme tuloksen välimuistista.

Näin yksinkertainen muistiin jätetty toiminto voi näyttää (ja tässä on CodePen, jos haluat olla vuorovaikutuksessa sen kanssa) :

// 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

Muistiinpanot

Jotkut takeaways edellä olevasta koodista ovat:

  • memoizedAddpalauttaa a, functionjota kutsutaan myöhemmin. Tämä on mahdollista, koska JavaScriptissä funktiot ovat ensiluokkaisia ​​objekteja, joiden avulla voimme käyttää niitä korkeamman tason funktioina ja palauttaa toisen funktion.
  • cachevoi muistaa arvot, koska palautetulla funktiolla on suljin.
  • On tärkeää, että muistiin tallennettu toiminto on puhdas. Puhdas toiminto palauttaa saman tuotoksen tietylle tulolle, ei materiaalia, kuinka monta kertaa sitä kutsutaan, mikä tekee cachetyöstä odotettua.

Oman memoizetoiminnon kirjoittaminen

Edellinen koodi toimii hyvin, mutta entä jos haluaisimme muuttaa minkä tahansa toiminnon muistiin tallennetuksi toiminnoksi?

Näin kirjoitat oman muistiinpanotoiminnon (koodisynkä):

// 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

Nyt se on hienoa! Tämä yksinkertainen memoizetoiminto kääri kaikki yksinkertaiset functionmuistiin merkityksi vastaavaksi. Koodi toimii hyvin yksinkertaisissa toiminnoissa, ja sitä voidaan helposti muokata käsittelemään mikä tahansa määrä argumentstarpeitasi. Toinen vaihtoehto on käyttää joitain tosiasiallisia kirjastoja, kuten:

  • Lodashin _.memoize(func, [resolver])
  • ES7- @memoizesisustajat deckolta

Rekursiivisten toimintojen muistaminen

Jos yrität siirtää rekursiivisen funktion Lodashin memoizeylä- tai _.memoizealapuolella olevaan funktioon, tulokset eivät ole odotettuja, koska sen seuraavien puheluiden rekursiivinen toiminto lopulta kutsuu itseään muistiin tallennetun toiminnon sijaan, jolloin ei käytetä cache.

Varmista vain, että rekursiivinen toiminto kutsuu muistiin tallennettua toimintoa. Näin voit säätää oppikirjan tekijäesimerkkiä (koodisäiliö):

// 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

Muutama huomautettava asia tästä koodista:

  • factorialToiminto rekursiivisesti kutsumalla memoized version itsestään.
  • Muistiin tallennettu toiminto tallentaa välimuistiin aiempien tekijöiden arvot, mikä parantaa merkittävästi laskelmia, koska niitä voidaan käyttää uudelleen factorial(6) = 6 * factorial(5)

Onko muistiointi sama kuin välimuisti?

Kyllä, sellainen. Muistiinpano on itse asiassa tietyntyyppinen välimuisti. Vaikka välimuisti voi viitata yleisesti mihin tahansa tallennustekniikkaan (kuten HTTP-välimuistiin) tulevaa käyttöä varten, muistiinpanoon liittyy nimenomaan a: n palautusarvojen välimuistifunction .

Milloin muistiinpano toiminnot

Vaikka muistiinpanoa saattaakin käyttää kaikkien toimintojen kanssa, sillä on tosiasiallisesti rajoitettuja käyttötapauksia:

  • Funktion muistiinpanon tulee olla puhdas, jotta paluuarvot ovat samat samoille tuloille joka kerta
  • Muistiinpano on kompromissi lisätyn tilan ja lisätyn nopeuden välillä ja siten merkittävä vain toiminnoille, joilla on rajallinen syöttöalue, jotta välimuistissa olevia arvoja voidaan käyttää useammin
  • Saattaa näyttää siltä, ​​että sinun pitäisi muistaa API-puhelut muistiinpanosta, mutta se ei ole välttämätöntä, koska selain tallentaa välimuistiin ne automaattisesti. Katso lisätietoja HTTP-välimuistista
  • Paras löydetty muistiominaisuuksien käyttötapa on raskas laskennallinen toiminto, joka voi parantaa merkittävästi suorituskykyä (faktori ja fibonacci eivät todellakaan ole hyviä esimerkkejä todellisesta maailmasta)
  • Jos olet React / Redux -ohjelmassa, voit tarkistaa uudelleenvalinnan, joka käyttää muistiin merkittyä valitsinta varmistaakseen, että laskelmat tapahtuvat vain, kun muutos tapahtuu tilapuun liittyvässä osassa.

Lisälukemista

Seuraavat linkit voivat olla hyödyllisiä, jos haluat tietää enemmän joistakin tämän artikkelin aiheista:

  • Korkeamman tilauksen toiminnot JavaScriptissä
  • Sulkemiset JavaScriptissä
  • Puhtaat toiminnot
  • Lodashin _.memoizeasiakirjat ja lähdekoodi
  • Lisää muistiinpanosimerkkejä täältä ja täältä
  • reagoi / valitse uudelleen

Toivon, että tämä artikkeli oli hyödyllinen sinulle, ja olet oppinut paremmin ymmärtämään muistiinpanoja JavaScriptissä :)

Voit seurata minua Twitterissä uusimpien päivitysten saamiseksi. Olen myös alkanut lähettää uudempia viestejä henkilökohtaiseen blogiini.