What bookkeeping data does a Delphi dynamic array contain?
-
19-09-2019 - |
Question
Here's a simple program to check memory allocation. Checking before and after values with Task Manager suggests that each dynamic array takes up 20 bytes of memory at size = 1. The element size is 4, which means 16 bytes of overhead for bookkeeping data.
From looking through system.pas, I can find an array length field at -4 bytes, and a reference count at -8 bytes, but I can't seem to find any references to the other 8. Anyone know what they do?
Sample program:
program Project1;
{$APPTYPE CONSOLE}
type
TDynArray = array of integer;
TLotsOfArrays = array[1..1000000] of TDynArray;
PLotsOfArrays = ^TLotsOfArrays;
procedure allocateArrays;
var
arrays: PLotsOfArrays;
i: integer;
begin
new(arrays);
for I := 1 to 1000000 do
setLength(arrays^[i], 1);
end;
begin
readln;
allocateArrays;
readln;
end.
Solution
I had a look into System.pas as well and noticed that the GetMem call in _DynArrayCopyRange supports your analyis:
allocated size = count * element size + 2 * Sizeof(Longint)
. So maybe the numbers you get from task manager aren't very accurate. You could try Pointer(someDynArray) := nil
and check which memory leak size FastMM reports for more reliable numbers.
Edit: I did a little test program:
program DynArrayLeak;
{$APPTYPE CONSOLE}
uses
SysUtils;
procedure Test;
var
arr: array of Integer;
i: Integer;
begin
for i := 1 to 6 do
begin
SetLength(arr, i);
Pointer(arr) := nil;
end;
end;
begin
ReportMemoryLeaksOnShutdown := True;
Test;
end.
This yields
An unexpected memory leak has occurred. The unexpected small block leaks are: 1 - 12 bytes: Unknown x 1 13 - 20 bytes: Unknown x 2 21 - 28 bytes: Unknown x 2 29 - 36 bytes: Unknown x 1
which supports the 8 byte overhead theory.
OTHER TIPS
Memory allocations have granularity to ensure all allocations are aligned. This is just the slop caused by this.
Updated...
I actually went to check the code (which I should've done before) and I came to the same conclusion as Ulrich, it's not storing any type information, just the 2 Longint overhead then NbElements*ElementSize.
And, Task manager is not accurate for this kind of measure.
With the oddity that if you measure the memory used by the dynarray, it increases non linearly with the size of the element: for a Record with 2 or 3 Integers it's the same size (20), with 4 or 5 it's 28... following the granularity of the blocksizes.
Memory measured with:
// Return the total Memory used as reported by the Memory Manager
function MemoryUsed: Cardinal;
var
MemMgrState: TMemoryManagerState;
SmallBlockState: TSmallBlockTypeState;
begin
GetMemoryManagerState(MemMgrState);
Result := MemMgrState.TotalAllocatedMediumBlockSize + MemMgrState.TotalAllocatedLargeBlockSize;
for SmallBlockState in MemMgrState.SmallBlockTypeStates do begin
Result := Result + SmallBlockState.UseableBlockSize * SmallBlockState.AllocatedBlockCount;
end;
end;