2018-10-31 02:43:02 +01:00
|
|
|
using System.Collections.Concurrent;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Diagnostics;
|
|
|
|
using System.Runtime.CompilerServices;
|
|
|
|
using System.Threading;
|
|
|
|
|
2019-02-04 22:26:05 +01:00
|
|
|
namespace ChocolArm64.Translation
|
2018-10-31 02:43:02 +01:00
|
|
|
{
|
|
|
|
class TranslatorCache
|
|
|
|
{
|
2019-07-02 04:39:22 +02:00
|
|
|
// Maximum size of the cache, the unit used is completely arbitrary.
|
2018-12-11 01:58:52 +01:00
|
|
|
private const int MaxTotalSize = 0x800000;
|
2018-10-31 02:43:02 +01:00
|
|
|
|
2019-07-02 04:39:22 +02:00
|
|
|
// Minimum time required in milliseconds for a method to be eligible for deletion.
|
2018-10-31 02:43:02 +01:00
|
|
|
private const int MinTimeDelta = 2 * 60000;
|
|
|
|
|
2019-07-02 04:39:22 +02:00
|
|
|
// Minimum number of calls required to update the timestamp.
|
2018-10-31 02:43:02 +01:00
|
|
|
private const int MinCallCountForUpdate = 250;
|
|
|
|
|
|
|
|
private class CacheBucket
|
|
|
|
{
|
|
|
|
public TranslatedSub Subroutine { get; private set; }
|
|
|
|
|
|
|
|
public LinkedListNode<long> Node { get; private set; }
|
|
|
|
|
|
|
|
public int CallCount { get; set; }
|
|
|
|
|
|
|
|
public int Size { get; private set; }
|
|
|
|
|
|
|
|
public long Timestamp { get; private set; }
|
|
|
|
|
|
|
|
public CacheBucket(TranslatedSub subroutine, LinkedListNode<long> node, int size)
|
|
|
|
{
|
|
|
|
Subroutine = subroutine;
|
|
|
|
Size = size;
|
|
|
|
|
|
|
|
UpdateNode(node);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void UpdateNode(LinkedListNode<long> node)
|
|
|
|
{
|
|
|
|
Node = node;
|
|
|
|
|
|
|
|
Timestamp = GetTimestamp();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private ConcurrentDictionary<long, CacheBucket> _cache;
|
|
|
|
|
|
|
|
private LinkedList<long> _sortedCache;
|
|
|
|
|
|
|
|
private int _totalSize;
|
|
|
|
|
|
|
|
public TranslatorCache()
|
|
|
|
{
|
|
|
|
_cache = new ConcurrentDictionary<long, CacheBucket>();
|
|
|
|
|
|
|
|
_sortedCache = new LinkedList<long>();
|
|
|
|
}
|
|
|
|
|
2019-02-04 22:26:05 +01:00
|
|
|
public TranslatedSub GetOrAdd(long position, TranslatedSub subroutine, int size)
|
|
|
|
{
|
|
|
|
ClearCacheIfNeeded();
|
|
|
|
|
|
|
|
lock (_sortedCache)
|
|
|
|
{
|
|
|
|
LinkedListNode<long> node = _sortedCache.AddLast(position);
|
|
|
|
|
|
|
|
CacheBucket bucket = new CacheBucket(subroutine, node, size);
|
|
|
|
|
|
|
|
bucket = _cache.GetOrAdd(position, bucket);
|
|
|
|
|
|
|
|
if (bucket.Node == node)
|
|
|
|
{
|
|
|
|
_totalSize += size;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_sortedCache.Remove(node);
|
|
|
|
}
|
|
|
|
|
|
|
|
return bucket.Subroutine;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-31 02:43:02 +01:00
|
|
|
public void AddOrUpdate(long position, TranslatedSub subroutine, int size)
|
|
|
|
{
|
|
|
|
ClearCacheIfNeeded();
|
|
|
|
|
|
|
|
lock (_sortedCache)
|
|
|
|
{
|
2018-12-11 01:58:52 +01:00
|
|
|
_totalSize += size;
|
|
|
|
|
2018-10-31 02:43:02 +01:00
|
|
|
LinkedListNode<long> node = _sortedCache.AddLast(position);
|
|
|
|
|
|
|
|
CacheBucket newBucket = new CacheBucket(subroutine, node, size);
|
|
|
|
|
|
|
|
_cache.AddOrUpdate(position, newBucket, (key, bucket) =>
|
|
|
|
{
|
|
|
|
_totalSize -= bucket.Size;
|
|
|
|
|
|
|
|
_sortedCache.Remove(bucket.Node);
|
|
|
|
|
|
|
|
return newBucket;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public bool HasSubroutine(long position)
|
|
|
|
{
|
|
|
|
return _cache.ContainsKey(position);
|
|
|
|
}
|
|
|
|
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
|
|
public bool TryGetSubroutine(long position, out TranslatedSub subroutine)
|
|
|
|
{
|
|
|
|
if (_cache.TryGetValue(position, out CacheBucket bucket))
|
|
|
|
{
|
|
|
|
if (bucket.CallCount++ > MinCallCountForUpdate)
|
|
|
|
{
|
|
|
|
if (Monitor.TryEnter(_sortedCache))
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
2019-07-02 04:39:22 +02:00
|
|
|
// The bucket value on the dictionary may have changed between the
|
|
|
|
// time we get the value from the dictionary, and we acquire the
|
|
|
|
// lock. So we need to ensure we are working with the latest value,
|
|
|
|
// we can do that by getting the value again, inside the lock.
|
2018-12-11 01:58:52 +01:00
|
|
|
if (_cache.TryGetValue(position, out CacheBucket latestBucket))
|
|
|
|
{
|
|
|
|
latestBucket.CallCount = 0;
|
|
|
|
|
|
|
|
_sortedCache.Remove(latestBucket.Node);
|
|
|
|
|
|
|
|
latestBucket.UpdateNode(_sortedCache.AddLast(position));
|
|
|
|
}
|
2018-10-31 02:43:02 +01:00
|
|
|
}
|
|
|
|
finally
|
|
|
|
{
|
|
|
|
Monitor.Exit(_sortedCache);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
subroutine = bucket.Subroutine;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
subroutine = default(TranslatedSub);
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void ClearCacheIfNeeded()
|
|
|
|
{
|
|
|
|
long timestamp = GetTimestamp();
|
|
|
|
|
|
|
|
while (_totalSize > MaxTotalSize)
|
|
|
|
{
|
|
|
|
lock (_sortedCache)
|
|
|
|
{
|
|
|
|
LinkedListNode<long> node = _sortedCache.First;
|
|
|
|
|
|
|
|
if (node == null)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
CacheBucket bucket = _cache[node.Value];
|
|
|
|
|
|
|
|
long timeDelta = timestamp - bucket.Timestamp;
|
|
|
|
|
|
|
|
if (timeDelta <= MinTimeDelta)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_cache.TryRemove(node.Value, out bucket))
|
|
|
|
{
|
|
|
|
_totalSize -= bucket.Size;
|
|
|
|
|
|
|
|
_sortedCache.Remove(bucket.Node);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static long GetTimestamp()
|
|
|
|
{
|
|
|
|
long timestamp = Stopwatch.GetTimestamp();
|
|
|
|
|
|
|
|
return timestamp / (Stopwatch.Frequency / 1000);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|