Skip to content

Instantly share code, notes, and snippets.

@eladmarg
Created September 28, 2016 20:28
Show Gist options
  • Save eladmarg/8d4a7f7a43a36c35d488e99475a101d9 to your computer and use it in GitHub Desktop.
Save eladmarg/8d4a7f7a43a36c35d488e99475a101d9 to your computer and use it in GitHub Desktop.
LRUCache C# Implementation
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace Utils
{
public class GenericLRUCache<TV, TU> where TU : class
{
private readonly int CacheMaxSize;
private readonly int CleanSize;
private readonly TimeSpan MaxDuration;
private readonly ConcurrentDictionary<TV, CacheDataObject<TU>> _cache = new ConcurrentDictionary<TV, CacheDataObject<TU>>();
public GenericLRUCache(int maxSize = 50000, int cleanPercentage = 30, TimeSpan maxDuration = default(TimeSpan))
{
CacheMaxSize = maxSize;
CleanSize = (int)Math.Max(CacheMaxSize * (1.0 * cleanPercentage / 100), 1000);
if (maxDuration == default(TimeSpan))
{
MaxDuration = TimeSpan.FromDays(1);
}
else
{
MaxDuration = maxDuration;
}
}
private static readonly object _lockObject = new object();
private static bool IsCleaning = false;
public bool AddToCache(TV cacheKey, TU value)
{
var cachedResult = new CacheDataObject<TU>
{
Usage = 1,
Value = value,
Timestamp = DateTime.UtcNow
};
_cache.AddOrUpdate(cacheKey, cachedResult, (_, __) => cachedResult);
if (_cache.Count > 0 && _cache.Count > CacheMaxSize && !IsCleaning)
{
lock (_lockObject)
{
if (IsCleaning)
return true;
try
{
IsCleaning = true;
var cacheArray = _cache.ToArray();
if (cacheArray.Length > 0)
{
var itemsToSkip = CacheMaxSize - CleanSize;
if (itemsToSkip > 10)
{
var items = cacheArray.OrderByDescending(x => x.Value.Usage)
.ThenBy(x => x.Value.Timestamp)
.Skip(itemsToSkip);
if (items.Any())
{
foreach (var source in items)
{
if (source.Key == null || cacheKey == null)
continue;
if (EqualityComparer<TV>.Default.Equals(source.Key, cacheKey))
continue; // we don't want to remove the one we just added
CacheDataObject<TU> ignored;
_cache.TryRemove(source.Key, out ignored);
}
}
}
}
}
finally
{
IsCleaning = false;
}
}
}
return true;
}
public TU GetFromCache(TV cacheKey, bool increment = true)
{
CacheDataObject<TU> value;
if (_cache.TryGetValue(cacheKey, out value) && (MaxDuration == TimeSpan.MaxValue || (DateTime.UtcNow - value.Timestamp) <= MaxDuration))
{
if (increment)
{
Interlocked.Increment(ref value.Usage);
}
return value.Value;
}
return null;
}
public TU GetOrAdd(TV cacheKey, Func<TU> aquire, bool increment = true)
{
CacheDataObject<TU> value;
if (_cache.TryGetValue(cacheKey, out value) && (MaxDuration == TimeSpan.MaxValue || (DateTime.UtcNow - value.Timestamp) <= MaxDuration))
{
if (increment)
{
Interlocked.Increment(ref value.Usage);
}
return value.Value;
}
TU val = aquire();
if (val != default(TU))
{
AddToCache(cacheKey, val);
}
return val;
}
public bool TryGetValue(TV cacheKey, out TU val, bool increment = true)
{
CacheDataObject<TU> value;
if (_cache.TryGetValue(cacheKey, out value) && (MaxDuration == TimeSpan.MaxValue || (DateTime.UtcNow - value.Timestamp) <= MaxDuration))
{
if (increment)
{
Interlocked.Increment(ref value.Usage);
}
val = value.Value;
return true;
}
val = default(TU);
return false;
}
public void Remove(TV cacheKey)
{
CacheDataObject<TU> value;
_cache.TryRemove(cacheKey, out value);
}
public bool IsExistInCache(TV cacheKey)
{
return (_cache.ContainsKey(cacheKey));
}
private class CacheDataObject<T> where T : class
{
public DateTime Timestamp;
public int Usage;
public T Value;
}
}
}
@emilsteen
Copy link

This does not look like a LRU cache to me. More like a MU and LRA cache.

@eladmarg
Copy link
Author

yes, this is old code, do not use it.
net core has better mechanism that solves all of this, including update callback policy.
to achieve this create CacheItemPolicy with UpdateCallback, and use the AbsoluteExpiration with your refresh interval.

@emilsteen
Copy link

Quick feedback. =)
Thanks for the honest reply.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment