We are searching data for your request:
Upon completion, a link will appear to access the found materials.
When writing long-running applications - the kind of programs that will spend most of the day minimized to the taskbar 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.01of 06
What Does Windows Think About Your Program's Memory Usage?
Take a look at the screenshot 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 artwork 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.02of 06
When to Create Forms in Your Delphi Applications
Let's say that you are going to design a program with the 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 application 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.03of 06
Trimming Allocated Memory: Not as Dummy as Windows Does ItStanislaw Pytel / Getty Images
Please note that the strategy outlined here is based on 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:
MaximumWorkingSetSize: DWORD) ;04of 06
The All Mighty SetProcessWorkingSetSize API FunctionSirijit 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 a low level setting of the minimum and maximum memory boundaries for the 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).
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:
- The handle referred to here is the process handle NOT the main forms handle (so we can't simply use “Handle” or “Self.Handle”).
- We cannot call this API indiscriminately, 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 keypress, 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.
Trimming Memory Usage on ForceHero Images / Getty Images
The SetProcessWorkingSetSize API function is intended to allow low-level setting of the minimum and maximum memory boundaries for the process's memory usage space.
Here's a sample Delphi function that wraps the call to SetProcessWorkingSetSize:
MainHandle : THandle;
MainHandle := OpenProcess(PROCESS_ALL_ACCESS, false, GetCurrentProcessID) ;
SetProcessWorkingSetSize(MainHandle, $FFFFFFFF, $FFFFFFFF) ;
Great! Now we have the mechanism to trim the memory usage. The only other obstacle is to decide WHEN to call it.
TApplicationEvents OnMessage + a Timer := TrimAppMemorySize NOWMorsa 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 is greater than the period deemed to be a safe idle period, trim the memory.
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.