// RecyclerMM {: Egg
Recycling Memory Manager (aka RMM).
Provides high-speed allocation/release of highly-aligned memory
via a segregated storage algorithm (for small and medium blocks)
and a virtual heap (large blocks).
Supports Shared Memory (like ShareMem, but no DLL required).
Copyright 2005 - Creative IT / Eric Grange
Default licensing is GPL, use under MPL can be granted (on request, for free)
for users/companies "supporting" Open Source (purely subjective decision by us)
Implementation Notes:
History :
}
unit RecyclerMM;
interface
{$OPTIMIZATION ON}
{$STACKFRAMES OFF}
{$WRITEABLECONST OFF}
{$BOOLEVAL OFF}
{$ifdef VER150} // of course it's "unsafe", so no warnings plz
{$WARN UNSAFE_CODE OFF}
{$WARN UNSAFE_TYPE OFF}
{$endif}
// No debug info, so the debugger won't step through memory management code
{.$D-}
uses Windows;
// if set the RecyclerMM will automatically bind itself as default memory manager
{$define AUTO_BIND}
// If set, RecyclerMM will automatically locate and share memory with other
// RecyclerMMs in DLL modules (same functionality as Borland's ShareMem unit).
// Sharing will only happen with compatible RMMs.
{.$define SHARE_MEM}
// If set the (possible) BPLs won't be patched, only the jump table will
{.$define NO_BPL_PATCHING}
// If set SSE code for Move16/Clear16 will be allowed
// Mileage on the efficiency of SSE over the FPU-based transfer may vary,
// you may want to test it and figure out which is best in your case. Typically,
// the FPU approach will be good for lots of small or scattered blocks on AMD
// CPUs, while SSE shines on large blocks with a P4
{$define ALLOW_SSE}
// If set and exception will be explicitly raised if your code attempts
// to release a block that isn't allocated. By default, RMM only detects
// that issue reliably for large blocks and signals the issue to the Borland RTL,
// which may then raise an exception. But in some circumstances, the RTL will
// just ignore the issue. When the option is active, RMM will accurately detect
// this issue all the time, and trigger an exception itself.
// Doing so incurs a performance penalty on block release, and should preferably
// only be used for testing or if memory integrity is of primary importance
{$define RAISE_EXCEPTION_ON_INVALID_RELEASE}
// Delayed release affects the management of the pool of free memory retained
// by the manager. In delayed mode, 1/4th of the freed blocks are returned
// to the OS every 250 ms, with by default up to 64 MB of memory whose release
// is delayed. In non-delayed mode, the pool is fully retained all the time
// (but the default pool size is much smaller at 8 MB)
// This can improve performance as blocks will linger a bit before being
// returned to the OS, but can temporarily increase memory consumption
// (should be harmless most of the time, as the memory is still usable
// by your application, just not usable for other applications within
// a small delay).
// This option is automatically turned off in DLLs
{$define ALLOW_DELAYED_RELEASE}
// If set usage of memory mapped files for large blocks will be allowed,
// this can have a significant performance impact on frequently reallocated
// large blocks, as it bypasses most of the copy on reallocation.
// However, as it stresses the file system, it may exhibit performance side
// effects if the application allocates a very large number of large blocks.
// Note that memorymapping will only be used for reallocated blocks, there is
// thus no penalty for statically allocated large blocks.
{$define ALLOW_MEMORYMAPPED_LARGE_BLOCKS}
// If set RMMUsageSnapShot functions will be available.
// These functions allow generating a diagnostic and memory map report
{.$define ALLOW_USAGE_SNAPSHOT}
// Selection of the locking mechanim
// If set, Windows CriticalSections will be used, otherwise it will be SpinLocks
{.$define USE_CRITICAL_SECTIONS}
// compile error when incompatible options have been selected
{$ifdef SHARE_MEM}
{$ifdef ALLOW_DELAYED_RELEASE}
Error : you cannot combine ALLOW_DELAYED_RELEASE and SHARE_MEM (yet)
{$endif}
{$endif}
const
// Ratio for ReallocDownSizing (4 = downsizing will happen if only 1/4 used)
cSMBReallocDownSizing = 4;
cLGBReallocDownSizing = 4;
// Ratio for upsizing (1 = allocate only what's needed, 2 = allocate twice the
// needed space, etc. Must be >= 1.0 or things will go banana )
cReallocUpSizing = 1.1;
cReallocUpSizing256 = Word(Round(cReallocUpSizing*256)); // what's actualy used internally
cReallocUpSizingLimit = Cardinal(1 shl 31) div cReallocUpSizing256;
cReallocUpSizingLGBLimit= Round((1 shl 30)/cReallocUpSizing);
cReallocUpSizingSMBPad = 48;
cReallocMinSize = 64;
// Size and Index limits for SMBs
cSMBMaxSizeIndex = 49;
cSMBSizes : packed array [0..cSMBMaxSizeIndex] of Word = (
// 52 values from an exponential curve manually adjusted to "look nice" :)
16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224,
240, 256, 288, 320, 384, 432, 496, 576, 656, 752, 864, 976, 1120,
1280, 1472, 1712, 2032, 2256, 2512, 2848, 3264, 3840, 4352, 4672, 5024,
5456, 5952, 6544, 7264, 8176, 9344, 10912, 13088, 16368, 21824, 32752 );
// Maximum Size (bytes) of blocks managed by SMBs (max 64kB)
cSMBMaxSize = 32752;
// Size of chunks to retrieve from the OS
cOSChunkSize = 1024*1024; // 640 kB should be enough for everyboy, no?
cOSChunkRandomOffset = 4096; // max size of random offset
cOSChunkItemSize = 65536;
cOSChunkBlockCount = (cOSChunkSize-cOSChunkRandomOffset) div cOSChunkItemSize;
// Amount of memory who's delayed release of the next seconds is tolerated
cOSDelayedAllowedMemoryLatency = 8*1024*1024; // 8 MB
cOSDelayedAllowedChunksLatency = cOSDelayedAllowedMemoryLatency div cOSChunkSize;
{$ifdef ALLOW_MEMORYMAPPED_LARGE_BLOCKS}
// minimal size of LGB before memory mapping mode is allowed to kick in
cMemoryMappedLargeBlocksMinSize = 1024*1024;
{$endif}
type
// TRMMStatus
//
TRMMStatus = (rmmsUnallocated, rmmsAllocated, rmmsReserved,
rmmsSysAllocated, rmmsSysReserved);
// TRMMMemoryMap
//
{: Describes a 64 kB range of the RMM memory use.
This structure isn't used by RMM itself, it's used to report the status of the memory allocation in RMMUsageSnapShot. } TRMMMemoryMap = packed record StartAddr : Pointer; // Start of address range Length : Cardinal; // Length of address range (bytes) AllocatedUserSize : Cardinal; // Bytes in range allocated by user Status : TRMMStatus; // Status of address range end; PRMMMemoryMap = ^TRMMMemoryMap; // TRMMSMBStat // TRMMSMBStat = packed record BlockSize : Cardinal; AllocatedBlocks : Cardinal; AllocatedUserSize : Cardinal; end; PRMMSMBStat = ^TRMMSMBStat; const // As a constant, we make our mem map big enough to support 3GB addressing // If you really need the extra 64 kB and know that your code will never be // run in /3GB mode then you can reduce this to 32767. // The actual limit of the memmap is in vMemMapUpper. cMemMapUpperMax = 49151; // Now the two we chose between to use as the actual limit within the array cMemMapUpper2GB = 32767; cMemMapUpper3GB = 49151; type // TRMMUsageBench // TRMMUsageBench = packed record TotalTime : Int64; // in CPU ticks! NbCalls : Cardinal; end; {$ifdef ALLOW_USAGE_SNAPSHOT} // TRMMUsageSnapShot // {: RMM usage diagnostic snapshot, returned by RMMUsageSnapShot. } TRMMUsageSnapShot = packed record // RMM Stats TotalVirtualAllocated : Cardinal; AllocatedBlocks : Cardinal; AllocatedUserSize : Cardinal; // Virtual Memory Stats TotalVMSpace : Cardinal; SystemAllocatedVM : Cardinal; SystemReservedVM : Cardinal; LargestFreeVM : Cardinal; // Map NbMapItems : Cardinal; Map : packed array [0..cMemMapUpperMax] of TRMMMemoryMap; SMBStats : packed array [0..cSMBMaxSizeIndex] of TRMMSMBStat; // Usage BenchRGetMem : TRMMUsageBench; BenchRReallocMem : TRMMUsageBench; BenchRFreeMem : TRMMUsageBench; end; PRMMUsageSnapShot = ^TRMMUsageSnapShot; {$endif} {: Fast 16 bytes-based move.
Copies blocks of 16 bytes only, ie. Count is rounded up to the nearest multiple of 16. Overlapping source/destination are not handled. } var Move16 : procedure (const Source; var Dest; Count: Integer); register; {: Fills an area whose size is a multiple of 16-bytes with zeros.
Count is rounded up to the nearest multiple of 16 } var MemClear16 : procedure (const Buffer; Count: Integer); register; // Direct access functions - only for single .EXE with no RMM DLLs function RGetMem(Size : Integer) : Pointer; function RAllocMem(Size : Cardinal) : Pointer; function RFreeMem(P : Pointer) : Integer; function RReallocMem(P : Pointer; Size : Cardinal) : Pointer; function RAllocated(const P : Pointer) : Boolean; {: True if P points to the beginning of an allocated block.
} var Allocated : function (const P : Pointer) : Boolean; {: Generates a memory map of RMM memory usage.
While the map is generated, all RMM activity is freezed. } {$ifdef ALLOW_USAGE_SNAPSHOT} function RMMUsageSnapShot : TRMMUsageSnapShot; overload; procedure RMMUsageSnapShot(var result : TRMMUsageSnapShot); overload; {$endif} procedure BindRMM; procedure UnBindRMM; function RMMActive : Boolean; procedure InitializeRMM; procedure FinalizeRMM; function RunningIn3GBMode : Boolean; var // Number of entries in memmap array vMemMapUpper : Cardinal; // Virtual memory limit (used for SECURE_MEMMAP) vVirtualLimit : Cardinal; const // Unused, this is just to have it in clear in the DCU cRecyclerMMCopyright = 'RecyclerMM - ©2005 Creative IT'; // ------------------------------------------------------------------ // ------------------------------------------------------------------ // ------------------------------------------------------------------ implementation // ------------------------------------------------------------------ // ------------------------------------------------------------------ // ------------------------------------------------------------------ const cMAX_PATH = 512; cBAADFOOD = $BAADF00D; type PPointer = ^Pointer; TPointerArrayMap = packed array [0..cMemMapUpperMax] of Pointer; TWordArray = packed array [0..MaxInt shr 2] of Word; PWordArray = ^TWordArray; TCardinalArray = packed array [0..MaxInt shr 3] of Cardinal; PCardinalArray = ^TCardinalArray; PSMBManager = ^TSMBManager; POSChunk = ^TOSChunk; {$ifdef USE_CRITICAL_SECTIONS} TCSLock = TRTLCriticalSection; {$else} TCSLock = LongBool; {$endif} PCSLock = ^TCSLock; TMemoryRange = packed record Start : Pointer; Length : Cardinal; end; // TSMBLinkedList // TSMBLinkedList = packed record First, Last : PSMBManager; end; // TSMBInfo // {: SmallBlock management info for a given size.
} TSMBInfo = packed record CSLock : TCSLock; FreeSMBs : TSMBLinkedList; FullSMBs : TSMBLinkedList; Size : Cardinal; BlocksPerSMB : Cardinal; DownSizingSize : Cardinal; end; PSMBInfo = ^TSMBInfo; // TSMBManager // {: Manages a Small Blocks chunk.
Small blocks manage many user blocks of constant (BlockSize) size, which are allocated/freed in a stack-like fashion. } TSMBManager = packed record SMBInfo : PSMBInfo; // pointer to the SMBInfo (size related) NbFreeBlocks : Cardinal; FirstFreedBlock : Pointer; Next, Prev : PSMBManager; // pointer to the next/prev managers MaxNbFreeBlocks : Cardinal; BlockSize : Cardinal; // Size of blocks in SMB BlockStart : Pointer; // base address for SMB blocks DownSizingSize : Cardinal; NextNonAllocatedBlkID : Pointer; Padding : packed array [1..6] of Cardinal; end; // TLGBManager // {: Manages a Large Block.
LGBs each manage a single user-allocated block. They are allowed to reserve address space (to improve the chances of in-place growth). } PLGBManager = ^TLGBManager; TLGBManager = record BlockSize : Cardinal; // Total allocated size for the block DataStart : Pointer; // Start of user data DataSize : Cardinal; // Size requested by the user MaxDataSize : Cardinal; // Maximum size without reallocation Next, Prev : PLGBManager; hFile, hMapping : Cardinal;// handles for memory mapping end; // TOSChunk // {: A range of heap-managed SMB or small LGB space } TOSChunk = packed record Prev, Next : POSChunk; FreeBlocks : Integer; Full : LongBool; FirstBlock : Cardinal; Manager : packed array [0..cOSChunkBlockCount-1] of TSMBManager; end; // TSharedMemoryManager // {: Extends TMemoryManager to accomodate RMM functions.
This structure is what RMMs cross-refer when sharing memory. }
TSharedMemoryManager = record
MemoryManager : TMemoryManager;
Allocated : function(const P : Pointer) : Boolean;
{$ifdef ALLOW_USAGE_SNAPSHOT}
RMMUsageSnapShot : function : TRMMUsageSnapShot;
{$endif}
end;
PSharedMemoryManager = ^TSharedMemoryManager;
var
// Only the lower 2 or 3 GB are accessible to an application under Win32,
// that's a maximum of 32768 or 49152 blocks which are all mapped by a 128/192 kB array
vRunningIn3GBMode : Boolean;
{$ifdef ALLOW_DELAYED_RELEASE}
// ID of the cleanup thread
vCleanupThreadID : Cardinal;
vCleanupThreadHnd : Cardinal;
vCleanupThreadEvent : Cardinal;
{$endif}
vMemoryMap : TPointerArrayMap;
// Binding variables
vOldMemoryManager : TMemoryManager;
vRMMBound : Integer;
{$ifdef ALLOW_SSE}
vSSESupported : Integer;
{$endif}
// Shared memory variables
vSharedMemoryManager : TSharedMemoryManager;
{$ifdef SHARE_MEM}
vSharedMemory_Data : HWND;
vSharedMemory_DataName : ShortString = '########-RecyclerMM-100'#0;
vSharedMemory_InUse : Boolean;
{$endif}
// Chunks pool
vOSChunksLock : TCSLock;
vOSChunksFirst : POSChunk;
vOSChunksFirstFull : POSChunk;
{$ifdef ALLOW_DELAYED_RELEASE}
vOSChunkNbEntirelyFree : Integer;
{$endif}
// SMB information array by size class (index)
vSMBs : array [0..cSMBMaxSizeIndex] of TSMBInfo;
vSMBSizeToPSMBInfo : packed array [0..(cSMBMaxSize-1) shr 4] of Byte;
// Large blocks are just chained
vLGBManagers : PLGBManager;
vLGBLock : TCSLock;
// Temporary path for memorymapped temp files
vTemporaryFilesPath : array [0..cMAX_PATH] of Char;
// RunningIn3GBMode
//
function RunningIn3GBMode : Boolean;
begin
Result:=vRunningIn3GBMode;
end;
// SwitchToThread logic
//
var vSwitchToThread : procedure; stdcall;
procedure Win9xSwitchToThread; stdcall;
begin
Sleep(0);
end;
procedure InitializeSwitchToThread;
var
hLib : Cardinal;
begin
hLib:=LoadLibrary('Kernel32.dll');
vSwitchToThread:=GetProcAddress(hLib, 'SwitchToThread');
FreeLibrary(hLib);
if not Assigned(vSwitchToThread) then
vSwitchToThread:=@Win9xSwitchToThread;
end;
// RaiseInvalidPtrError
//
procedure RaiseInvalidPtrError;
begin
RunError(204); // Invalid pointer operation
end;
// InitializeCSLock
//
procedure InitializeCSLock(var csLock : TCSLock);
begin
{$ifdef USE_CRITICAL_SECTIONS}
InitializeCriticalSection(csLock);
{$else}
csLock:=False;
{$endif}
end;
// DeleteCSLock
//
procedure DeleteCSLock(var csLock : TCSLock);
begin
{$ifdef USE_CRITICAL_SECTIONS}
DeleteCriticalSection(csLock);
{$endif}
end;
// LockCmpxchg
//
function LockCmpXchg(compareVal, newVal : Byte; anAddress : PByte) : Byte;
// AL = compareVal, dl = newVal, ecx = anAddress
asm
lock cmpxchg [ecx], dl
end;
// CSLockEnter
//
procedure CSLockEnter(var csLock : TCSLock);
{$ifdef USE_CRITICAL_SECTIONS}
begin
if IsMultiThread then
EnterCriticalSection(csLock); //}
{$else}
begin
if IsMultiThread then begin
while LockCmpxchg(0, 1, @csLock)<>0 do begin
vSwitchToThread;
if LockCmpxchg(0, 1, @csLock)=0 then
Break;
Windows.Sleep(10);
end;
end; // }
{asm
cmp byte ptr [IsMultiThread], 0
jz @@LockDone
mov ecx, eax
xor eax, eax
mov dl, 1
lock cmpxchg [ecx], dl
jz @@LockDone
push ebx
mov ebx, ecx
call [vSwitchToThread]
@@LockLoop:
xor eax, eax
mov dl, 1
lock cmpxchg [ebx], dl
jz @@LockEntered
push 10
call Windows.Sleep
jmp @@LockLoop
@@LockEntered:
pop ebx
@@LockDone: //}
{$endif}
end;
// CSLockTryEnter
//
function CSLockTryEnter(var csLock : TCSLock) : Boolean;
begin
{$ifdef USE_CRITICAL_SECTIONS}
Result:=(not IsMultiThread) or (TryEnterCriticalSection(csLock));
{$else}
Result:=(not IsMultiThread) or (LockCmpxchg(0, 1, @csLock)=0);
{$endif}
end;
// CSLockLeave
//
procedure CSLockLeave(var csLock : TCSLock);
begin
{$ifdef USE_CRITICAL_SECTIONS}
if IsMultiThread then
LeaveCriticalSection(csLock);
{$else}
csLock:=False;
{$endif}
end;
// MMRandom
//
var vRandomLast : Cardinal;
function MMRandom : Integer;
begin
vRandomLast:=(3877*vRandomLast+29573) mod 139968;
Result:=vRandomLast xor (vRandomLast shr 8);
end;
// UpdateMemoryMap
//
procedure UpdateMemoryMap(baseAddr : Pointer; size : Cardinal; manager : Pointer);
var
i : Cardinal;
begin
for i:=(Cardinal(baseAddr) shr 16) to ((Cardinal(baseAddr)+size-1) shr 16) do
vMemoryMap[i]:=manager;
end;
// CreateTemporaryFileAndMapping
// returns False if failed
function CreateTemporaryFile(var hFile : Cardinal) : Boolean;
var
tempFileName : array [0..cMAX_PATH] of Char;
begin
GetTempFileName(@vTemporaryFilesPath[0], 'RMM', 0, @tempFileName[0]);
hFile:=Windows.CreateFile(@tempFileName[0], GENERIC_READ or GENERIC_WRITE,
0, nil, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY or FILE_FLAG_DELETE_ON_CLOSE, 0);
Result:=(hFile<>INVALID_HANDLE_VALUE);
end;
// SetTemporaryFileSizeAndRemap
// returns pointer to mapped space, nil if failed
function SetTemporaryFileSizeAndMap(const hFile, newSize : Cardinal;
var hMapping : Cardinal) : Pointer;
begin
Result:=nil;
SetFilePointer(hFile, newSize, nil, FILE_BEGIN);
if SetEndOfFile(hFile) then begin
hMapping:=CreateFileMapping(hFile, nil, PAGE_READWRITE, 0, 0, nil);
if (hMapping<>0) and (hMapping<>ERROR_ALREADY_EXISTS) then begin
Result:=MapViewOfFile(hMapping, FILE_MAP_WRITE, 0, 0, newSize);
if Result=nil then begin
CloseHandle(hMapping);
hMapping:=0;
end;
end;
end;
end;
// CutOutChunk
//
procedure CutOutChunk(chunk : POSChunk);
begin
if chunk.Full then begin
// cut from full
if chunk.Prev=nil then
vOSChunksFirstFull:=chunk.Next
else chunk.Prev.Next:=chunk.Next;
if chunk.Next<>nil then
chunk.Next.Prev:=chunk.Prev;
end else begin
// cut from non-full
if chunk.Prev=nil then
vOSChunksFirst:=chunk.Next
else chunk.Prev.Next:=chunk.Next;
if chunk.Next<>nil then
chunk.Next.Prev:=chunk.Prev;
end;
end;
// DestroyChunk
//
procedure DestroyChunk(chunk : POSChunk);
begin
if chunk.Full then RunError(103);
if chunk.FreeBlocks<>cOSChunkBlockCount then RunError(104);
chunk:=Pointer(Cardinal(chunk) and $FFFF0000);
UpdateMemoryMap(chunk, cOSChunkSize, nil);
VirtualFree(chunk, 0, MEM_RELEASE);
end;
// RMMAllocChunkItem
//
function RMMAllocChunkItem : PSMBManager;
var
chunk : POSChunk;
chunkRandomOffset : Cardinal;
i : Integer;
alignedAddress : Cardinal;
begin
CSLockEnter(vOSChunksLock);
chunk:=vOSChunksFirst;
if chunk=nil then begin
// all chunks full, allocate a new one
chunk:=VirtualAlloc(nil, cOSChunkSize, MEM_COMMIT, PAGE_READWRITE);
if chunk=nil then begin
Result:=nil;
Exit;
end;
// randomize chunk location by up to 4 kB
chunkRandomOffset:=(((cOSChunkRandomOffset div 16)-1) and MMRandom)*16;
while chunkRandomOffset+SizeOf(TOSChunk)>4096 do
Dec(chunkRandomOffset, 16);
// update MM and initialize chunk structure
UpdateMemoryMap(chunk, cOSChunkSize, Pointer(Cardinal(chunk)+chunkRandomOffset));
Inc(Cardinal(chunk), chunkRandomOffset);
chunk.FreeBlocks:=cOSChunkBlockCount;
alignedAddress:=(Cardinal(chunk)+SizeOf(TOSChunk)+15) and $FFFFFFF0;
chunk.FirstBlock:=alignedAddress;
for i:=0 to cOSChunkBlockCount-1 do
chunk.Manager[i].BlockStart:=nil;
// place in linked list
chunk.Prev:=nil;
chunk.Next:=vOSChunksFirst;
if vOSChunksFirst<>nil then
vOSChunksFirst.Prev:=chunk;
vOSChunksFirst:=chunk;
end;
i:=0;
while chunk.Manager[i].BlockStart<>nil do Inc(i);
Result:=@chunk.Manager[i];
Result.BlockStart:=Pointer(chunk.FirstBlock+Cardinal(i)*cOSChunkItemSize);
Dec(chunk.FreeBlocks);
// if we filled this one up, move it to the full chunks
if chunk.FreeBlocks=0 then begin
// cut from non-full
CutOutChunk(chunk);
// paste to full
chunk.Full:=True;
chunk.Next:=vOSChunksFirstFull;
if vOSChunksFirstFull<>nil then
vOSChunksFirstFull.Prev:=chunk;
vOSChunksFirstFull:=chunk;
chunk.Prev:=nil;
end;
CSLockLeave(vOSChunksLock);
end;
// RMMVirtualFreeChunkItem
//
procedure RMMVirtualFreeChunkItem(p : Pointer);
var
i : Integer;
chunk : POSChunk;
begin
CSLockEnter(vOSChunksLock);
// identify the chunk for the pointer
chunk:=POSChunk(vMemoryMap[Cardinal(p) shr 16]);
if (Cardinal(chunk) and 1)<>0 then
RaiseInvalidPtrError;
chunk:=POSChunk(Cardinal(Chunk) and $FFFFFFF0);
// release
i:=(Cardinal(p)-chunk.FirstBlock) div cOSChunkItemSize;
chunk.Manager[i].BlockStart:=nil;
Inc(chunk.FreeBlocks);
if chunk.Full then begin
// we're no longer full, cut from full
CutOutChunk(chunk);
// paste to non-full
chunk.Full:=False;
chunk.Next:=vOSChunksFirst;
if vOSChunksFirst<>nil then
vOSChunksFirst.Prev:=chunk;
vOSChunksFirst:=chunk;
chunk.Prev:=nil;
end;
// if completely freed and not the only chunk, cleanup
if (chunk.FreeBlocks=cOSChunkBlockCount) then begin
{$ifndef ALLOW_DELAYED_RELEASE}
if (chunk.Prev<>nil) or (chunk.Next<>nil) then begin
CutOutChunk(chunk);
DestroyChunk(chunk);
end;
{$else}
Inc(vOSChunkNbEntirelyFree);
if vOSChunkNbEntirelyFree>cOSDelayedAllowedChunksLatency then begin
if vCleanupThreadID<>0 then
SetEvent(vCleanupThreadEvent);
end;
{$endif}
end;
CSLockLeave(vOSChunksLock);
end;
// RMMVirtualAlloc
//
function RMMVirtualAlloc(const blkSize : Cardinal) : Pointer;
begin
Result:=VirtualAlloc(nil, blkSize, MEM_COMMIT+MEM_TOP_DOWN, PAGE_READWRITE);
end;
// RMMVirtualFree
//
procedure RMMVirtualFree(p : Pointer; const blkSize : Cardinal);
begin
VirtualFree(p, 0, MEM_RELEASE);
end;
// ComputeLGBBlockSize
//
function ComputeLGBBlockSize(dataSize : Cardinal) : Cardinal;
var
baseOffset : Cardinal;
begin
baseOffset:=(SizeOf(TLGBManager)+15) and $FFFFFFF0;
Result:=((dataSize+baseOffset+$FFFF) and $FFFF0000);
end;
// ComputeLGBDataStart
//
function ComputeLGBDataStart(p : Pointer; blockSize, dataSize : Cardinal) : Pointer;
var
baseOffset, margin, randomOffset, test : Cardinal;
begin
baseOffset:=(SizeOf(TLGBManager)+15) and $FFFFFFF0;
margin:=(blockSize-baseOffset-dataSize);
if margin>$2000 then margin:=$2000; // 8 kB max
test:=0;
repeat
randomOffset:=test;
test:=(test shl 1)+16;
until test>margin;
randomOffset:=randomOffset and MMRandom;
Result:=Pointer(Cardinal(P)+baseOffset+randomOffset);
end;
// AllocateLGB
//
function AllocateLGB(Size : Cardinal) : Pointer;
var
blkSize : Cardinal;
lgbManager : PLGBManager;
begin
blkSize:=ComputeLGBBlockSize(Size);
// Spawn manager, allocate block
lgbManager:=RMMVirtualAlloc(blkSize);
if lgbManager=nil then
Result:=nil
else begin
lgbManager.hFile:=0;
lgbManager.DataSize:=Size;
lgbManager.BlockSize:=blkSize;
lgbManager.DataStart:=ComputeLGBDataStart(lgbManager, blkSize, Size);
lgbManager.MaxDataSize:=blkSize-(Cardinal(lgbManager.DataStart)-Cardinal(lgbManager));
// Add in the LGB linked list
CSLockEnter(vLGBLock);
if vLGBManagers<>nil then
vLGBManagers.Prev:=lgbManager;
lgbManager.Next:=vLGBManagers;
lgbManager.Prev:=nil;
vLGBManagers:=lgbManager;
CSLockLeave(vLGBLock);
UpdateMemoryMap(lgbManager, lgbManager.BlockSize, Pointer(Cardinal(lgbManager)+1));
Result:=lgbManager.DataStart;
end;
end;
// ReleaseLGB
//
procedure ReleaseLGB(aManager : PLGBManager);
var
manager : TLGBManager; // local copy
begin
UpdateMemoryMap(aManager, aManager.BlockSize, nil);
manager:=aManager^;
// Remove from LGB linked list
CSLockEnter(vLGBLock);
if manager.Prev=nil then
vLGBManagers:=manager.Next
else manager.Prev.Next:=manager.Next;
if manager.Next<>nil then
manager.Next.Prev:=manager.Prev;
CSLockLeave(vLGBLock);
// Free block
{$ifdef ALLOW_MEMORYMAPPED_LARGE_BLOCKS}
if aManager.hFile<>0 then begin
UnmapViewOfFile(aManager);
CloseHandle(manager.hMapping);
CloseHandle(manager.hFile);
end else RMMVirtualFree(aManager, manager.BlockSize);
{$else}
RMMVirtualFree(aManager, manager.BlockSize);
{$endif}
end;
// ReallocateLGB
//
function ReallocateLGB(oldManager : PLGBManager; newSize : Cardinal) : PLGBManager;
var
blkSize, oldDataOffset, copySize : Cardinal;
newManager : PLGBManager;
hFile, hMapping : Cardinal;
needDataTransfer : Boolean;
begin
if (newSize>oldManager.DataSize) and (newSize