Optimizing Your Delphi Program's Memory Usage

of 06

What Does Windows Think About Your Program's Memory Usage?

windows taskbar manager
windows taskbar manager.

When writing long running applications - the kind of programs that will spend most of the day minimized to the task bar or system tray, it can become important not to let the program 'run away' with memory usage.

Learn how to clean up the memory used by your Delphi program using the SetProcessWorkingSetSize Windows API function.

Memory Usage of a Program / Application / Process

Take a look at the screen shot of the Windows Task Manager...

The two rightmost columns indicate CPU (time) usage and memory usage. If a process impacts on either of these severely, your system will slow down.

The kind of thing that frequently impacts on CPU usage is a program that is looping (ask any programmer that has forgotten to put a "read next" statement in a file processing loop). Those sorts of problems are usually quite easily corrected.

Memory usage on the other hand is not always apparent, and needs to be managed more than corrected. Assume for example that a capture type program is running.

This program is used right throughout the day, possibly for telephonic capture at a help desk, or for some other reason. It just doesn’t make sense to shut it down every twenty minutes and then start it up again. It’ll be used throughout the day, although at infrequent intervals.

If that program relies on some heavy internal processing, or has lots of art work on its forms, sooner or later its memory usage is going to grow, leaving less memory for other more frequent processes, pushing up the paging activity, and ultimately slowing down the computer.

Read on to find out how to design your program in such a way that it keeps its memory usage in check...

Note: if you want to know how much memory your application is currently using, and since you cannot ask the user of the application to look at the Task Manager, here's a custom Delphi function: CurrentMemoryUsage

of 06

When to Create Forms in Your Delphi Applications

Delphi program DPR file listing auto-create forms
delphi program DPR file auto-create listing forms.

Lets say that you are going to design a program with a main form and two additional (modal) forms. Typically, depending on your Delphi version, Delphi is going to insert the forms into the project unit (DPR file) and will include a line to create all forms at application startup (Application.CreateForm(...)

The lines included in the project unit are by Delphi design, and are great for people that are not familiar with Delphi or are just starting to use it. It's convenient and helpful. It also means that ALL the forms are going to be created when the program starts up and NOT when they are needed.

Depending on what your project is about and the functionality you have implemented a form can use a lot of memory, so forms (or in general: objects) should only be created when needed and destroyed (freed) as soon as they are no longer necessary.

If "MainForm" is the main form of the appliction it needs to be the only form created at startup in the above example.

Both, "DialogForm" and "OccasionalForm" need to be removed from the list of "Auto-create forms" and moved to the "Available forms" list.

Read the "Making Forms Work - a Primer" for a more in-depth explanation and how to specify what forms are created when.

Read the "TForm.Create(AOwner) ... AOwner ?!?" to learn who the owner of the form should be (plus: what is the "owner").

Now, when you know when forms should be created and who the Owner should be, let's move on to how to watchout for memory consumption...

of 06

Trimming Allocated Memory: Not as Dummy as Windows Does It

Portrait, girl lighted with colorful code
Stanislaw Pytel / Getty Images

Please note that the strategy outlined here is based on a the assumption that the program in question is a real time “capture” type program. It can however be easily adapted for batch type processes.

Windows and Memory Allocation

Windows has a rather inefficient way of allocating memory to its processes. It allocates memory in significantly large blocks.

Delphi has tried to minimize this and has its own memory management architecture which uses much smaller blocks but this is virtually useless in the Windows environment because the memory allocation ultimately rests with the operating system.

Once Windows has allocated a block of memory to a process, and that process frees up 99.9% of the memory, Windows will still perceive the whole block to be in use, even if only one byte of the block is actually being used. The good news is that Windows does provide a mechanism to clean up this problem. The shell provides us with an API called SetProcessWorkingSetSize. Here's the signature:

hProcess: HANDLE;
MinimumWorkingSetSize: DWORD;
MaximumWorkingSetSize: DWORD) ;

Let's find out about the SetProcessWorkingSetSize function...

of 06

The All Mighty SetProcessWorkingSetSize API Function

Cropped Hands Of Businesswoman Using Laptop At Table In Office
Sirijit Jongcharoenkulchai / EyeEm / Getty Images

By definition, the SetProcessWorkingSetSize function sets the minimum and maximum working set sizes for the specified process.

This API is intended to allow low level setting of the minimum and maximum memory boundaries for process’s memory usage space. It does however have a little quirk built into it which is most fortunate.

If both the minimum and the maximum values are set to $FFFFFFFF then the API will temporarily trim the set size to 0, swapping it out of memory, and immediately as it bounces back into RAM, it will have the bare minimum amount of memory allocated to it (this all happens within a couple of nanoseconds, so to the user it should be imperceptible).

Also a call to this API will only be made at given intervals – not continuously, so there should be no impact at all on performance.

We need to watch out for a couple of things.

Firstly, the handle referred to here is the process handle NOT the main forms handle (so we can’t simply use “Handle” or “Self.Handle”).

The second thing is that we cannot call this API indescrimminately, we need to try and call it when the program is deemed to be idle. The reason for this is that we don’t want trim memory away at the exact time that some processing (a button click, a key press, a control show etc.) is about to happen or is happening. If that is allowed to happen, we run a serious risk of incurring access violations.

Read on to learn how and when to call the SetProcessWorkingSetSize function fromy our Delphi code...

of 06

Trimming Memory Usage on Force

Reflection of male hacker coding working hackathon at laptop
Hero Images / Getty Images

The SetProcessWorkingSetSize API function is intended to allow low level setting of the minimum and maximum memory boundaries for process’s memory usage space.

Here's a sample Delphi function that wraps the call to SetProcessWorkingSetSize:

 procedure TrimAppMemorySize;
  MainHandle : THandle;
    MainHandle := OpenProcess(PROCESS_ALL_ACCESS, false, GetCurrentProcessID) ;
    SetProcessWorkingSetSize(MainHandle, $FFFFFFFF, $FFFFFFFF) ;
    CloseHandle(MainHandle) ;

Great! Now we have the mechanism to trim the memory usage. The only other obstacle is to decide WHEN to call it. I have seen quite a few third party VCLs and strategies for getting system, application and all sorts of idle time. In the end I decided to stick with something simple.

In the case of a capture/enquiry type program, I decided that it would be safe to assume that the program is idle if it is minimized, or if there have been no key presses or mouse clicks for a certain period. So far this seems to have worked pretty well seeing as though we’re trying to avoid conflicts with something that is only going to take up a fraction of a second.

Here's a way to programmatically track a user's idle time.

Read on to find out how I have used the TApplicationEvent's OnMessage event to call my TrimAppMemorySize ...

of 06

TApplicationEvents OnMessage + a Timer := TrimAppMemorySize NOW

Businessman using computer in office
Morsa Images / Getty Images

In this code we have it laid down like this:

Create a global variable to hold the last recorded tick count IN THE MAIN FORM. At any time that there is any keyboard or mouse activity record the tick count.

Now, periodically check the last tick count against “Now” and if the difference between the two are greater than the period deemed to be a safe idle period, trim the memory.

  LastTick: DWORD;

Drop an ApplicationEvents component on the main form. In its OnMessage event handler enter the following code:

 procedure TMainForm.ApplicationEvents1Message(var Msg: tagMSG; var Handled: Boolean) ;
  case Msg.message of
      LastTick := GetTickCount;

Now decide after what period of time you will deem the program to be idle. We decided on two minutes in my case, but you can choose any period you want depending on the circumstances.

Drop a timer on the main form. Set its interval to 30000 (30 seconds) and in its “OnTimer” event put the following one line instruction:

 procedure TMainForm.Timer1Timer(Sender: TObject) ;
  if (((GetTickCount - LastTick) / 1000) > 120) or (Self.WindowState = wsMinimized) then TrimAppMemorySize;

Adaptation For Long Processes Or Batch Programs

To adapt this method for long processing times or batch processes is quite simple. Normally you’ll have a good idea where a lengthy process will start (eg beginning of a loop reading through millions of database records) and where it will end (end of database read loop).

Simply disable your timer at the start of the process, and enable it again at the end of the process.