Use per-class allocator in Delphi program
Download source codeIntroduction:
If you ever know a little C++ knowledge, you may already know C++ allows to override the operators 'new' and 'delete' for any specified class to manage the memory in a different way than C++ internal memory manager.
Such per-class memory allocator may bring big advantage when you need to:
1, Optimize object allocation for specified classes.
For instance, if a kind of objects will be created millions of times, you may want to use an optimized memory allocation algorithm to improve the performance or reduce memory fragment.
2, Make program for hardware limited system such as embedded system, mobile phone, etc.
For instance, some mobile phones support not only normal heap memory, they also support video memory which is used for displaying and may faster than the heap. So if you want to allocate objects in the video memory, you need a different memory manger for those kinds of objects.
Now let's see how we can use per-class allocator in Delphi.
How it works:
It hooks the class' NewInstance and FreeInstance by overwriting their VTable entry, so the hooking is only limited to current class and has no effect on inherited children classes.
In the hooks of NewInstance and FreeInstance, new allocator procedure will be called to allocate and free memory, instead of the default memory manager.
So please always keep in mind: installing an allocator for a class doesn't affect that class' child class. It only affects that class itself only.
Using the code:
The source code is in PerClassAllocator.pas, all functions are exposed by a global function PerClassAllocatorInstaller that returns an instance of TPerClassAllocatorInstaller.
To make life simple, TPerClassAllocatorInstaller is designed as private and can't be created. So there is always only one instance.
To install an allocator for a specified class, call PerClassAllocatorInstaller.InstallAllocator, to uninstall it, call PerClassAllocatorInstaller.UninstallAllocator.
procedure InstallAllocator(AClass: TClass; const AAllocator: TPerClassAllocator; AAfterNewInstance: TPerClassAfterNewInstance; ABeforeFreeInstance: TPerClassBeforeFreeInstance; AInstanceSize: Integer = 0; AFlags: Cardinal = 0); overload;This is the core function to install an allocator.
AClass: The class type which you want to install allocator for.
AAllocator: The allocator. It's a record of TPerClassAllocator.
TPerClassAllocator = packed record
GetMem: TPerClassGetMem;
FreeMem: TPerClassFreeMem;
Param: Pointer;
end;
GetMem is the pointer to the function to allocate memory, FreeMem is to free. Param is the extra information passed to GetMem and FreeMem. TPerClassGetMem = function(Param: Pointer; Size: Integer; AClass: TClass): Pointer; TPerClassFreeMem = procedure(Param: Pointer; P: Pointer; AClass: TClass);AClass is the class type which is being allocating or freeing. It's usually useless unless you want to get the class information.
Why is Param passed as the first parameter? This is a trick that makes it possible to use object procedures as GetMem and FreeMem and pass the Self pointer as Param.
AAfterNewInstance: Pointer to a function that does some things such like adding reference count after the memory is allocated. It can be nil which means do what TObject does. Its prototype must be,
function MyAfterNewInstance(AClass: TClass; Instance: Pointer): TObject;
It should be a global procedure. Usually the first thing in the procedure is to initialize the object, and the first line of code will look like,
Result := AClass.InitInstance(Instance);
Function TInterfacedObject_AfterNewInstance is a sample of such usage.
ABeforeFreeInstance: Pointer to a function that does some things before the memory is freed. It can be nil which means do what TObject does. Its prototype must be,
procedure MyBeforeFreeIntance(AObj: TObject);
It should be a global procedure.
AInstanceSize: The size of the object instance. It can be 0 or negative, means use Delphi RTTI information to get the size.
It's possible that you specify a larger size, then in the function AAfterNewInstance, you store some extra information to it.
AFlags: Flags. Now there is only one flag, PCAIF_MIXED_MODE. If PCAIF_MIXED_MODE is not specified, PerClassAllocatorInstaller always assumes all creating and destroying of that class are through the new allocator. If PCAIF_MIXED_MODE is specified, PerClassAllocatorInstaller will deal with the situation that an object is created through the Delphi default memory manager, then freed by the new allocator.
procedure InstallAllocator(AClass: TClass; const AAllocator: TPerClassAllocator; AInstanceSize: Integer = 0; AFlags: Cardinal = 0); overload;An overload procedure. Works same as above InstallAllocator, but lacks of the parameters AAfterNewInstance and ABeforeFreeInstance.
Those two parameters are found in a global registered information.
You can use the function RegisterKnownClass to register the information.
Note: only classes which override NewInstance or FreeInstance should be registered. Other classes share the same NewInstance and FreeInstance and no need to be registered.
procedure InstallAllocators(AFromClass: TClass; AToClass: TClass; const AAllocator: TPerClassAllocator; AFlags: Cardinal = 0);Install allocators to the class hierarch path. AToClass must be the parent of AFromClass. If AToClass is nil, TObject is used.
procedure UninstallAllocator(AClass: TClass);Uninstall allocator for a class.
procedure UninstallAll;Uninstall all allocators.
Note: be careful to uninstall allocator. You must be sure no existing objects were allocated through the allocator, other wise, AV error will occur.
Conclusion:
Per-class memory manager is not only C++'s privilege feature. Now you can do it in a decent way in Delphi.
Though the method I used here looks like hacking Delphi, the method is more powerful than in C++. Why? You can pass extra information to the allocator, and you can get the class information that which class is being constructint or destroying in the allocator. You can even specify a larger memory size so that extra information can be stored in the extra space.
Whenever you are working on huge amount of objects, maybe you can consider use such per-class allocator to design a better memory manager to achieve high performance, low fragment, or any other benefits.