Completați tutorialul despre cache LRU cu implementări
Ce este LRU Cache?
Algoritmii de înlocuire a memoriei cache sunt proiectați eficient pentru a înlocui memoria cache atunci când spațiul este plin. The Cel mai puțin recent folosit (LRU) este unul dintre acei algoritmi. După cum sugerează și numele când memoria cache este plină, LRU alege datele care au fost utilizate cel mai puțin recent și le elimină pentru a face spațiu pentru noile date. Prioritatea datelor din memoria cache se modifică în funcție de necesitatea acelor date, adică dacă unele date sunt preluate sau actualizate recent, atunci prioritatea acelor date va fi schimbată și atribuită celei mai mari priorități, iar prioritatea datelor scade dacă rămâne operațiuni neutilizate după operații.
Cuprins
- Ce este LRU Cache?
- Operațiuni pe cache LRU:
- Funcționarea LRU Cache:
- Modalități de implementare a LRU Cache:
- Implementarea cache-ului LRU folosind Queue și Hashing:
- Implementarea cache-ului LRU utilizând Listă dublu legată și Hashing:
- Implementarea cache-ului LRU folosind Deque & Hashmap:
- Implementarea cache-ului LRU folosind Stack & Hashmap:
- Cache-ul LRU folosind Implementarea Counter:
- Implementarea cache-ului LRU folosind Lazy Updates:
- Analiza complexității cache-ului LRU:
- Avantajele cache-ului LRU:
- Dezavantajele cache-ului LRU:
- Aplicația în lumea reală a LRU Cache:
LRU algoritmul este o problemă standard și poate avea variații în funcție de nevoi, de exemplu, în sistemele de operare LRU joacă un rol crucial, deoarece poate fi folosit ca algoritm de înlocuire a paginii pentru a minimiza erorile de pagină.
Operațiuni pe cache LRU:
- LRUCache (capacitate c): Inițializați memoria cache LRU cu capacitate de dimensiune pozitivă c.
- obține (cheia) : returnează valoarea cheii „ k’ dacă este prezent în cache, altfel returnează -1. De asemenea, actualizează prioritatea datelor din memoria cache LRU.
- pune (cheie, valoare): Actualizați valoarea cheii dacă acea cheie există. În caz contrar, adăugați perechea cheie-valoare în cache. Dacă numărul de chei a depășit capacitatea cache-ului LRU, atunci respingeți cheia folosită cel mai puțin recent.
Funcționarea LRU Cache:
Să presupunem că avem un cache LRU de capacitate 3 și am dori să efectuăm următoarele operații:
- Puneți (cheie=1, valoare=A) în cache
- Puneți (cheie=2, valoare=B) în cache
- Puneți (cheie=3, valoare=C) în cache
- Obțineți (cheie=2) din cache
- Obțineți (cheie=4) din cache
- Puneți (cheie=4, valoare=D) în cache
- Puneți (cheie=3, valoare=E) în cache
- Obțineți (cheie=4) din cache
- Puneți (cheie=1, valoare=A) în cache
Operațiile de mai sus sunt efectuate una după alta, așa cum se arată în imaginea de mai jos:
Explicație detaliată a fiecărei operațiuni:
- Put (cheia 1, valoarea A) : Deoarece memoria cache LRU are capacitate goală=3, nu este nevoie de nicio înlocuire și punem {1 : A} în partea de sus, adică {1 : A} are cea mai mare prioritate.
- Put (cheia 2, valoarea B) : Deoarece memoria cache LRU are capacitate goală = 2, din nou nu este nevoie de nicio înlocuire, dar acum {2 : B} are cea mai mare prioritate și prioritate de {1 : A} scade.
- Put (cheia 3, valoarea C) : Totuși există 1 spațiu liber liber în cache, așadar puneți {3 : C} fără nicio înlocuire, observați că acum cache-ul este plin și ordinea curentă de prioritate de la cea mai mare la cea mai mică este {3:C}, {2:B }, {1:A}.
- Obține (cheia 2) : Acum, returnați valoarea cheie=2 în timpul acestei operațiuni, de asemenea, deoarece este folosită cheia=2, acum noua ordine de prioritate este {2:B}, {3:C}, {1:A}
- Obțineți (cheia 4): Observați că cheia 4 nu este prezentă în cache, returnăm „-1” pentru această operație.
- Put (cheia 4, valoarea D) : Observați că memoria cache este FULL, acum utilizați algoritmul LRU pentru a determina care cheie a fost folosită cel mai puțin recent. Deoarece {1:A} a avut cea mai mică prioritate, eliminați {1:A} din memoria cache și puneți {4:D} în cache. Observați că noua ordine de prioritate este {4:D}, {2:B}, {3:C}
- Put (cheia 3, valoarea E) : Deoarece cheia=3 era deja prezentă în memoria cache având valoare=C, deci această operație nu va duce la eliminarea niciunei chei, mai degrabă va actualiza valoarea cheii=3 la „ ȘI' . Acum, noua ordine de prioritate va deveni {3:E}, {4:D}, {2:B}
- Obține (cheia 4) : Returnează valoarea key=4. Acum, noua prioritate va deveni {4:D}, {3:E}, {2:B}
- Put (cheia 1, valoarea A) : Deoarece memoria cache este PLINĂ, folosiți algoritmul nostru LRU pentru a determina ce cheie a fost folosită cel mai puțin recent și, deoarece {2:B} a avut cea mai mică prioritate, eliminați {2:B} din memoria cache și puneți {1:A} în cache. Acum, noua ordine de prioritate este {1:A}, {4:D}, {3:E}
Modalități de implementare a LRU Cache:
Cache-ul LRU poate fi implementat într-o varietate de moduri și fiecare programator poate alege o abordare diferită. Cu toate acestea, mai jos sunt abordările frecvent utilizate:
- LRU folosind Queue și Hashing
- LRU folosind Listă dublu legată + Hashing
- LRU folosind Deque
- LRU folosind Stack
- LRU folosind Implementarea contra
- LRU utilizând Lazy Updates
Implementarea cache-ului LRU folosind Queue și Hashing:
Folosim două structuri de date pentru a implementa un cache LRU.
- Coadă este implementat folosind o listă dublu legată. Dimensiunea maximă a cozii va fi egală cu numărul total de cadre disponibile (dimensiunea cache). Cele mai recente pagini utilizate vor fi aproape de partea din față, iar cele mai puțin recente pagini vor fi aproape de partea din spate.
- Un Hash cu numărul paginii ca cheie și adresa nodului corespunzător din coadă ca valoare.
Când se face referire la o pagină, pagina necesară poate fi în memorie. Dacă se află în memorie, trebuie să detașăm nodul listei și să-l aducem în fața cozii.
Dacă pagina necesară nu este în memorie, o aducem în memorie. Cu cuvinte simple, adăugăm un nou nod în fața cozii și actualizăm adresa nodului corespunzătoare în hash. Dacă coada este plină, adică toate cadrele sunt pline, eliminăm un nod din spatele cozii și adăugăm noul nod în partea din față a cozii.
Ilustrare:
Să luăm în considerare operațiunile, Se referă cheie X cu în memoria cache LRU: { 1, 2, 3, 4, 1, 2, 5, 1, 2, 3 }
Notă: Inițial nu există nicio pagină în memorie.Imaginile de mai jos arată execuția pas cu pas a operațiunilor de mai sus pe memoria cache LRU.
![]()
Algoritm:
- Creați o clasă LRUCache cu declara o listă de tip int, o hartă neordonată de tip
, și o variabilă pentru a stoca dimensiunea maximă a memoriei cache - În funcția de referință a LRUCache
- Dacă această valoare nu este prezentă în coadă, împingeți această valoare în fața cozii și eliminați ultima valoare dacă coada este plină
- Dacă valoarea este deja prezentă, eliminați-o din coadă și împingeți-o în fața cozii
- În funcția de afișare tipăriți, LRUCache-ul utilizând coada începând din față
Mai jos este implementarea abordării de mai sus:
C++
// We can use stl container list as a double> // ended queue to store the cache keys, with> // the descending time of reference from front> // to back and a set container to check presence> // of a key. But to fetch the address of the key> // in the list using find(), it takes O(N) time.> // This can be optimized by storing a reference> // (iterator) to each key in a hash map.> #include> using> namespace> std;> > class> LRUCache {> > // store keys of cache> > list <> int> >dq;>>> int> csize;> // maximum capacity of cache> > public> :> > LRUCache(> int> );> > void> refer(> int> );> > void> display();> };> > // Declare the size> LRUCache::LRUCache(> int> n) { csize = n; }> > // Refers key x with in the LRU cache> void> LRUCache::refer(> int> x)> {> > // not present in cache> > if> (ma.find(x) == ma.end()) {> > // cache is full> > if> (dq.size() == csize) {> > // delete least recently used element> > int> last = dq.back();> > > // Pops the last element> > dq.pop_back();> > > // Erase the last> > ma.erase(last);> > }> > }> > > // present in cache> > else> > dq.erase(ma[x]);> > > // update reference> > dq.push_front(x);> > ma[x] = dq.begin();> }> > // Function to display contents of cache> void> LRUCache::display()> {> > > // Iterate in the deque and print> > // all the elements in it> > for> (> auto> it = dq.begin(); it != dq.end(); it++)> > cout < < (*it) < <> ' '> ;> > > cout < < endl;> }> > // Driver Code> int> main()> {> > LRUCache ca(4);> > > ca.refer(1);> > ca.refer(2);> > ca.refer(3);> > ca.refer(1);> > ca.refer(4);> > ca.refer(5);> > ca.display();> > > return> 0;> }> // This code is contributed by Satish Srinivas> |
C
// A C program to show implementation of LRU cache> #include> #include> > // A Queue Node (Queue is implemented using Doubly Linked> // List)> typedef> struct> QNode {> > struct> QNode *prev, *next;> > unsigned> > pageNumber;> // the page number stored in this QNode> } QNode;> > // A Queue (A FIFO collection of Queue Nodes)> typedef> struct> Queue {> > unsigned count;> // Number of filled frames> > unsigned numberOfFrames;> // total number of frames> > QNode *front, *rear;> } Queue;> > // A hash (Collection of pointers to Queue Nodes)> typedef> struct> Hash {> > int> capacity;> // how many pages can be there> > QNode** array;> // an array of queue nodes> } Hash;> > // A utility function to create a new Queue Node. The queue> // Node will store the given 'pageNumber'> QNode* newQNode(unsigned pageNumber)> {> > // Allocate memory and assign 'pageNumber'> > QNode* temp = (QNode*)> malloc> (> sizeof> (QNode));> > temp->pageNumber = pageNumber;>>> > return> temp;> }> > // A utility function to create an empty Queue.> // The queue can have at most 'numberOfFrames' nodes> Queue* createQueue(> int> numberOfFrames)> {> > Queue* queue = (Queue*)> malloc> (> sizeof> (Queue));> > > // The queue is empty> > queue->număr = 0;>>> > // Number of frames that can be stored in memory> > queue->numberOfFrames = numberOfFrames;>>> > // Create an array of pointers for referring queue nodes> > hash->matrice>>> (QNode*));> > > // Initialize all hash entries as empty> > int> i;> > for> (i = 0; i capacity; ++i)> > hash->matrice[i] = NULL;>>> > // A utility function to check if queue is empty> int> isQueueEmpty(Queue* queue)> {> > return> queue->spate == NULL;>>> > // Change rear and remove the previous rear> > QNode* temp = queue->spate;>>> > if> (queue->spate)>>> > free> (temp);> > > // decrement the number of full frames by 1> > queue->numara--;>>> // queue and hash> void> Enqueue(Queue* queue, Hash* hash, unsigned pageNumber)> {> > // If all frames are full, remove the page at the rear> > if> (AreAllFramesFull(queue)) {> > // remove page from hash> > hash->matrice[coadă->spate->număr pagină] = NULL;>>> > // If queue is empty, change both front and rear> > // pointers> > if> (isQueueEmpty(queue))> > queue->spate = coada->fata = temp;>>> queue->front = temp;>>> > // increment number of full frames> > queue->numără++;>>> // are two cases:> // 1. Frame is not there in memory, we bring it in memory> // and add to the front of queue> // 2. Frame is there in memory, we move the frame to front> // of queue> void> ReferencePage(Queue* queue, Hash* hash,> > unsigned pageNumber)> {> > QNode* reqPage = hash->matrice[pageNumber];>>> // Unlink rquested page from its current location> > // in queue.> > reqPage->prev->next = reqPage->next;>>> reqPage->următorul->prev = reqPage->prev;>>> queue->spate = reqPage->prev;>>> }> > > // Put the requested page before current front> > reqPage->următor = coadă->față;>>> > // Change prev of current front> > reqPage->următor->prev = reqPage;>>> }> }> > // Driver code> int> main()> {> > // Let cache can hold 4 pages> > Queue* q = createQueue(4);> > > // Let 10 different pages can be requested (pages to be> > // referenced are numbered from 0 to 9> > Hash* hash = createHash(10);> > > // Let us refer pages 1, 2, 3, 1, 4, 5> > ReferencePage(q, hash, 1);> > ReferencePage(q, hash, 2);> > ReferencePage(q, hash, 3);> > ReferencePage(q, hash, 1);> > ReferencePage(q, hash, 4);> > ReferencePage(q, hash, 5);> > > // Let us print cache frames after the above referenced> > // pages> > printf> (> '%d '> , q->front->pageNumber);>>> , q->front->next->pageNumber);>>> , q->front->next->next->pageNumber);>>> , q->front->next->next->next->pageNumber);>>> |