多数人对LRU应该很熟悉吧。这里就是LRU的近似实现算法,其原理跟LRU差不错。
大家在准备笔试面试的时候,肯定遇到过LRU相关的问题。
- /* LRU approximation algorithm
- *
- * Redis uses an approximation of the LRU algorithm that runs in constant
- * memory. Every time there is a key to expire, we sample N keys (with
- * N very small, usually in around 5) to populate a pool of best keys to
- * evict of M keys (the pool size is defined by EVPOOL_SIZE).
- *
- * The N keys sampled are added in the pool of good keys to expire (the one
- * with an old access time) if they are better than one of the current keys
- * in the pool.
- *
- * After the pool is populated, the best key we have in the pool is expired.
- * However note that we don't remove keys from the pool when they are deleted
- * so the pool may contain keys that no longer exist.
- *
- * When we try to evict a key, and all the entries in the pool don't exist
- * we populate it again. This time we'll be sure that the pool has at least
- * one key that can be evicted, if there is at least one key that can be
- * evicted in the whole database. */
-
- /* Create a new eviction pool. */
- void evictionPoolAlloc(void) {
- struct evictionPoolEntry *ep;
- int j;
-
- ep = zmalloc(sizeof(*ep)*EVPOOL_SIZE);
- for (j = 0; j < EVPOOL_SIZE; j++) {
- ep[j].idle = 0;
- ep[j].key = NULL;
- ep[j].cached = sdsnewlen(NULL,EVPOOL_CACHED_SDS_SIZE);
- ep[j].dbid = 0;
- }
- EvictionPoolLRU = ep;
- }
-
- /* This is an helper function for performEvictions(), it is used in order
- * to populate the evictionPool with a few entries every time we want to
- * expire a key. Keys with idle time bigger than one of the current
- * keys are added. Keys are always added if there are free entries.
- *
- * We insert keys on place in ascending order, so keys with the smaller
- * idle time are on the left, and keys with the higher idle time on the
- * right. */
-
- void evictionPoolPopulate(int dbid, dict *sampledict, dict *keydict, struct evictionPoolEntry *pool) {
- int j, k, count;
- dictEntry *samples[server.maxmemory_samples];
-
- count = dictGetSomeKeys(sampledict,samples,server.maxmemory_samples);
- for (j = 0; j < count; j++) {
- unsigned long long idle;
- sds key;
- robj *o;
- dictEntry *de;
-
- de = samples[j];
- key = dictGetKey(de);
-
- /* If the dictionary we are sampling from is not the main
- * dictionary (but the expires one) we need to lookup the key
- * again in the key dictionary to obtain the value object. */
- if (server.maxmemory_policy != MAXMEMORY_VOLATILE_TTL) {
- if (sampledict != keydict) de = dictFind(keydict, key);
- o = dictGetVal(de);
- }
-
- /* Calculate the idle time according to the policy. This is called
- * idle just because the code initially handled LRU, but is in fact
- * just a score where an higher score means better candidate. */
- if (server.maxmemory_policy & MAXMEMORY_FLAG_LRU) {
- idle = estimateObjectIdleTime(o);
- } else if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
- /* When we use an LRU policy, we sort the keys by idle time
- * so that we expire keys starting from greater idle time.
- * However when the policy is an LFU one, we have a frequency
- * estimation, and we want to evict keys with lower frequency
- * first. So inside the pool we put objects using the inverted
- * frequency subtracting the actual frequency to the maximum
- * frequency of 255. */
- idle = 255-LFUDecrAndReturn(o);
- } else if (server.maxmemory_policy == MAXMEMORY_VOLATILE_TTL) {
- /* In this case the sooner the expire the better. */
- idle = ULLONG_MAX - (long)dictGetVal(de);
- } else {
- serverPanic("Unknown eviction policy in evictionPoolPopulate()");
- }
-
- /* Insert the element inside the pool.
- * First, find the first empty bucket or the first populated
- * bucket that has an idle time smaller than our idle time. */
- k = 0;
- while (k < EVPOOL_SIZE &&
- pool[k].key &&
- pool[k].idle < idle) k++;
- if (k == 0 && pool[EVPOOL_SIZE-1].key != NULL) {
- /* Can't insert if the element is < the worst element we have
- * and there are no empty buckets. */
- continue;
- } else if (k < EVPOOL_SIZE && pool[k].key == NULL) {
- /* Inserting into empty position. No setup needed before insert. */
- } else {
- /* Inserting in the middle. Now k points to the first element
- * greater than the element to insert. */
- if (pool[EVPOOL_SIZE-1].key == NULL) {
- /* Free space on the right? Insert at k shifting
- * all the elements from k to end to the right. */
-
- /* Save SDS before overwriting. */
- sds cached = pool[EVPOOL_SIZE-1].cached;
- memmove(pool+k+1,pool+k,
- sizeof(pool[0])*(EVPOOL_SIZE-k-1));
- pool[k].cached = cached;
- } else {
- /* No free space on right? Insert at k-1 */
- k--;
- /* Shift all elements on the left of k (included) to the
- * left, so we discard the element with smaller idle time. */
- sds cached = pool[0].cached; /* Save SDS before overwriting. */
- if (pool[0].key != pool[0].cached) sdsfree(pool[0].key);
- memmove(pool,pool+1,sizeof(pool[0])*k);
- pool[k].cached = cached;
- }
- }
-
- /* Try to reuse the cached SDS string allocated in the pool entry,
- * because allocating and deallocating this object is costly
- * (according to the profiler, not my fantasy. Remember:
- * premature optimization bla bla bla. */
- int klen = sdslen(key);
- if (klen > EVPOOL_CACHED_SDS_SIZE) {
- pool[k].key = sdsdup(key);
- } else {
- memcpy(pool[k].cached,key,klen+1);
- sdssetlen(pool[k].cached,klen);
- pool[k].key = pool[k].cached;
- }
- pool[k].idle = idle;
- pool[k].dbid = dbid;
- }
- }