Hi Dano,
das sind sehr gute Fragen. Es ist richtig das IInteger KEINE Objecte sind aber denoch Interfaces. Es sind preallozierte Records bzw. genauer gesagt stinknormale Interfaces
Der Irrglaube heutzutage ist eben das Interfaces aus Objecten bestehen müssen, falsch: die Objecte sind eine "umständlicher" Überbau auf die Interfaces. D.h. die einfachste Interface Implementierung sind Records.
Bisher kenne ich aber immer noch keine andere Delphi Source die dieses Tricks,wie bei IInteger, benutzen oder kennen.
Es gibt nun mehrere Gründe warum IInteger so arbeitet:
1.) das Interface von DECMath ist komplett prozedural -> sprich es macht keinerlei Sinn die IInteger als Objecte zu bauen, das procedurale Interface mit overloaded Fuctions ist wesentlich effizienter und ausbaubarer.
2.) der nötige Programcode-"Überbau" damit IInterface mit TObject funktioniert reduziert die Performance bei sehr häufigen Aufrufen der Interfaces, ohne dabei Vorteile zu bringen (siehe 1.) Nutzt man Records hat man das alles selber unter Kontrolle und kann es hoch optimieren.
3.) die Speicherbereiche der Interfaces als Records sind "prealloziert". D.h. es ist nun ein leichtes über einen eigenen "Memory Manager" -> NMath.NPool -> die Speicherbereiche von freigegbenen Interfaces zwischenzuspeichern und später wiederzuverwenden. D.h. indirekt stellen die Interfacemethoden von IInteger, im speziellen die Allokatorfunktion und die Methode ._Release; die Schnittstelle zum internen Speichermanager NPool dar. Denn exakt zum autom. Aufruf von ._Release + ._RefCount = 0 wird der Interface Record als freier Record im Pool des
DEC Speichermanagers eingeordnet.
Das hat zB. zur Folge das bei der Berechnung von Pi zu 1 Million Dezimalstellen normalerweise ca. 250.000 mal ein Speicherbereich als Record-Interface vom Borland Speichermanager erzeugt werden müsste. Der Speicherpool NPool reduziert aber diese 250.000 Aufrufe auf ca. 1.000 tatsächlich Speicheranforderungen an den Borland MM. Im Falle Elliptischer Curven Berechnungen habe ich dabei eine Performancsteigerungvon ca. 10% erreicht. D.h. die IInteger Interfacemethoden stellen die Grndlage dafür dar, das man deren Speicherbereiche durch einen eigenen und optimierteren Speichermanager verwalten kann.
4.) das der Speichermanager im DECMath nun fast unabhängig vom Borland MM ist kann dieser auch speziell für Multi-Threaded Berechnungen optimiert werden. -> heist: NPool arbeitet Thread bezogen, jeder Thread installiert seinen eigenen NPool -> ergo bei IInteger-Allokationen verschiedener Thread kommen diese sich nur minimal in die Quere -> ergo kein Locking per RTLCriticalSection's wird benötigt -> ergo viel mehr Performance
5.) da der NPool als stark frequentierte Schnittstelle arbeitet, kann er dazu benutzt werden um ein Polling auf
GUI Ebene durchzuführen. D.h. NPool ist auch verantwortlich das Computations-Management zu steuern, sprich über Benutzerinstallierbare Callback Interfaces kann der Programmierer periodisch Application.ProcessMesages aufrufen, oder in der Callback übder Abort eine Berechnung abbrechen, oder den zeitlichen- berchnungsmäßigen Fortschritt anzeigen. Für all diese Operationen muß der Programmierer aber NICHT in seinem mathematischem Code rumwurschteln. Man kennt das ja: man baut komplizierte Funktionen und muß darin das
GUI mit Progressbars, Buttonabfragen usw. einbauen. Man muß also zeri komplette getrennte Sachen vermischen und verliert den Überblick. Nun, NPool implementiert also ein asynchrones, intervall-zeitgesteuertes Polling während der mathematischen Berechnungen.
6.) da nur die internen Funktionen im DECMath einen IInteger allozieren können, spricht man auch von Auto-allokatoren. D.h. du als Benutzer kümmerst dich NIE um die Allozierung, Zuweisung, Kopierung bei Veränderungen von IInteger Interfaces. Somit snd IInteger wie zB. LongStrings der
RTL, Datenspeicher mit Autoallokation, Garbage Collections und Copy on Write Demand Feature.
Du kannst als zB. auch
Delphi-Quellcode:
var
A,B,C: IInteger;
begin
// 1.) <- A,B,C werden automatisch mit NIL initialisiert
NSet(A, 1);
// 2.) <- A wurde intern automatisch alloziert und mit 1 gefüllt
B := A;
C := B;
// 3.) <- Zuweisung von Variablen bedeutet das A,B,C auf das gleiche Object zeigen, wie bei Strings
NInc(B);
// 4.) <- Veränderungen an B im Inhalt führen zum "Copy on Write Demand" Feature,
// B bekommt ein neue Interfacekopie alloziert, mit Inhalt '1'
C := B;
// 5.) <- Interface A,C wird im refCounting um 1 dekremetiert,
// und C zeigt auf B, B Interface +1 im RefCounter
// 6.) <- über unsichtbare try finally Blöcke sind ALLE Interface Variablen im Delphi geschützt.
// hier also try finally, mit _Release(InterfaceArray[0..2]->@A)
// der Compiler legt also die Variablen A,B,C so im Speicher ab das sie in einem Array[0..2] of IInteger liegen
// somit kann er schnellere, Looporientierte Funktionen zum Freigeben dieser Interfaces benutzen.
end;
Schaue mal hier in die Code Library rein, da müsste es einen weitergehenden Beitrag zu meinem Interface-Trick -> "forged Interface" zu finden sein.
Gruß Hagen
PS: ein IInteger wie A := nil wird mathematisch als 0 betrachtet, so gesehen gibt es KEINEN logischen Zusammenhang zwischen einen allozierten IInteger Interface und einem NIL Interface. Mathematisch sind BEIDES Zahlen, die eine <> 0 die andere == 0. Dies vereinfacht die Denkweise wie man IInteger benutzen kann, sie sind
fast ordinale Datentypen wie Integer, Comp, Real, Double etc. Am besten vergleichbar mit LongStrings.
Historisch gesehen sind IInteger die 3. Generation, nach zwei versuchen mit Objecten die wesentlich unhandlicher waren.