Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does New and Dispose do internally?

Tags:

delphi

I was wondering about what happens internally when New and Disposed is called. Delphi help gives a reasonable explanation but what would have to be done if a person would write one's own New and Dispose? Which methods are called internally to the two or is it all assembly?

I am not looking to write my own New and Dispose. I am just extremely curious about both methods' internal working.

like image 738
Blurry Sterk Avatar asked Jan 18 '16 12:01

Blurry Sterk


2 Answers

New does the following:

  1. Allocate memory for the new object with a call to GetMem.
  2. Initialize any managed fields such as strings, interfaces, dynamic arrays etc.

Dispose reverses this:

  1. Finalize the managed fields.
  2. Deallocate the memory with a call to FreeMem.

Note that both New and Dispose are intrinsic functions. This means that the compiler has extra knowledge of them and is able to vary how they are implemented depending on the type in question.

For instance, if the type has no managed fields then New is optimized into a simple call to GetMem. If the type has managed fields then New is implemented with a call to System._New, which performs the steps described above.

The implementation of Dispose is much the same. A simple call to FreeMem for non-managed types, and a call to System._Dispose otherwise.

Now, System._New is implemented like this:

function _New(Size: NativeInt; TypeInfo: Pointer): Pointer;
begin
  GetMem(Result, Size);
  if Result <> nil then
    _Initialize(Result, TypeInfo);
end;

Note that I have just shown the PUREPASCAL variant for simplicity. The call to GetMem is simple enough. The call to System._Initialize is a lot more involved. That uses the TypeInfo argument to find all the managed types contained in the object and initialize them. This is a recursive process because, for example, a record may contain members that are themselves structure types. You can see all the gory details in the RTL source.

As for System._Dispose, it calls System._Finalize and then FreeMem. And System._Finalize is very similar to System._Initialize except that it finalizes managed types rather than initializing them.

It's long been something of a bug bear of performance sensitive Delphi users that System._Initialize and System._Finalize are implemented this way, on top of runtime type information. The types are known at compile time and the compiler could be written to inline the initialization and finalization which would result in better performance.

like image 146
David Heffernan Avatar answered Nov 15 '22 04:11

David Heffernan


It is how to write your own New() and Dispose() functions, with your own record initialization/finalization:

uses
  TypInfo;

procedure RecordInitialize(Dest, TypeInfo: pointer);
asm
  {$ifdef CPUX64}
  .NOFRAME
  {$endif}
  jmp System.@InitializeRecord
end;

procedure RecordClear(Dest, TypeInfo: pointer);
asm
  {$ifdef CPUX64}
  .NOFRAME
  {$endif}
  jmp System.@FinalizeRecord
end;

function NewRec1(TypeInfo: pointer): pointer;
begin
  GetMem(result, GetTypeData(TypeInfo)^.RecSize);
  RecordInitialize(result, TypeInfo);
end;

function NewRec2(TypeInfo: pointer): pointer;
var
  len: integer;
begin
  len := GetTypeData(TypeInfo)^.RecSize;
  GetMem(result, len);
  FillChar(result^, len, 0);
end;

procedure NewDispose(Rec: pointer; TypeInfo: Pointer);
begin
  RecordClear(Rec, TypeInfo);
  FreeMem(Rec);
end;

First, there is a low-level asm trick to call the hidden intrinsic functions we need.

I then propose two ways of initializing the record, one using System.@InitializeRecord, the other using FillChar. Also note that if you allocate your records within a dynamic array, the initialization/finalization of the items would be done automatically. And initialization won't call Initialize() but FillChar to fill the memory with zeros.

It is a pity that we could not define global functions/procedures with generic parameters, otherwise we may have get rid of the TypeInfo() parameter.

Some years ago, I re-wrote the RTL part of record initialization/finalization, for faster execution. Note that TObject would call FinalizeRecord, so it is a part of the RTL which gain of being optimized. The compiler should emit the code instead of using the RTTI - but at least the RTL should be optimized a bit more.

If you use our Open Source SynCommons.pas unit, and define the DOPATCHTRTL conditional for your project, you will have in-process patch of the RTL FillChar Move RecordCopy FinalizeRecord InitializeRecord TObject.CleanupInstance low-level functions, which would use optimized asssembly, and SSE2 opcodes if available.

like image 41
Arnaud Bouchez Avatar answered Nov 15 '22 02:11

Arnaud Bouchez