2016-01-18 2 views
5

Ich habe mich gefragt, was intern passiert, wenn New and Disposed aufgerufen wird. Delphi-Hilfe gibt eine vernünftige Erklärung, aber was müsste getan werden, wenn eine Person ihr eigenes Neues schreiben und entsorgen würde? Welche Methoden werden intern zu den beiden aufgerufen oder ist das alles Assembly?Was machen Neu und Dispose intern?

Ich suche nicht mein eigenes Neues und Dispose zu schreiben. Ich bin nur sehr neugierig auf die interne Arbeitsweise beider Methoden.

Antwort

9

New macht folgende:

  1. Speicher zuweisen für das neue Objekt mit einem Aufruf an GetMem. diese
  2. alle verwalteten Felder initialisieren wie Strings, Schnittstellen, dynamische Arrays usw.

Dispose umkehrt:

  1. die verwalteten Felder finalisieren.
  2. Den Speicher mit einem Aufruf von freigeben.

Beachten Sie, dass beide New und Disposeintrinsic functions sind. Dies bedeutet, dass der Compiler zusätzliche Kenntnisse von ihnen besitzt und in der Lage ist, die Art der Implementierung je nach Typ zu variieren.

Wenn der Typ beispielsweise keine verwalteten Felder hat, wird New in einen einfachen Aufruf an GetMem optimiert. Wenn der Typ verwaltete Felder hat, wird New mit einem Aufruf an System._New implementiert, der die oben beschriebenen Schritte ausführt.

Die Implementierung von Dispose ist in etwa gleich. Ein einfacher Aufruf an FreeMem für nicht verwaltete Typen und andernfalls ein Aufruf an System._Dispose.

Nun wird System._New wie folgt umgesetzt:

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

Bitte beachte, dass ich die PUREPASCAL Variante der Einfachheit halber nur gezeigt haben. Der Anruf zu GetMem ist einfach genug. Der Anruf bei System._Initialize ist viel mehr beteiligt. Das Argument TypeInfo verwendet alle verwalteten Typen, die im Objekt enthalten sind, und initialisiert sie. Dies ist ein rekursiver Prozess, da beispielsweise ein Datensatz Elemente enthalten kann, die selbst Strukturtypen sind. Sie können alle blutigen Details in der RTL-Quelle sehen.

Für System._Dispose ruft es System._Finalize und dann FreeMem. Und System._Finalize ist sehr ähnlich zu System._Initialize, außer dass es verwalteten Typen finalisiert, anstatt sie zu initialisieren.

Es war schon lange ein Fehler der leistungssensitiven Delphi-Benutzer, dass System._Initialize und System._Finalize auf diese Weise implementiert wurden, zusätzlich zu Informationen zum Laufzeittyp. Die Typen sind zum Zeitpunkt der Kompilierung bekannt, und der Compiler könnte inline geschrieben werden, um die Initialisierung und Finalisierung durchzuführen, was zu einer besseren Leistung führt.

+0

Danke David. Könnte man eine schnellere Implementierung für einen bestimmten Datensatztyp schreiben? –

+1

Bei verwaltetem Typ würde ein Aufruf von 'GetMem' gefolgt von einer Nullstellung des Speichers oft schneller sein. Für unmanaged Typen gibt es nichts zu gewinnen. –

+0

Ich denke ich verstehe jetzt. –

4

Es ist, wie Sie Ihre eigenen New() und Dispose() Funktionen zu schreiben, mit Ihrer eigenen Rekord Initialisierung/Fertigstellung:

uses 
    TypInfo; 

procedure RecordInitialize(Dest, TypeInfo: pointer); 
asm 
    {$ifdef CPUX64} 
    .NOFRAME 
    {$endif} 
    jmp [email protected] 
end; 

procedure RecordClear(Dest, TypeInfo: pointer); 
asm 
    {$ifdef CPUX64} 
    .NOFRAME 
    {$endif} 
    jmp [email protected] 
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; 

Erstens gibt es einen Low-Level-asm Trick, um die verborgenen inneren Funktionen aufzurufen wir brauchen.

Ich schlage dann zwei Möglichkeiten der Initialisierung des Datensatzes vor, einen mit [email protected], den anderen mit FillChar. Beachten Sie außerdem, dass die Initialisierung/Finalisierung der Elemente automatisch erfolgt, wenn Sie Ihre Datensätze in einem dynamischen Array zuweisen. Und die Initialisierung ruft nicht Initialize() sondern FillChar auf, um den Speicher mit Nullen zu füllen.

Es ist schade, dass wir globale Funktionen/Prozeduren mit generischen Parametern nicht definieren konnten, sonst könnten wir den Parameter TypeInfo() loswerden.

Vor einigen Jahren schrieb ich die RTL part of record initialization/finalization für schnellere Ausführung neu. Beachten Sie, dass TObject würde FinalizeRecord aufrufen, so ist es ein Teil der RTL, die Verstärkung optimiert werden. Der Compiler sollte den Code ausgeben, anstatt die RTTI zu verwenden - aber zumindest sollte die RTL ein bisschen mehr optimiert werden.

Wenn Sie unsere Open Source SynCommons.pas Gerät verwenden, und legen Sie die DOPATCHTRTL bedingten für Ihr Projekt finden Sie im Prozess Patch der FillChar Move RecordCopy FinalizeRecord InitializeRecord TObject.CleanupInstance Low-Level-Funktionen RTL haben, die asssembly optimiert verwenden würde, und SSE2 Opcodes, falls verfügbar.

Verwandte Themen