Ryujinx/src/ARMeilleure/Common/ArenaAllocator.cs
TSRBerry 2989c163a8
editorconfig: Set default encoding to UTF-8 (#5793)
* editorconfig: Add default charset

* Change file encoding from UTF-8-BOM to UTF-8
2023-12-04 14:17:13 +01:00

191 lines
5.0 KiB
C#

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace ARMeilleure.Common
{
unsafe sealed class ArenaAllocator : Allocator
{
private class PageInfo
{
public byte* Pointer;
public byte Unused;
public int UnusedCounter;
}
private int _lastReset;
private ulong _index;
private int _pageIndex;
private PageInfo _page;
private List<PageInfo> _pages;
private readonly ulong _pageSize;
private readonly uint _pageCount;
private readonly List<IntPtr> _extras;
public ArenaAllocator(uint pageSize, uint pageCount)
{
_lastReset = Environment.TickCount;
// Set _index to pageSize so that the first allocation goes through the slow path.
_index = pageSize;
_pageIndex = -1;
_page = null;
_pages = new List<PageInfo>();
_pageSize = pageSize;
_pageCount = pageCount;
_extras = new List<IntPtr>();
}
public Span<T> AllocateSpan<T>(ulong count) where T : unmanaged
{
return new Span<T>(Allocate<T>(count), (int)count);
}
public override void* Allocate(ulong size)
{
if (_index + size <= _pageSize)
{
byte* result = _page.Pointer + _index;
_index += size;
return result;
}
return AllocateSlow(size);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void* AllocateSlow(ulong size)
{
if (size > _pageSize)
{
void* extra = NativeAllocator.Instance.Allocate(size);
_extras.Add((IntPtr)extra);
return extra;
}
if (_index + size > _pageSize)
{
_index = 0;
_pageIndex++;
}
if (_pageIndex < _pages.Count)
{
_page = _pages[_pageIndex];
_page.Unused = 0;
}
else
{
_page = new PageInfo
{
Pointer = (byte*)NativeAllocator.Instance.Allocate(_pageSize),
};
_pages.Add(_page);
}
byte* result = _page.Pointer + _index;
_index += size;
return result;
}
public override void Free(void* block) { }
public void Reset()
{
_index = _pageSize;
_pageIndex = -1;
_page = null;
// Free excess pages that was allocated.
while (_pages.Count > _pageCount)
{
NativeAllocator.Instance.Free(_pages[^1].Pointer);
_pages.RemoveAt(_pages.Count - 1);
}
// Free extra blocks that are not page-sized
foreach (IntPtr ptr in _extras)
{
NativeAllocator.Instance.Free((void*)ptr);
}
_extras.Clear();
// Free pooled pages that has not been used in a while. Remove pages at the back first, because we try to
// keep the pages at the front alive, since they're more likely to be hot and in the d-cache.
bool removing = true;
// If arena is used frequently, keep pages for longer. Otherwise keep pages for a shorter amount of time.
int now = Environment.TickCount;
int count = (now - _lastReset) switch
{
>= 5000 => 0,
>= 2500 => 50,
>= 1000 => 100,
>= 10 => 1500,
_ => 5000,
};
for (int i = _pages.Count - 1; i >= 0; i--)
{
PageInfo page = _pages[i];
if (page.Unused == 0)
{
page.UnusedCounter = 0;
}
page.UnusedCounter += page.Unused;
page.Unused = 1;
// If page not used after `count` resets, remove it.
if (removing && page.UnusedCounter >= count)
{
NativeAllocator.Instance.Free(page.Pointer);
_pages.RemoveAt(i);
}
else
{
removing = false;
}
}
_lastReset = now;
}
protected override void Dispose(bool disposing)
{
if (_pages != null)
{
foreach (PageInfo info in _pages)
{
NativeAllocator.Instance.Free(info.Pointer);
}
foreach (IntPtr ptr in _extras)
{
NativeAllocator.Instance.Free((void*)ptr);
}
_pages = null;
}
}
~ArenaAllocator()
{
Dispose(false);
}
}
}