- DelphiTools - https://www.delphitools.info -

Introducing TRefCountedObject

DWScript source code recently introduced a newcomer: TRefCountedObject.

This base class takes the place of TObject in dwsUtils, and is now present throughout the DWScript code. What it adds is, well, a manually reference-counted class.

If you’re not interested in the DWScript internals, you just need to know it allowed to achieve a 5% to 25% reduction in compiled scripts memory usage (depending on what a script does), along with a minor speedup in script execution speed, at the cost of a minor compile slowdown (which should disappear once I improve the current hash table approach). It also allowed to catch a handful of memory management issues that had gone undetected even by FastMM before (as they had no side-effect).

Using TRefCountedObject

The class itself is a simple affair:

TRefCountedObject = class
   private
      function GetRefCount : Integer; inline;
      procedure SetRefCount(n : Integer); inline;
   public
      function IncRefCount : Integer; inline;
      function DecRefCount : Integer;
      property RefCount : Integer read GetRefCount write SetRefCount;
      procedure Free;
end;

But you’ll notice it introduces its own Free method, which is implemented as

procedure TRefCountedObject.Free;
begin
   if Self<>nil then
      DecRefCount;
end;

So it doesn’t directly release the object but decreases the reference count.

In practice, when you introduce a new reference to an existing instance, you invoke IncRefCount, and when you remove such a reference, you invoke DecRefCount, or Free. That’s about it. It’s manual, so it’s not fool-proof, but it is simple, and the overhead is much lower than when using interfaces, and unlike when using interfaces, you still benefit from faster calling conventions (for non-virtual methods) and direct read/write for the simple properties.

Note that this is a generalization of a mechanism that was previously present only for constant unifications (TUnifiedConstExpr), and it’s purposes was to allow the same instance to be referenced in multiple places. TUnifiedConstExpr used a very low level hack to enforce that and wasn’t as flexible, TRefCountedObject is much higher level hack, but it’s still a hack, as to behave correctly, it means a TRefCountedObject should never be freed as a TObject (or TObject.Free will be invoked).

Recovery of the hidden TMonitor field

TRefCountedObject also has another trick up its sleeves, it uses the TMonitor hidden field to store the reference counter. From D2009 up to XE2, TMonitor is still buggy and isn’t really suitable for real-world use, so not being able to use TMonitor on a TRefCOuntedObject is no big loss, and it allows to recover some bytes that were previously wasted in all class instances.

TInterfacedSelfObject was also re-based on TRefCountedObject, and benefits from the same “recovery” of the TMonitor field bytes.

With the introduction of TRefCountedObject, a whole subset TVarExpr is also now getting unified as the constants were, which in practice allows to achieve the above-mentioned reduction in compiled-scripts memory usage.