ArenaAllocator.cs 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Runtime.CompilerServices;
  4. using System.Threading;
  5. namespace ARMeilleure.Common
  6. {
  7. unsafe sealed class ArenaAllocator : Allocator
  8. {
  9. private class PageInfo
  10. {
  11. public byte* Pointer;
  12. public byte Unused;
  13. public int UnusedCounter;
  14. }
  15. private int _lastReset;
  16. private ulong _index;
  17. private int _pageIndex;
  18. private PageInfo _page;
  19. private List<PageInfo> _pages;
  20. private readonly ulong _pageSize;
  21. private readonly uint _pageCount;
  22. private readonly List<IntPtr> _extras;
  23. public ArenaAllocator(uint pageSize, uint pageCount)
  24. {
  25. _lastReset = Environment.TickCount;
  26. // Set _index to pageSize so that the first allocation goes through the slow path.
  27. _index = pageSize;
  28. _pageIndex = -1;
  29. _page = null;
  30. _pages = new List<PageInfo>();
  31. _pageSize = pageSize;
  32. _pageCount = pageCount;
  33. _extras = new List<IntPtr>();
  34. }
  35. public Span<T> AllocateSpan<T>(ulong count) where T : unmanaged
  36. {
  37. return new Span<T>(Allocate<T>(count), (int)count);
  38. }
  39. public override void* Allocate(ulong size)
  40. {
  41. if (_index + size <= _pageSize)
  42. {
  43. byte* result = _page.Pointer + _index;
  44. _index += size;
  45. return result;
  46. }
  47. return AllocateSlow(size);
  48. }
  49. [MethodImpl(MethodImplOptions.NoInlining)]
  50. private void* AllocateSlow(ulong size)
  51. {
  52. if (size > _pageSize)
  53. {
  54. void* extra = NativeAllocator.Instance.Allocate(size);
  55. _extras.Add((IntPtr)extra);
  56. return extra;
  57. }
  58. if (_index + size > _pageSize)
  59. {
  60. _index = 0;
  61. _pageIndex++;
  62. }
  63. if (_pageIndex < _pages.Count)
  64. {
  65. _page = _pages[_pageIndex];
  66. _page.Unused = 0;
  67. }
  68. else
  69. {
  70. _page = new PageInfo();
  71. _page.Pointer = (byte*)NativeAllocator.Instance.Allocate(_pageSize);
  72. _pages.Add(_page);
  73. }
  74. byte* result = _page.Pointer + _index;
  75. _index += size;
  76. return result;
  77. }
  78. public override void Free(void* block) { }
  79. public void Reset()
  80. {
  81. _index = _pageSize;
  82. _pageIndex = -1;
  83. _page = null;
  84. // Free excess pages that was allocated.
  85. while (_pages.Count > _pageCount)
  86. {
  87. NativeAllocator.Instance.Free(_pages[_pages.Count - 1].Pointer);
  88. _pages.RemoveAt(_pages.Count - 1);
  89. }
  90. // Free extra blocks that are not page-sized
  91. foreach (IntPtr ptr in _extras)
  92. {
  93. NativeAllocator.Instance.Free((void*)ptr);
  94. }
  95. _extras.Clear();
  96. // Free pooled pages that has not been used in a while. Remove pages at the back first, because we try to
  97. // keep the pages at the front alive, since they're more likely to be hot and in the d-cache.
  98. bool removing = true;
  99. // If arena is used frequently, keep pages for longer. Otherwise keep pages for a shorter amount of time.
  100. int now = Environment.TickCount;
  101. int count = (now - _lastReset) switch {
  102. >= 5000 => 0,
  103. >= 2500 => 50,
  104. >= 1000 => 100,
  105. >= 10 => 1500,
  106. _ => 5000
  107. };
  108. for (int i = _pages.Count - 1; i >= 0; i--)
  109. {
  110. PageInfo page = _pages[i];
  111. if (page.Unused == 0)
  112. {
  113. page.UnusedCounter = 0;
  114. }
  115. page.UnusedCounter += page.Unused;
  116. page.Unused = 1;
  117. // If page not used after `count` resets, remove it.
  118. if (removing && page.UnusedCounter >= count)
  119. {
  120. NativeAllocator.Instance.Free(page.Pointer);
  121. _pages.RemoveAt(i);
  122. }
  123. else
  124. {
  125. removing = false;
  126. }
  127. }
  128. _lastReset = now;
  129. }
  130. protected override void Dispose(bool disposing)
  131. {
  132. if (_pages != null)
  133. {
  134. foreach (PageInfo info in _pages)
  135. {
  136. NativeAllocator.Instance.Free(info.Pointer);
  137. }
  138. foreach (IntPtr ptr in _extras)
  139. {
  140. NativeAllocator.Instance.Free((void*)ptr);
  141. }
  142. _pages = null;
  143. }
  144. }
  145. ~ArenaAllocator()
  146. {
  147. Dispose(false);
  148. }
  149. }
  150. }