TranslatorCache.cs 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. using System.Collections.Concurrent;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.Runtime.CompilerServices;
  5. using System.Threading;
  6. namespace ChocolArm64.Translation
  7. {
  8. class TranslatorCache
  9. {
  10. // Maximum size of the cache, the unit used is completely arbitrary.
  11. private const int MaxTotalSize = 0x800000;
  12. // Minimum time required in milliseconds for a method to be eligible for deletion.
  13. private const int MinTimeDelta = 2 * 60000;
  14. // Minimum number of calls required to update the timestamp.
  15. private const int MinCallCountForUpdate = 250;
  16. private class CacheBucket
  17. {
  18. public TranslatedSub Subroutine { get; private set; }
  19. public LinkedListNode<long> Node { get; private set; }
  20. public int CallCount { get; set; }
  21. public int Size { get; private set; }
  22. public long Timestamp { get; private set; }
  23. public CacheBucket(TranslatedSub subroutine, LinkedListNode<long> node, int size)
  24. {
  25. Subroutine = subroutine;
  26. Size = size;
  27. UpdateNode(node);
  28. }
  29. public void UpdateNode(LinkedListNode<long> node)
  30. {
  31. Node = node;
  32. Timestamp = GetTimestamp();
  33. }
  34. }
  35. private ConcurrentDictionary<long, CacheBucket> _cache;
  36. private LinkedList<long> _sortedCache;
  37. private int _totalSize;
  38. public TranslatorCache()
  39. {
  40. _cache = new ConcurrentDictionary<long, CacheBucket>();
  41. _sortedCache = new LinkedList<long>();
  42. }
  43. public TranslatedSub GetOrAdd(long position, TranslatedSub subroutine, int size)
  44. {
  45. ClearCacheIfNeeded();
  46. lock (_sortedCache)
  47. {
  48. LinkedListNode<long> node = _sortedCache.AddLast(position);
  49. CacheBucket bucket = new CacheBucket(subroutine, node, size);
  50. bucket = _cache.GetOrAdd(position, bucket);
  51. if (bucket.Node == node)
  52. {
  53. _totalSize += size;
  54. }
  55. else
  56. {
  57. _sortedCache.Remove(node);
  58. }
  59. return bucket.Subroutine;
  60. }
  61. }
  62. public void AddOrUpdate(long position, TranslatedSub subroutine, int size)
  63. {
  64. ClearCacheIfNeeded();
  65. lock (_sortedCache)
  66. {
  67. _totalSize += size;
  68. LinkedListNode<long> node = _sortedCache.AddLast(position);
  69. CacheBucket newBucket = new CacheBucket(subroutine, node, size);
  70. _cache.AddOrUpdate(position, newBucket, (key, bucket) =>
  71. {
  72. _totalSize -= bucket.Size;
  73. _sortedCache.Remove(bucket.Node);
  74. return newBucket;
  75. });
  76. }
  77. }
  78. public bool HasSubroutine(long position)
  79. {
  80. return _cache.ContainsKey(position);
  81. }
  82. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  83. public bool TryGetSubroutine(long position, out TranslatedSub subroutine)
  84. {
  85. if (_cache.TryGetValue(position, out CacheBucket bucket))
  86. {
  87. if (bucket.CallCount++ > MinCallCountForUpdate)
  88. {
  89. if (Monitor.TryEnter(_sortedCache))
  90. {
  91. try
  92. {
  93. // The bucket value on the dictionary may have changed between the
  94. // time we get the value from the dictionary, and we acquire the
  95. // lock. So we need to ensure we are working with the latest value,
  96. // we can do that by getting the value again, inside the lock.
  97. if (_cache.TryGetValue(position, out CacheBucket latestBucket))
  98. {
  99. latestBucket.CallCount = 0;
  100. _sortedCache.Remove(latestBucket.Node);
  101. latestBucket.UpdateNode(_sortedCache.AddLast(position));
  102. }
  103. }
  104. finally
  105. {
  106. Monitor.Exit(_sortedCache);
  107. }
  108. }
  109. }
  110. subroutine = bucket.Subroutine;
  111. return true;
  112. }
  113. subroutine = default(TranslatedSub);
  114. return false;
  115. }
  116. private void ClearCacheIfNeeded()
  117. {
  118. long timestamp = GetTimestamp();
  119. while (_totalSize > MaxTotalSize)
  120. {
  121. lock (_sortedCache)
  122. {
  123. LinkedListNode<long> node = _sortedCache.First;
  124. if (node == null)
  125. {
  126. break;
  127. }
  128. CacheBucket bucket = _cache[node.Value];
  129. long timeDelta = timestamp - bucket.Timestamp;
  130. if (timeDelta <= MinTimeDelta)
  131. {
  132. break;
  133. }
  134. if (_cache.TryRemove(node.Value, out bucket))
  135. {
  136. _totalSize -= bucket.Size;
  137. _sortedCache.Remove(bucket.Node);
  138. }
  139. }
  140. }
  141. }
  142. private static long GetTimestamp()
  143. {
  144. long timestamp = Stopwatch.GetTimestamp();
  145. return timestamp / (Stopwatch.Frequency / 1000);
  146. }
  147. }
  148. }