views:

181

answers:

3

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.
A: 

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;
François
I don't think so. All the relevant routines seem to take type info as a separate parameter, which the compiler keeps track of.
Mason Wheeler
Re: non-linearity: I guess this is a memory manager artefact and nothing special about dyn arrays.
Ulrich Gerhardt
As a rider to this thread, I've had problems with mem allocation using dynamic arrays and SetLength which occur if you build an expanding list (now obviously). I had not wanted to use a TList descendent. A simple way around this would be to have a 'capacity' setting for dynamic arrays, I wondered about doing my own GetMem and then using ABSOLUTE to fix the expanding array in the same 'slot' but yuk...... I'm surpised that the compiler could not support this feature.
Brian Frost
+4  A: 

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.

Ulrich Gerhardt
You're right. It's right there in the code. And Task Manager is pretty accurate, but what it measures is the amount of RAM that the app has allocated from Windows. So the real question is, why is FastMM grabbing so much more memory than it needs from the OS? Maybe to reduce the total number of memory requests required, and keep fragmentation down?
Mason Wheeler
Task Manager is NOT accurate when diagnosing Delphi memory usage. It does not take into account the extra overhead that the VCL's memory manager needs for its own internals in addition to the overhead of the dynamic array itself. And it does not take into account that the memory manager caches and reuses blocks of memory when they are "Freed", as they are not returned to the OS.
Remy Lebeau - TeamB
Yes. What I meant by that was that Task Manager accurately measures what it measures, which is how much the app has requested from Windows, not necessarily how much the app is actually using internally.
Mason Wheeler
As I recall FastMM uses a smart approach. It does not return every block of freed memory back at once. This is resonable because new allocations usually follow. Probably it has similar approach with array allocation. But I don't know that for sure.
Runner
+2  A: 

Memory allocations have granularity to ensure all allocations are aligned. This is just the slop caused by this.

Loren Pechtel