Ark Server API (ASA) - Wiki
Loading...
Searching...
No Matches
FPooledVirtualMemoryAllocator Struct Reference

#include <PooledVirtualMemoryAllocator.h>

+ Collaboration diagram for FPooledVirtualMemoryAllocator:

Classes

struct  FPoolDescriptorBase
 

Public Member Functions

 FPooledVirtualMemoryAllocator ()
 
voidAllocate (SIZE_T Size, uint32 AllocationHint=0, FCriticalSection *Mutex=nullptr)
 
void Free (void *Ptr, SIZE_T Size, FCriticalSection *Mutex=nullptr, bool ThreadIsTimeCritical=false)
 
void FreeAll (FCriticalSection *Mutex=nullptr)
 
uint64 GetCachedFreeTotal ()
 
void Refresh ()
 
void UpdateStats ()
 

Private Types

enum  Limits { NumAllocationSizeClasses = 64 , MaxAllocationSizeToPool = NumAllocationSizeClasses * 65536 , MaxOSAllocCacheSize = 64 * 1024 * 1024 , MaxOSAllocsCached = 64 }
 

Private Member Functions

FORCEINLINE int32 GetAllocationSizeClass (SIZE_T Size)
 
FORCEINLINE SIZE_T CalculateAllocationSizeFromClass (int32 Class)
 
void DecideOnTheNextPoolSize (int32 SizeClass, bool bGrowing)
 
FPoolDescriptorBaseCreatePool (SIZE_T AllocationSize, int32 NumPooledAllocations)
 
void DestroyPool (FPoolDescriptorBase *Pool)
 

Private Attributes

int32 NextPoolSize [Limits::NumAllocationSizeClasses]
 
FPoolDescriptorBaseClassesListHeads [Limits::NumAllocationSizeClasses]
 
FCriticalSection ClassesLocks [Limits::NumAllocationSizeClasses]
 
FCriticalSection OsAllocatorCacheLock
 
TCachedOSPageAllocator< MaxOSAllocsCached, MaxOSAllocCacheSizeOsAllocatorCache
 

Detailed Description

This class pools OS allocations made from FMallocBinned2.

The class fulfills FMallocBinned2 requirements of returning a 64KB-aligned address and it also avoids fragmenting the memory into too many small VMAs (virtual memory areas).

The logic is like follows:

There are N buckets that represent allocation sizes from 64KB to N*64 KB. Each bucket is a linked list of pools that hold varied number of same-sized allocations (called blocks). Each bucket starts empty (linked list is empty).

Whenever an allocation request arrives, it is first bucketed based on its size (if it is larger than the largest bucket, it is passed through to a caching OS allocator). Then, the linked list of that bucket is walked, and the allocation is fulfilled by the first pool that has empty "blocks". If there are no such pool, a new pool is created (with number of blocks being possibly larger than in the existing list, if any), this pool becomes the new head of the list and the allocation happens there.

Whenever a free request arrives, it is again first bucketed based on the size (which must be passed in and must match allocation size). If it is larger than the largest bucket, it is passed through to a platform function BinnedFreeToOS(). Otherwise, the appropriate bucket's list of pools is walked to find the pool where the allocation belongs, and the appropriate block becomes free (a platform-specific function is called to evict the memory range from the RAM). If this was the last used block in the pool, the whole pool is destroyed and the list shrinks by one.

So, to visualize an example state:

64KB bucket: Head -> [ A pool of 200 64KB "blocks", of which 50 are empty ] -> [ A pool of 100 64KB blocks, of which 30 are empty ] -> null 128KB bucket: Head -> [ A pool of 60 128KB "blocks", of which 25 are empty ] -> null 192KB bucket: Head -> null 256KB bucket: Head -> [ A pool of 40 256KB "blocks", of which 10 are empty ] -> [ A pool of 20 256KB blocks, of which 10 are emtpy ] -> [ A pool of 4 256 KB blocks of which 0 are empty ] -> null ... 4MB bucket: Head -> [ A pool of 2 4MB "blocks", of which 1 is empty ] -> null

Each pool uses one distinct VMA on Linux (or one distinct VirtualAlloc on Windows).

The class also maintains an idea of what current size of each pool (per bucket) should be. Each time we add a new pool to a particular bucket, this size can grow (exponentially or otherwise). Each time we delete a pool from a particular bucket, this size can shrink. The logic that handles that is in DecideOnTheNextPoolSize().

Right now the class is less multithread friendly than it could be. There are per-bucket locks so allocations of different sizes would not block each other if happening on different threads. It is possible to make allocations within one bucket lock-free as well (e.g. with hazard pointers), but since FMallocBinned2 itself needs a lock to maintain its own structures when making a call here, the point is moot.

This class is somewhat similar to FCachedOSPageAllocator. However, unlike FCOSPA, this is not a cache and we cannot "trim" anything here. Also, it does not make sense to put the global cap on the pooled memory since BinnedAllocFromOS() can support only a limited number of allocations on some platforms.

CachedOSPageAllocator sits "below" this and is used for allocs larger than the largest bucketed.

Definition at line 60 of file PooledVirtualMemoryAllocator.h.

Member Enumeration Documentation

◆ Limits

Enumerator
NumAllocationSizeClasses 
MaxAllocationSizeToPool 
MaxOSAllocCacheSize 
MaxOSAllocsCached 

Definition at line 89 of file PooledVirtualMemoryAllocator.h.

Constructor & Destructor Documentation

◆ FPooledVirtualMemoryAllocator()

FPooledVirtualMemoryAllocator::FPooledVirtualMemoryAllocator ( )

Member Function Documentation

◆ Allocate()

void * FPooledVirtualMemoryAllocator::Allocate ( SIZE_T Size,
uint32 AllocationHint = 0,
FCriticalSection * Mutex = nullptr )

◆ CalculateAllocationSizeFromClass()

FORCEINLINE SIZE_T FPooledVirtualMemoryAllocator::CalculateAllocationSizeFromClass ( int32 Class)
inlineprivate

Returns allocation size for a class.

Definition at line 116 of file PooledVirtualMemoryAllocator.h.

◆ CreatePool()

FPoolDescriptorBase * FPooledVirtualMemoryAllocator::CreatePool ( SIZE_T AllocationSize,
int32 NumPooledAllocations )
private

Allocates a new pool

◆ DecideOnTheNextPoolSize()

void FPooledVirtualMemoryAllocator::DecideOnTheNextPoolSize ( int32 SizeClass,
bool bGrowing )
private

Increases the number of pooled allocations next time we need a pool

Parameters
bGrowing- if true, we are allocating it, if false, we have just deleted a pool of this size

◆ DestroyPool()

void FPooledVirtualMemoryAllocator::DestroyPool ( FPoolDescriptorBase * Pool)
private

Destroys a pool

◆ Free()

void FPooledVirtualMemoryAllocator::Free ( void * Ptr,
SIZE_T Size,
FCriticalSection * Mutex = nullptr,
bool ThreadIsTimeCritical = false )

◆ FreeAll()

void FPooledVirtualMemoryAllocator::FreeAll ( FCriticalSection * Mutex = nullptr)

◆ GetAllocationSizeClass()

FORCEINLINE int32 FPooledVirtualMemoryAllocator::GetAllocationSizeClass ( SIZE_T Size)
inlineprivate

Buckets allocations by its size.

Allocation size class is an index into a number of tables containing per-class elements. Index of 0 (smallest possible) represents allocations of size 65536 or less, index of NumAllocationSizeClasses - 1 (largest possible) represents allocations of size larger or equal than (NumAllocationSizeClasses - 1 * 65536) and smaller than (NumAllocationSizeClasses * 65536).

This function should not be passed 0 as Size!

Definition at line 108 of file PooledVirtualMemoryAllocator.h.

◆ GetCachedFreeTotal()

uint64 FPooledVirtualMemoryAllocator::GetCachedFreeTotal ( )

Returns free memory in the pools

◆ Refresh()

void FPooledVirtualMemoryAllocator::Refresh ( )
inline

Refresh allocator if needed. (does nothing in that implementation)

Definition at line 82 of file PooledVirtualMemoryAllocator.h.

◆ UpdateStats()

void FPooledVirtualMemoryAllocator::UpdateStats ( )
inline

Update memory stats (does nothing in that implementation)

Definition at line 85 of file PooledVirtualMemoryAllocator.h.

Member Data Documentation

◆ ClassesListHeads

FPoolDescriptorBase* FPooledVirtualMemoryAllocator::ClassesListHeads[Limits::NumAllocationSizeClasses]
private

Head of the pool descriptor lists

Definition at line 132 of file PooledVirtualMemoryAllocator.h.

◆ ClassesLocks

FCriticalSection FPooledVirtualMemoryAllocator::ClassesLocks[Limits::NumAllocationSizeClasses]
private

Per-class locks

Definition at line 135 of file PooledVirtualMemoryAllocator.h.

◆ NextPoolSize

int32 FPooledVirtualMemoryAllocator::NextPoolSize[Limits::NumAllocationSizeClasses]
private

We only pool allocations that are divisible by 64KB. Max allocation size is pooled is NumAllocationSizeClasses * 64KB, after which we just allocate directly from the OS.

This array keeps the number of pooled allocations for each allocation size class that we keep bumping up whenever the pool is exhausted.

Definition at line 129 of file PooledVirtualMemoryAllocator.h.

◆ OsAllocatorCache

TCachedOSPageAllocator<MaxOSAllocsCached, MaxOSAllocCacheSize> FPooledVirtualMemoryAllocator::OsAllocatorCache
private

Caching allocator for larger allocs

Definition at line 153 of file PooledVirtualMemoryAllocator.h.

◆ OsAllocatorCacheLock

FCriticalSection FPooledVirtualMemoryAllocator::OsAllocatorCacheLock
private

Lock for accessing the caching allocator for larger allocs

Definition at line 150 of file PooledVirtualMemoryAllocator.h.


The documentation for this struct was generated from the following file: