One main method of the unit is the following:
function TThreadMemManager.GetMem(aSize: NativeUInt): Pointer;
var
bm: PMemBlockList;
begin
if aSize <= (length(FMiniMemoryBlocks)*32) then
if aSize > 0 then
// blocks of 32: 32, 64, 96, 128, 160, 192, 224
bm := @FMiniMemoryBlocks[(aSize-1) shr 5] else
begin
Result := nil;
Exit;
end
else if aSize <= (length(FSmallMemoryBlocks)*256) then
// blocks of 256: 256,512,768,1024,1280,1536,1792 bytes
bm := @FSmallMemoryBlocks[(aSize-1) shr 8]
{$ifdef USEMEDIUM}
else if aSize <= (length(FMediumMemoryBlocks)*2048) then
// blocks of 2048: 2048, 4096... bytes
bm := @FMediumMemoryBlocks[(aSize-1) shr 11]
{$endif}
else
begin
// larger blocks are allocated via the old Memory Manager
Result := GetOldMem(aSize);
Exit;
end;
if FOtherThreadFreedMemory <> nil then
ProcessFreedMemFromOtherThreads;
with bm^ do
begin
if FFirstFreedMemBlock <> nil then
// first get from freed mem (fastest because most chance?)
Result := FFirstFreedMemBlock.GetUsedMemoryItem else
// from normal list
Result := GetMemFromNewBlock;
end;
Result := Pointer(NativeUInt(Result) + SizeOf(TMemHeader));
end;
This method is usually called from the Delphi Getmem() function. This method was declared inline, that is the code will be like if it was typed in the source, whereas a not inlined method will just be called in place.
In some cases, the asm code produced by the Delphi compiler was really
optimized. For instance, when a pm := Owner.GetMem(SizeOf(pm^));
is called, it will generate this asm instructions:
SynScaleMM.pas.752: pm := Owner.GetMem(SizeOf(pm^)); 0040B7FA 8B4308 mov eax,[ebx+$08] 0040B7FD BA02000000 mov edx,$00000002 0040B802 8D74D074 lea esi,[eax+edx*8+$74] 0040B806 833800 cmp dword ptr [eax],$00 0040B809 7405 jz $0040b810 0040B80B E810FEFFFF call TThreadMemManager.ProcessFreedMemFromOtherThreads 0040B810 8BC6 mov eax,esi 0040B812 8B10 mov edx,[eax] 0040B814 85D2 test edx,edx 0040B816 7409 jz $0040b821 0040B818 8BC2 mov eax,edx 0040B81A E889FFFFFF call TMemBlock.GetUsedMemoryItem 0040B81F EB05 jmp $0040b826 0040B821 E8EA000000 call TMemBlockList.GetMemFromNewBlock 0040B826 83C008 add eax,$08 0040B829 8BF0 mov esi,eax
As you can see, all if aSize <= (length(FMiniMemoryBlocks)*32)
then branches have been changed into a fixed branch, because in our case
the aSize parameter was a constant (in our case,
SizeOf(pm^)).
Modern compilers rock!
Inlining is one of the only features I'm missing with Delphi 7. But the same
code compiled with Delphi 7 (without the inlining, I wanted our fork
to compile from Delphi 6 up to XE), performs very well. A little less
aggressive, but it does scale much better than FastMM4, in all cases!
Feedback is welcome on our forum!
