1

When using VirtualAlloc I can (ab)use the following property to simplify memory management.

Actual physical pages are not allocated unless/until the virtual addresses are actually accessed.

I run the following code to allocate the block.

type
  PArrayMem = ^TArrayMem;    //pointer
  TArrayMem = packed record  //as per documentation
    RefCount: Integer;
    Length: NativeInt;
    Elements: Integer;
  end;

var
  a: array of integer;  //dynamic array, structure see above

procedure TForm38.Button1Click(Sender: TObject);
const
  AllocSize = 1024 * 1024 * 1024; //1 GB
var
  ArrayMem: PArrayMem;
begin
  //SetLength(a, 1024*1024*1024); //1G x 8*16
  ArrayMem:= VirtualAlloc(nil, AllocSize, MEM_COMMIT or MEM_RESERVE, PAGE_READWRITE);
  ArrayMem.RefCount:= 1;
  ArrayMem.Length:= AllocSize div SizeOf(Integer);
  a:= @ArrayMem.Elements;   //a:= AddressOf(elements)
  a[1]:= 10;        //testing, works
  a[0]:= 4;
  a[500000]:= 56;   //Works, autocommits, only adds a few k to the used memory
  button1.Caption:= IntToStr(a[500000]);  //displays '56'
end;

All this works great. If my structure grows to 1.000.000 elements everything works.
However suppose afterwards my structure shrinks back to 1.000 elements.

How do I release the RAM so that it will get auto-magically committed when needed again?

WARNING
David warned my that allocating an committing large (huge) continous pages of memory carries a large cost.
So it may be more advantageous to split up the array in smaller blocks and abstract away the internals using a class/record.

Johan
  • 74,508
  • 24
  • 191
  • 319
  • It is not very clear what you want to release, "memory" is a pretty vague term. Are you talking about RAM or address space? RAM is automatic, no need to help. If you want to release address space then using VirtualAlloc() like this is a bad idea, use HeapAlloc() instead. – Hans Passant Mar 08 '16 at 15:26
  • @HansPassant, sorry I meant RAM. I'm not worried about address space, because I'm only running in Win64. – Johan Mar 08 '16 at 15:38
  • You cannot directly tinker with RAM allocation, that is the OS' job. SetProcessWorkingSetSize() is a crude sledge-hammer to force RAM pages out, certainly not appropriate here. Follow up with @David, I don't think he understood what you meant. – Hans Passant Mar 08 '16 at 15:47
  • @HansPassant, ? Where does `SetProcessWorkingSetSize` come in? I'm talking about VirtualAlloc+VirtualFree+VirtualAlloc again. – Johan Mar 08 '16 at 15:50
  • You are sending mixed signals, I followed up on your "sorry I meant RAM" comment. No idea what you are taking about anymore, I'd better stop interfering. – Hans Passant Mar 08 '16 at 15:53

1 Answers1

3

You can decommit pages using VirtualFree passing the MEM_DECOMMIT flag. Then you can commit again using VirtualAlloc.

Or you may use the DiscardVirtualMemory function introduced in Windows 8.1.

Use this function to discard memory contents that are no longer needed, while keeping the memory region itself committed. Discarding memory may give physical RAM back to the system. When the region of memory is again accessed by the application, the backing RAM is restored, and the contents of the memory is undefined.

You may find something useful in the comments to this related question: New Windows 8.1 APIs for virtual memory management: `DiscardVirtualMemory()` vs `VirtualAlloc()` and `MEM_RESET` and `MEM_RESET_UNDO`

Community
  • 1
  • 1
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Can I do a partial decommit-recommit as well? E.g. I want to decommit only elements above 5.000 and resubmit those to the autocommit pool. Would code like this work: `virtualfree(@a[5001],Size,MEM_DECOMMIT); VirtualAlloc(@a[5001], size,....)` – Johan Mar 08 '16 at 15:32
  • As I understand it you can do so. I rather suspect that you may be over-thinking the problem though. – David Heffernan Mar 08 '16 at 15:33
  • Are you suggesting I don't bother with the `VirtualFree` and just let the memory no longer in use remain committed? – Johan Mar 08 '16 at 15:37
  • I think I'd probably be trying to solve the problem without resorting to direct calls to virtual mem API. – David Heffernan Mar 08 '16 at 15:40
  • I tried that, but using SetLength with 4 GB of data trashes the system and locks up the PC :-) and I can't afford the costs of a `move` when I grow a plain dynarray using SetLength. The app uses memorization to speed up a process, hence the memory hunger. Once the solution is found I can release the memory. – Johan Mar 08 '16 at 15:46
  • You don't need to allocate in one contiguous block. And indeed that does have terrible perf costs when you grow. You can stitch together an array out of smaller blocks. When it grows beyond what you have already allocated, allocate another block. When the user asks for item i, work out which block it is in, at what offset, and return it. – David Heffernan Mar 08 '16 at 15:49
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/105718/discussion-between-johan-and-david-heffernan). – Johan Mar 08 '16 at 15:52