views:

362

answers:

3

There is sometimes a problem with running out of memory when it got fragmented.

Is it possible to find the largest free memoryblock ? I use Delphi 2007 with FastMM. Developing on Windows XP running app on Windows 2003.

Regards

EDIT: I could add the info that the app is running on a server with 32 GB memory on a Windows Server 2003 x64. But the app is a 32 bit application so the theoretical max allocated memory for each instance is 2 GB. Many instances is run at once. I do not think it is the total physical memory that is to little. I guess when started the app got a 32 bit virtual memory space. This can then be too fragmented during runtime.

I also found the method FastGetHeapStatus that returns a THeapStatus with some fields for free memory. Maybe I could use those.

EDIT2: I found this How to get the largest available continues memory block. The code is C but maybe it could be translated to Delphi.

+6  A: 

No, this is the "maxavail" in old Turbo Pascal, a often requested feature, but unfortunately it is a useless concept in a multiuser, multi-tasking environment

The heapmanager can know the biggest block in the memory it maintains itself, but that will usually be small, since big chunks are directly allocated from (and returned to) windows.

And schemes to progressively try to allocate bigger blocks will fail because the OS will grant requests even if that means swapping to disk for it (which you don't want). Same for tricks that try to mine such values with windows api calls.

The whole protected mode environment has as an fundament that the memory is shared, and every app only uses as much as needed. Ignoring this and pretending everything is still like under Dos will only yield massive complaints from people that do run multiple applications at once.

If your application is really dependant on this, make it a configuration setting (how much memory to allocate on startup for something) with a safe (small) default. If it is REALLY crucial, confront the user with it during setup

One can of course always seed the defaults with heuristic attempts by doing a few winapi calls and assuming that no other apps run. But always leave the end decision with the user, SPECIALLY for server apps.

Marco van de Voort
+3  A: 

On a virtual memory system, the virtual address space means that virtual pages can map anywhere. You don't need large contiguous blocks of physical memory. If you are having problems with fragmentation of your virtual address space then you may need a different memory management strategy.

However, most options would require your application code to be aware of the memory management strategy at some level. I don't believe there is a quick fix for this problem - you are probably up for reasonably major surgery to fix it. None of these options are simple to implement, you will have to find the one most likely to work in your particular case.

The major options that I can see are: custom memory allocators, something involving AWE (see below) or rebuilding the memory allocation strategy within the application.

Option 1: Custom memory allocators

Custom memory allocators are not uncommon in C and C++ circles. You might be able to implement something similar. Two possibilities are open to you:

  • Build a memory allocator with a mechanism that attempts to merge adjacent free blocks into a single larger block (you could run this as a part of attempting to recover from a failed memory allocation). This might allow you to transparently manage the memory without the application having to be aware. Implementing this would be fiddly and technical but is probably feasible.

    The principal benefit of this approach is that it is the only one that would not require you to change existing application code. The downside is that it is not guaranteed to work; it is still possible that the merge operation can fail to consolidate a block of memory large enough to fulfil the request. The merge operation may also cause significant pauses in application response while it runs.

  • You may need to build your application in a way that allows the data structure to be compacted. This would require you to maintain handles that support the objects being moved, i.e. a double indirection mechanism. I'm guessing that there is probably one or a fairly small number of different data structures that cause this fragmentation problem, so it may be possible to localise any re-architecture work within your application.

Option 2: PAE

Windows does support facilities to directly manipulate the MMU, and there are a couple of possibilities where this could apply to your application. This would definitely require explicit architectural support from your application, but offers the possibility of using a pool of memory that is much larger than 2GB.

On server versions of Windows, look into PAE, which is supported by API's that allow you to manually manipulate the system's MMU and re-map chunks of memory. This might be helpful to you in one of two ways

  • You could build the manager for the data structure in a way that uses this mechanism as an inherent part of managing the data.

  • If you can fit the items in your data structure to page boundaries you may be able to use this as a way to consolidate memory.

However, this approach would require you to re-engineer your application so that object references had enough information to manage the explicit swap-in process (possibly some sort of overlay manager with a proxy mechanism for the objects being referenced through this system). This means that any solution involving PAE is not a drop-in replacement for FastMM - you would have to modify the application to explicitly support PAE.

However, a proxy mechanism of this sort might mean that this subsystem could be relatively transparent to clients. Apart from the overhead of managing the indirection and overlays (which may or may not be a significant issue) the proxies could be virtually indistinguisable from the original API. This type of approach would work best for a relatively small number of large, heavyweight objects with minimal interconnection - the canonical application for this is disk caching. The proxies would have to remain in a fixed location in memory, with the larger objects being accessed through the overlay mechanism. YMMV.

Option 3: Fix the problem at source

One possibility is that your object allocation strategy can be optimised from within the application code (perhaps from pools of objects allocated in bulk and then managed from within the application). This might allow you to deal with memory fragmentation from within your application without attempting to re-write the memory manager.

Again, this approach means that you will have to re-build parts of your application, and the applicability of the approach really depends on the nature of your application. Only you can be the judge of how well this might work.

ConcernedOfTunbridgeWells
1) Merging adjacent blocks doesn't really yield much if blocks are immovable. An own memmanager can be a desparate solution, but you probably have to add knowledge about the apps access pattern to the manager to do significantly better than fastmm. 2)PAE doesn't help because the Delphi compiler is 32, not 64-bit, and assumes 32-bit lineair pointers. Schemes like not storing the lower 4 bits will break pointer arithmetic. Factoring memory intensive bits out to 64-bits FPC dll's is a saner solution then. I'd go for option 3 in combination with allocating the largest block needed on startup
Marco van de Voort
Thanks for the answer. We do not build an own memorymanager, I'm sure of that. What we really hope for is an 64-bit Delphi compiler as memory is cheap today. In the meantime I added a memorymeter in the application so the user can observe the situation. In case of EOutOfMemory I suggest a restart... :)
Roland Bengtsson
+2  A: 

This is the translation to Delphi code that you wanted:

function GetLargestFreeMemRegion(var AAddressOfLargest: pointer): LongWord;
var
  Si: TSystemInfo;
  P, dwRet: LongWord;
  Mbi: TMemoryBasicInformation;
begin
  Result := 0;
  AAddressOfLargest := nil;
  GetSystemInfo(Si);
  P := 0;
  while P < LongWord(Si.lpMaximumApplicationAddress) do begin
    dwRet := VirtualQuery(pointer(P), Mbi, SizeOf(Mbi));
    if (dwRet > 0) and (Mbi.State and MEM_FREE <> 0) then begin
      if Result < Mbi.RegionSize then begin
        Result := Mbi.RegionSize;
        AAddressOfLargest := Mbi.BaseAddress;
      end;
      Inc(P, Mbi.RegionSize);
    end else
      Inc(P, Si.dwPageSize);
  end;
end;

You can use it like this:

procedure TForm1.FormCreate(Sender: TObject);
var
  BaseAddr: pointer;
  MemSize: LongWord;
begin
  MemSize := GetLargestFreeMemRegion(BaseAddr);
  // allocate dynamic array of this size
  SetLength(fArrayOfBytes, MemSize - 16);

  Caption := Format('Largest address block: %u at %p; dynamic array at %p',
    [MemSize, BaseAddr, pointer(@fArrayOfBytes[0])]);
end;

Note that I had to subtract 16 bytes from the maximum size, presumably because the dynamic array itself uses a few bytes which were allocated from the same chunk of memory, so the next allocation was based on the next multiple of 16.

mghie