Variablenbezüge in Assembler innerhalb Pascal

Diskussion zum Thema Programmierung unter DOS (Intel x86)
Brueggi

Re: Variablenbezüge in Assembler innerhalb Pascal

Beitrag von Brueggi »

Ach herrje... jetzt komm ich schon nicht mehr ins Forum rein - angeblich ist das PW immer falsch.. seelltsaaam - erst nach der Sicherheitsabfrage gings :-))) (dabei hab ich das PW mit Copy+Paste übernommen...)

Ihr beschäftigt Euch mit Code-Optimierung und wünscht mir gutes Gelingen mit dem PnP... So So. ;-))
Ihr Freaks! :-P

Also ich will jetzt nix sagen - aber das PnP auf dem PC ist wirklich "krank" - wenn ich mir da die elegante Amiga-Methode anschaue (jede Karte liegt nach dem Einschalten an $E80000 - nacheinander werden die Slots eingeschaltet und die neue Basis-Adresse an $E80000 geschrieben - und schon ist die Karte konfiguriert :-D )

Aber das ist mir schon klar, dass es am PC wieder über 20 Ecken geht :-)))) Da muss ich mich halt mal durchfummeln - hilft ja nix.
Benutzeravatar
Dosenware
DOS-Gott
Beiträge: 3745
Registriert: Mi 24. Mai 2006, 20:29

Re: Variablenbezüge in Assembler innerhalb Pascal

Beitrag von Dosenware »

wobo hat geschrieben: Ursache der unterschiedlichen Laufzeiten ein und derselben Routine auf demselben PC ist das Code Alignment. Denn die Laufzeiten stehen in direkter Relation zu den Startoffsets der procedure, wie sich aus der Tabelle ergibt.
Problem an der These ist: ich habe 2 mal dieselbe Routine (Code ist also Identisch) definiert, beide auf einen Startoffset mod16 geschoben aber unterschiedliche Laufzeiten bekommen.
brueggi hat geschrieben: das PnP auf dem PC ist wirklich "krank"
mein reden...
Benutzeravatar
Dosenware
DOS-Gott
Beiträge: 3745
Registriert: Mi 24. Mai 2006, 20:29

Re: Variablenbezüge in Assembler innerhalb Pascal

Beitrag von Dosenware »

Gibt es eine Art "SizeOf(procedure)" in Pascal?
wobo
DOS-Guru
Beiträge: 613
Registriert: So 17. Okt 2010, 14:40

Re: Variablenbezüge in Assembler innerhalb Pascal

Beitrag von wobo »

Dosenware hat geschrieben:
wobo hat geschrieben: Ursache der unterschiedlichen Laufzeiten ein und derselben Routine auf demselben PC ist das Code Alignment. Denn die Laufzeiten stehen in direkter Relation zu den Startoffsets der procedure, wie sich aus der Tabelle ergibt.
Problem an der These ist: ich habe 2 mal dieselbe Routine (Code ist also Identisch) definiert, beide auf einen Startoffset mod16 geschoben aber unterschiedliche Laufzeiten bekommen.
Ich hatte doch geschrieben, ich wünsche keinen Widerspruch ?!? ( ;-) )

Ich habe leider keinen Pentium, um das zu prüfen. Aber vielleicht benötigt der Pentium auch Offset-Ausrichtungen an Modulo 32. Schließlich erfolgte (nach meiner These) ja auch vom 386sx auf den 486dx ein Sprung von Modulo 4 nach Modulo 16. Der Pentium hat ja zwei Pipelines. Da erscheint mir ein modulo 32 nicht abwegig, wenn insoweit 486dx-Technologie weiterentwickelt wurde.

Vielleicht kommen beim Pentium einfach noch zusätzliche Kriterien hinzu, wie z.B. die Sprungvorhersage (hat doch der Pentium, oder?).

Dosenware hat geschrieben:Gibt es eine Art "SizeOf(procedure)" in Pascal?
Ja: SizeOf(procedure)

Edit: Nein, SizeOf(procedure) gibt es nicht! Falschen Code gelöscht. Danke Dosenware.
Zuletzt geändert von wobo am Sa 26. Mai 2012, 20:08, insgesamt 1-mal geändert.
Benutzeravatar
Dosenware
DOS-Gott
Beiträge: 3745
Registriert: Mi 24. Mai 2006, 20:29

Re: Variablenbezüge in Assembler innerhalb Pascal

Beitrag von Dosenware »

ahh, ich hatte nur @ und ^ probiert, aber nicht beides gleichzeitig.

jetzt wird das Testprogramm umgebaut:

2x Pchar (64kbyte Speicher), 2 Zeiger auf Procedure, Hin und herschieben des Codes + Messung -> Automatisches Codealignment mit Messung auf beste Performance... so langsam näherere ich mich dem Isapnp, zumindest was den "Boah, ist das Krank"-Faktor angeht :twisted:
Vielleicht kommen beim Pentium einfach noch zusätzliche Kriterien hinzu, wie z.B. die Sprungvorhersage
ist aber seltsam das zwei komplett identische Routinen unterschiedliche Ergebnisse liefern.

EDIT: geht nicht, mit Procedure(x:variable); will er das Argument - außerdem liefert das Konstrukt 0 zurück

EDIT: ich werde das wohl vorher anders lösen:
Procedure Test1;
Procedure Test2; {Startoffset-1 ist das Ende von Procedure Test1}
Procedure Dummy;{Startoffset-1 ist das Ende von Procedure Test2}
Benutzeravatar
Dosenware
DOS-Gott
Beiträge: 3745
Registriert: Mi 24. Mai 2006, 20:29

Re: Variablenbezüge in Assembler innerhalb Pascal

Beitrag von Dosenware »

Boah, Pascal ist echt eine Diva wenn es um das verschieben von Proceduren geht - es weigert sich einfach korrekte Adressen auszugeben/anzunehmen... aber ich glaube ich habs: der Schlüssel ist (mal wieder) "absolute" (der Parameter wird so langsam mein persönlicher Held)

Code: Alles auswählen

Program copytest;
uses crt;
Procedure Testdummy1;far;
begin
writeln('it works');
end;
Procedure Testdummy2;far;
begin
asm
nop
end
end;
type procpntr=procedure;
var pntr1,pntr2,pntrh:Pointer;
var pntr1a:array[0..1] of word absolute pntr1; {Ofs:Seg}
var pntr2a:array[0..1] of word absolute pntr2; {Ofs:Seg}
var pntrha:array[0..1] of word absolute pntrh;
var testPchar:pchar;
var pcharpntr:array[0..1] of word absolute testPchar;
var sizeTd1:word;
var proccpntr:^procpntr;
var pntrproc:array[0..1] of word absolute proccpntr;
begin
pntr1:=@Testdummy1;
pntr2:=@Testdummy2;
Writeln('Addr. Testdummy1: ',seg(Testdummy1),':',Ofs(Testdummy1));
Writeln('Addr. Testdummy1: ',pntr1a[1],':',pntr1a[0]);
Writeln('Addr. Testdummy2: ',seg(Testdummy2),':',Ofs(Testdummy2));
Writeln('Addr. Testdummy2: ',pntr2a[1],':',pntr2a[0]);
if pntr1a[1]=pntr2a[1] then sizeTd1:=pntr2a[0]-pntr1a[0];
writeln('sizeof Testdummy1: ',sizetd1);
getmem(testPchar,sizetd1);
move(pntr1,testpchar^,sizetd1);
pntrproc[1]:=pcharpntr[1];
pntrproc[0]:=pcharpntr[0];
Writeln('Addr. Programptr: ',pntrproc[1],':',pntrproc[0]);
readln;
proccpntr^;

readln;
end.
Gibt es eigentlich so eine Art Variablen für den Editor? D.h. etwas der Art:

Define x=Blah
begin;x;end.

Beim Kompilieren soll x durch Blah ersetzt werden - das '#define' das mir Bp7 in der Hilfe anbietet führt nur zu einer fehlerhaften Integerkonstante.
Benutzeravatar
Dosenware
DOS-Gott
Beiträge: 3745
Registriert: Mi 24. Mai 2006, 20:29

Re: Variablenbezüge in Assembler innerhalb Pascal

Beitrag von Dosenware »

Hmm, grade nach ewiger suche gemerkt: BP7 mag "(*)" nicht (gibt die merkwürdigsten Fehler) - hat wer eine Idee was das macht? in der Hilfe ist nichts dazu zu finden.

EDIT: is klar, (*) macht auskommentieren, auch wenn der Editor das nicht anzeigt.
wobo
DOS-Guru
Beiträge: 613
Registriert: So 17. Okt 2010, 14:40

Re: Variablenbezüge in Assembler innerhalb Pascal

Beitrag von wobo »

Dosenware hat geschrieben:Boah, Pascal ist echt eine Diva wenn es um das verschieben von Proceduren geht - es weigert sich einfach korrekte Adressen auszugeben/anzunehmen... aber ich glaube ich habs: der Schlüssel ist (mal wieder) "absolute" (der Parameter wird so langsam mein persönlicher Held)

Code: Alles auswählen

Program copytest;
uses crt;
Procedure Testdummy1;far;
begin
writeln('it works');
end;
Procedure Testdummy2;far;
begin
asm
nop
end
end;
type procpntr=procedure;
var pntr1,pntr2,pntrh:Pointer;
var pntr1a:array[0..1] of word absolute pntr1; {Ofs:Seg}
var pntr2a:array[0..1] of word absolute pntr2; {Ofs:Seg}
var pntrha:array[0..1] of word absolute pntrh;
var testPchar:pchar;
var pcharpntr:array[0..1] of word absolute testPchar;
var sizeTd1:word;
var proccpntr:^procpntr;
var pntrproc:array[0..1] of word absolute proccpntr;
begin
pntr1:=@Testdummy1;
pntr2:=@Testdummy2;
Writeln('Addr. Testdummy1: ',seg(Testdummy1),':',Ofs(Testdummy1));
Writeln('Addr. Testdummy1: ',pntr1a[1],':',pntr1a[0]);
Writeln('Addr. Testdummy2: ',seg(Testdummy2),':',Ofs(Testdummy2));
Writeln('Addr. Testdummy2: ',pntr2a[1],':',pntr2a[0]);
if pntr1a[1]=pntr2a[1] then sizeTd1:=pntr2a[0]-pntr1a[0];
writeln('sizeof Testdummy1: ',sizetd1);
getmem(testPchar,sizetd1);
move(pntr1,testpchar^,sizetd1);
pntrproc[1]:=pcharpntr[1];
pntrproc[0]:=pcharpntr[0];
Writeln('Addr. Programptr: ',pntrproc[1],':',pntrproc[0]);
readln;
proccpntr^;

readln;
end.
Gibt es eigentlich so eine Art Variablen für den Editor? D.h. etwas der Art:

Define x=Blah
begin;x;end.

Beim Kompilieren soll x durch Blah ersetzt werden - das '#define' das mir Bp7 in der Hilfe anbietet führt nur zu einer fehlerhaften Integerkonstante.
Erst einmal dickes Sorry für mein SizeOf(@proc^). Das war ja total falsch. Ich teste zwar Code, bevor ich hier irgendwas poste, aber damals habe ich wohl geschlafen. Die Größe einer Procedure zu bestimmen, geht nur so, wie Du es gemacht hast.

Deinen obigen Code finde ich allerdings sehr pnp-like. Ich konnte das leider bisher nicht testen. Du versuchst doch den Code von Testdummy1 auf den Heap (nach testpChar^) zu verschieben?

Unabhängig davon, glaube ich, dass ein Code - Verschieben in 90% der Fälle nicht erfolgreich ist. Wenn Du den Code verschiebst, dann müsstest Du doch auch alle Loop-Labels, die Pascal angelegt hat, ebenfalls anpassen. Ich wüßte jetzt nicht, wie das gehen sollte?
Benutzeravatar
Dosenware
DOS-Gott
Beiträge: 3745
Registriert: Mi 24. Mai 2006, 20:29

Re: Variablenbezüge in Assembler innerhalb Pascal

Beitrag von Dosenware »

realative Sprünge sind kein Problem - also: passt schon, denn innerhalb deines UP wirst du kaum absolute Sprünge finden - es sei denn es ist sehr groß.
Benutzeravatar
Dosenware
DOS-Gott
Beiträge: 3745
Registriert: Mi 24. Mai 2006, 20:29

Re: Variablenbezüge in Assembler innerhalb Pascal

Beitrag von Dosenware »

var x:Pointer
var xar:array:[0..1] of byte absolute x;

-.-' - was für ein hässlicher Fehler - noch dazu einer den ich ewig gesucht habe... *WILDEFLÜCHE*

vorläufiges Ergebnis:

Code: Alles auswählen

uses crt,dos;

const startstop:word=1;                         {Startstopflag für die Messung}
const error:word=0;                             {Fehlerflag}

const intcount:word=0;                          {Zählvariable}
const lauf1:comp=0;                             {Laufvariablen}
const lauf2:comp=0;

var laufw1:array[0..3]of word absolute lauf1;
var laufw2:array[0..3]of word absolute lauf2;
var laufh0,laufh1,laufh2,laufh3:word;
var offsetTest1,offsetTest2:word;
var w:char;                                     {Tastaturinput}
var oInt:procedure;                             {alter Int}
var sizeofTest1,sizeofTest2:word;


(*#######################Insert Coin here#############################*)

const Ticks=200;                                {Anzahl der Interrupts}

Const NameTest1='Testdummy1';                 {für Übersichtlichkeit bitte auf gleiche Länge achten}
Const NameTest2='Testdummy2';                 {weiter unten Proceduren einsetzen}

Type Testproc=Procedure;                  {Muss dem Unterprogrammkopf der zu testenden Routine entsprechen}
var testbase1,testbase2:Pointer;             {Zeiger auf Unterprogramme}
var test:^Testproc;
var testpntrh:array[0..1]of word absolute test;
var testbase1pntrh:array[0..1]of word absolute testbase1;
var testbase2pntrh:array[0..1]of word absolute testbase2;
var testmem:Pchar;
var testmempntr:array[0..1] of word absolute testmem;

procedure Testdummy1;far;var i:word;
 begin;for i:=0 to 65535 do begin;end;{writeln('it works');}end;

procedure Messdummy1;far;begin;end;

procedure Testdummy2;far;var i:word;
 begin;for i:=0 to 65535 do begin;end;end;

procedure Messdummy2;far;begin;end;
(*/*)


procedure MInt;Interrupt;
 begin
 inc(intcount);
 if  intcount>=Ticks then startstop:=1;
 oInt;
 end;

procedure WaitForInt;Interrupt;
 begin
 startstop:=0;
 oInt;
 SetIntVec($1C,@MInt);
 end;

begin
(*#######################Insert Coin here#############################*)
testbase1:=@Testdummy1;
testbase2:=@Testdummy2;
(*/*)
Messdummy1;Messdummy2;
if testbase1pntrh[1]=Seg(Messdummy1) then sizeOfTest1:=Ofs(Messdummy1)-testbase1pntrh[0] else error:=1;
if testbase2pntrh[1]=Seg(Messdummy2) then sizeOfTest2:=Ofs(Messdummy2)-testbase2pntrh[0] else error:=1;
getmem(testmem,16384);

writeln('(S)tandardtest oder (E)rweiterter Test?');
w:=readkey;
if w='s' then
 begin
 writeln('Starte Messung');
 laufh0:=0;laufh1:=0;laufh2:=0;laufh3:=0;

 {verschieben und Zeiger setzen mal in n UP packn}
 testpntrh[0]:=testbase1pntrh[0];
 testpntrh[1]:=testbase1pntrh[1];
 move(testbase1,testmem^,sizeOfTest1);
 testpntrh[1]:=testmempntr[1];{!!Addressübergabe}
 testpntrh[0]:=testmempntr[0];
 {/}

 {Als UP leider nicht lauffähig, vmtl. Absturz bei setzen von Startstop}
 GetIntVec($1C,@oInt);
 SetIntVec($1C,@WaitForInt);
 repeat;until startstop=0;
  repeat;
  asm
   add laufh0,1
   adc laufh1,0
   adc laufh2,0
   adc laufh3,0
   jnc @end
   mov error,1
  @end:
  end;
(*#######################Insert Coin here#############################*)
  test^; {Up Aufruf über die Variable Test}
(*/*)
 until startstop=1;
{ }
 SetIntVec($1C,@oInt);
 laufw1[0]:=laufh0;laufw1[1]:=laufh1;laufw1[2]:=laufh2;laufw1[3]:=laufh3;
 laufh0:=0;laufh1:=0;laufh2:=0;laufh3:=0;
 intcount:=0;

 {verschieben und Zeiger setzen mal in n UP packn}
 testpntrh[0]:=testbase1pntrh[0];
 testpntrh[1]:=testbase1pntrh[1];
 move(testbase1,testmem^,sizeOfTest1);
 testpntrh[1]:=testmempntr[1];{!!Addressübergabe}
 testpntrh[0]:=testmempntr[0];
 {/}

 {Als UP leider nicht lauffähig, vmtl. Absturz bei setzen von Startstop}
 SetIntVec($1C,@WaitForInt);
 repeat;until startstop=0;
  repeat;
  asm
   add laufh0,1
   adc laufh1,0
   adc laufh2,0
   adc laufh3,0
   jnc @end
   mov error,1
  @end:
  end;
(*#######################Insert Coin here#############################*)
  test^; {Up Aufruf über die Variable Test}
(*/*)
 until startstop=1;
{ }
 SetIntVec($1C,@oInt);
 laufw2[0]:=laufh0;laufw2[1]:=laufh1;laufw2[2]:=laufh2;laufw2[3]:=laufh3;
 if error=1 then writeln('ERROR, ueberlauf');
 writeln('Ticks    : ',Ticks);
 writeln(NameTest1,' ',lauf1:0:0,' Durchlaeufe. Offset: ',offsetTest1,' Groesse: ',sizeofTest1);
 writeln(NameTest2,' ',lauf2:0:0,' Durchlaeufe. Offset: ',offsetTest2,' Groesse: ',sizeofTest2);
 writeln;
 if lauf1>lauf2 then
  begin
  lauf1:=lauf1-lauf2;
  writeln(NameTest1,' ',lauf1:0:0,' Durchlaeufe schneller.');
  end
 else
  begin
  lauf1:=lauf2-lauf1;
  writeln(NameTest2,' ',lauf1:0:0,' Durchlaeufe schneller.');
  end;
 sound(2000);
 delay(2000);
 nosound;
 readln;
 end {/standardtest}
else if w='e' then
begin; {erweiterter Test}
 writeln('kommt noch');
 readln;
end; {/erweiterter Test}
dispose(testmem);
end.
zumindest ist der Code jetzt immer an derselben Adresse müsste jetzt also in seiner Ausführungsgeschwindigkeit identisch sein - der Codealignmenttest ("Erweitert") wird noch eingebaut (verschiebung bis 128byte wird gemessen - Laufzeit etwa 46Minuten... werde wohl für den Alignmenttest eine Abfrage einbauen - allerdings sollten die Durchlaufzahlen mindestens das Doppelte der Ticks betragen...
wobo
DOS-Guru
Beiträge: 613
Registriert: So 17. Okt 2010, 14:40

Re: Variablenbezüge in Assembler innerhalb Pascal

Beitrag von wobo »

Hallo Dose,

wieder einmal muss ich feststellen, dass sich die Welt auch im Retro-Bereich viel schneller dreht, als ich ihr folgen kann. Deswegen habe ich es erst jetzt geschafft, mich wieder mit dem Thema zu befassen.

Hast Du bzgl. des Code-Alignments auf dem Pentium schon ein Ergebnis?

Ich wollte Dein Testprogramm auf meinen PCs laufen lassen. Allerdings hatte ich da das Problem, dass der Variablentyp COMP wegen des fehlenden Coprozessors auf meinem 386sx16 nicht läuft. Da ich die ganzen ABSOLUTE-Anweisungen wenig verwirrend fand (ich bin ja noch am Programmieren-Üben), habe ich mal Deine erste Version genommen, die ich vor ein paar Wochen schon ein Mal angepaßt hatte.

Ich habe das Programm übrigens jetzt so modifiziert, dass es auch auf einem 286-er laufen sollte.

Das Programm verschiebt das zu testende Code-Schnipsel (PROCEDURE TestProc) 64 Mal byteweise durch das Code-Segment. Die Procedure beginnt dabei an einer durch 64 teilbaren Speicheradresse, was durch Aufruf der PROCEDURE AlignByBruteForce erreicht wird. Damit das Verschieben zu keinem Überschreiben von anderem Code führt, ist unmittelbar hinter TestProc die PROCEDURE EndOfTestProc definiert, die zum einen zum Ermitteln der Größe von TestProc notwendig ist und die zum anderen mind. 64 byte umfasst, damit hier die TestProc sukzessive drüber geschoben werden kann, ohne dass etwas relevantes zerstört wird.

Was das Listing jetzt etwas länger macht, ist, dass versucht wird die Textdatei "bench.txt" zu laden und die neuen Testergebnisse ansatzweise formatiert hinzuzufügen.


Beim Programmieren ist mir wieder mal aufgefallen, dass ich das Type-Casting mindestens so gerne liebe wie Du das ABSOLUTE. Im Ergebnis ist es ja das gleiche: Eine Variable verschiedenen Typs erhält immer die gleiche Speicherstelle und kann deswegen unterschiedlich interpretiert werden.


Hier mal das Listing:

Code: Alles auswählen

USES  crt,dos;

CONST Ticks=200;                       {Anzahl der Interrupts}
      startstop:word=1;                {Startstopflag fuer die Messung}
      error:word=0;                    {Fehlerflag}
      intcount:word=0;                 {Zaehlvariable}



TYPE  tEasyPtr = record                {TypeCasting zur Pointer-Behandlung}
        ofs, sgm : word;
      end;

      tMyProc = record                 {TypeCasting zur Procedure-Behandlung}
        case byte of
          0: (ProcVar : procedure);
          1: (ProcPtr : pointer);
          2: (Ofs,Sgm : word);
      end;

      tLauf = record                   {TypeCasting fuer DWORD }
        LoWord, HiWord : word;
      end;

VAR
      lauf       : tLauf;
      i          : integer;

      MyProc     : tMyProc;
      ProcAdr,
      ProcEnd    : pointer;
      ProcSize   : word;

      oInt       : procedure;                  {alter Int}

      t          : array[0..63] of string;     {nur zum Abspeichern...}
      pcIDstr,                                 {...der gebenchten Werte...}
      Titelzeile : string;                     {...in bench.txt}



{ *** gibt ein Word als hexadezimalen String aus *** }
FUNCTION HexW( x:word ) : string;
CONST  Ziffer : array[0..15] of char='0123456789ABCDEF';
BEGIN
  HexW[0]:=#4;
  HexW[1]:=Ziffer[hi(x) shr $4];
  HexW[2]:=Ziffer[hi(x) and $F];
  HexW[3]:=Ziffer[lo(x) shr $4];
  HexW[4]:=Ziffer[lo(x) and $F];
END;

{ *** wandelt ein Integer in einen rechtszentrierten String *** }
FUNCTION FormatInt( l : longint; stellen : byte; FormatChar : char ) : string;
VAR s : string;
    i : byte;
BEGIN
  Str( l, s );
  if Length(s) < stellen then
    for i := Length(s)+1 to stellen do s:=FormatChar+s;
  FormatInt := s;
END;

{ *** erstellt aus String einen rechtszentrierten String *** }
FUNCTION FormatString( s:string; Stellen:byte ) : string;
VAR temp:byte;
BEGIN
  if Stellen > Length(s) then
    for temp := 1 to (Stellen-Length(s)) do s := ' '+s;
  FormatString := s;
END;

{ *** Lade bench.txt, falls vorhanden *** }
PROCEDURE LoadBenchText;
VAR i : integer;
    f : text;
BEGIN
  TitelZeile := '          ';
  for i := 0 to 63 do t[i] := 'MOD64='+FormatInt(i mod 64,2,'0')+'  ';
  assign( f, 'bench.txt' );
  {$I-}  reset( f );  {$I+}
  if IOResult=0 then
  begin
    readln( f, Titelzeile );
    i := 0;
    repeat
      readln( f, t[i] );
      inc(i);
    until EoF(f) or (i=64);
    close( f );
  end;
END;

{ *** Speichere das aktualisierte bench.txt *** }
PROCEDURE SaveBenchText( CPUname : string );
VAR i : integer;
    f : text;
BEGIN
  TitelZeile := TitelZeile+FormatString(CPUname,8)+' ';
  assign( f, 'bench.txt' );
  rewrite( f );
  writeln( f, Titelzeile );
  for i := 0 to 63 do writeln( f, t[i] );
  close( f );
END;


{ *** Dosenware`s Timing-Routinen *** }
PROCEDURE MInt; INTERRUPT;
BEGIN
  inc(intcount);
  if  intcount>=Ticks then startstop:=1;
  oInt;
END;
PROCEDURE WaitForInt; INTERRUPT;
BEGIN
  startstop:=0;
  oInt;
  SetIntVec($1C,@MInt);
END;

{ *** Durch Auszaehlen bestimmt: TestProc beginnt bei Modulo64=0 *** }
PROCEDURE AlignByBruteForce;
BEGIN
  asm
    inc bx; inc bx; inc bx; inc bx; inc bx; inc bx; inc bx; inc bx;
    inc bx; inc bx; inc bx; inc bx; inc bx; inc bx; inc bx; inc bx;

    inc bx; inc bx; inc bx; inc bx; inc bx; inc bx; inc bx; inc bx;
    inc bx; inc bx; inc bx; inc bx; inc bx; inc bx; inc bx; inc bx;

    inc bx; inc bx; inc bx; inc bx; inc bx; inc bx; inc bx; inc bx;
    inc bx; inc bx; inc bx; inc bx; inc bx; inc bx; inc bx;
  end;
END;

{$F+}
PROCEDURE TestProc; VAR i:word;
 BEGIN for i:=0 to 65535 do i:=i; END;
{$F-}

PROCEDURE EndOfTestProc; ASSEMBLER;
asm
  inc bx; inc bx; inc bx; inc bx; inc bx; inc bx; inc bx; inc bx;
  inc bx; inc bx; inc bx; inc bx; inc bx; inc bx; inc bx; inc bx;

  inc bx; inc bx; inc bx; inc bx; inc bx; inc bx; inc bx; inc bx;
  inc bx; inc bx; inc bx; inc bx; inc bx; inc bx; inc bx; inc bx;

  inc bx; inc bx; inc bx; inc bx; inc bx; inc bx; inc bx; inc bx;
  inc bx; inc bx; inc bx; inc bx; inc bx; inc bx; inc bx; inc bx;

  inc bx; inc bx; inc bx; inc bx; inc bx; inc bx; inc bx; inc bx;
  inc bx; inc bx; inc bx; inc bx; inc bx; inc bx; inc bx; inc bx;
end;


BEGIN
  TextMode( co80+Font8x8 );
  EndOfTestProc;     { Dummy-Aufruf, damit TP das nicht wegoptimiert }
  AlignByBruteForce; { dto. }

  LoadBenchText;

  writeln( 'Bitte PC-ID-String (z.B.:`386sx16`, max. 8 Zeichen!) eingeben:');
  readln(  pcIDstr );
  if Length( pcIDstr )>8 then Delete( pcIDstr, 9, 255 );

  MyProc.ProcVar := TestProc;
  ProcAdr    := @TestProc;
  ProcEnd    := @EndOfTestProc;
  ProcSize   := tEasyPtr(ProcEnd).ofs - tEasyPtr(ProcAdr).ofs;

  writeln( 'Teste Routine (Groesse: ', ProcSize, ' byte) bei: ' );
  for i := 0 to 63 do
  begin

    write( 'þ '+ HexW( MyProc.sgm )+':'+HexW( MyProc.ofs ) +
           ' [MOD4=', MyProc.ofs mod 4:2,
           ', MOD64=', MyProc.ofs mod 64:2, '] ...' );

    lauf.LoWord := 0;
    lauf.HiWord := 0;

    intCount    := 0;
    startstop   := 1;
    GetIntVec($1C,@oInt);
    SetIntVec($1C,@WaitForInt);

    repeat until startstop=0;
    repeat
      asm
       add lauf.LoWord, 1           { 32 bit ought to be enough for }
       adc lauf.HiWord, 0           { everybody... }
       jnc @end
         mov error, 1               { ... Bill Gates is lying. }
       @end:
      end;

      MyProc.ProcVar;               { Aufruf der Testroutine }

    until startstop=1;

    SetIntVec($1C,@oInt);

    if error=1 then writeln(' ERROR, Ueberlauf')
               else writeln(' ',longint(lauf),' Durchlaeufe.');

    t[i] := t[i]+ FormatInt(longint(lauf),6,' ')+'   '; {fuer bench.txt}

    ProcAdr := MyProc.ProcPtr;			{ alte Startadr. merken }
    Inc( MyProc.Ofs );				{ neuer Start ein Byte weiter }
    Move( ProcAdr^, MyProc.ProcPtr^, ProcSize );{ verschieben; Pascal berueck- } 
  end;						{ sichtigt Ueberschneidungen }

  writeln( 'Press Enter!' ); ReadLn; 
  SaveBenchText( pcIDstr );

  TextMode( co80 );
END.
Für den Fall, dass jemand die lauffähige Exe auf seinem PC ausprobieren möchte, habe ich die EXE nebst Source und Bench.txt hier hochgeladen:


http://uploading.com/files/d4c74m65/DosenwareBench.zip/



Die Bench-Ergebnisse kommen im nächsten Beitrag.
wobo
DOS-Guru
Beiträge: 613
Registriert: So 17. Okt 2010, 14:40

Re: Variablenbezüge in Assembler innerhalb Pascal

Beitrag von wobo »

Wie vorhin geschrieben, hier die Ergebnisse:

Code: Alles auswählen

           386sx16  386dx25 486dx100 duron750 

MOD64=00      56      155     1397    10267   
MOD64=01      62      155     1397    11974   
MOD64=02      59      146     1397    11972   
MOD64=03      58      146     1397    10073   
MOD64=04      56      155     1397    10073   
MOD64=05      62      155     1397    10073   
MOD64=06      59      146     1397    11712   
MOD64=07      58      146     1397    11260   
MOD64=08      56      155     1397    11257   
MOD64=09      62      155     1289    11965   
MOD64=10      59      146     1289     8994   
MOD64=11      58      146     1397    10080   
MOD64=12      56      155     1397    10081   
MOD64=13      62      155     1397     9940   
MOD64=14      59      146     1397    11712   
MOD64=15      58      146     1397    10267   
MOD64=16      56      155     1397    10267   
MOD64=17      62      155     1397    11973   
MOD64=18      59      146     1397    11971   
MOD64=19      58      146     1397    10073   
MOD64=20      56      155     1397    10073   
MOD64=21      62      155     1397    10073   
MOD64=22      59      146     1397    11712   
MOD64=23      58      146     1397    11261   
MOD64=24      56      155     1397    11257   
MOD64=25      62      155     1289    11965   
MOD64=26      59      146     1289     8994   
MOD64=27      58      146     1397    10081   
MOD64=28      56      155     1397    10081   
MOD64=29      62      155     1397     9940   
MOD64=30      59      146     1397    11712   
MOD64=31      58      146     1397    10267   
MOD64=32      56      155     1397    10267   
MOD64=33      62      155     1397    11973   
MOD64=34      59      146     1397    11973   
MOD64=35      58      146     1397    10073   
MOD64=36      56      155     1397    10073   
MOD64=37      62      155     1397    10073   
MOD64=38      59      146     1397    11712   
MOD64=39      58      146     1397    11261   
MOD64=40      56      155     1397    11257   
MOD64=41      62      155     1289    11964   
MOD64=42      59      146     1289     8994   
MOD64=43      58      146     1397    10082   
MOD64=44      56      155     1397    10081   
MOD64=45      62      155     1397     9940   
MOD64=46      59      146     1397    11712   
MOD64=47      58      146     1397    10267   
MOD64=48      56      155     1397    10267   
MOD64=49      62      155     1397    11975   
MOD64=50      59      146     1397    11971   
MOD64=51      58      146     1397    10073   
MOD64=52      56      155     1397    10073   
MOD64=53      62      155     1397    10073   
MOD64=54      59      146     1397    11712   
MOD64=55      58      146     1397    11261   
MOD64=56      56      155     1397    11258   
MOD64=57      62      155     1289    11963   
MOD64=58      59      146     1289     8994   
MOD64=59      58      146     1397    10083   
MOD64=60      56      155     1397    10081   
MOD64=61      62      155     1397     9941   
MOD64=62      59      146     1397    11712   
MOD64=63      58      146     1397    10267   

Die erste Spalte gibt das Modulo des Procedure-Beginns an. Die nachfolgenden Spalten beinhalten die Durchläufe auf dem 386sx16, dem 386dx25, dem 486dx4-100 und dem amd duron 750 Mhz.


Ich versuche das mal mutig zu interpretieren ;-):

(1) 386sx16

Der 386sx mag offensichtlich ein dword alignment. Wie schon mal geschrieben, hat mich das sehr überrascht: der 386sx holt sich den auszuführenden Code ja über einen 16-bittigen Datenbus, so dass ich ein word alignment erwartet hatte. Ursache dürfte aber sein, dass der 386sx wie der 386dx seine Prefetch-Queue immer nur dann auffüllt, wenn dort 4 byte frei sein.


(2) 386dx25

Hier gab`s für mich die nächste Überraschung. Der 386dx25 führt denselben 16bit-Real-Mode-Code fast 3 Mal so schnell aus wie der 386sx16! Ich hatte lediglich einen Faktor von 1,5 erwartet, wie es auch der Fall ist, wenn man nicht die Code-Ausführungszeit, sondern den Durchsatz von 16bit-Daten (REP STOSW) mißt. Aber Code und Daten sind zwei unterschiedliche Schuhe, zumal die einzelnen Code-Befehle auf x86 in ihrer Größe ziemlich unterschiedlich sind. Der 386dx kann sich den Code - sofern der Hauptspeicher 32-bittig angeschlossen ist - im 4-Byte-Rutsch in seine Prefetch-Queue holen, was doppelt so viel ist wie beim 386sx. Im besten Fall kann daher der 386dx den gleichen Code doppelt so schnell ausführen wie ein gleich getakter 386sx.

Ich muß hier aber dazu sagen, dass mein 386dx25 auf einem 32-bittigen MCA-Board sitzt. Ob der Speicher bei einem 16-bit ISA - Board auch 32-bittig an die CPU angeschlossen ist, weiß ich nicht.


(3) 486dx4 - 100

Der 486dx mag offensichtlich ein Paragraphen-Alignment. Als Ursache tippe ich mal darauf, dass beim 486dx eine Cacheline immer 16 byte groß ist.

Was mich immer wieder erstaunt, ist das Tempo des 486 gegenüber einem 386sx. Wenn ich mal die Best-Case-Werte vergleiche, dann schafft der 386sx 3,875 Durchläufe pro Mhz, der 386dx 6,2 Durchläufe pro Mhz und der 486dx4 13,97 Durchläufe pro Mhz. Da ist der AMD Duron mit 15,97 Durchläufe pro Mhz auch nicht viel schneller! Wenn ich mir die Worst-Case-Szenarien angucke, dann ist der 486dx4 pro Mhz sogar schneller als der AMD Duron: Der 486dx4 schafft im worst case 12,89 Durchläufe pro Mhz, während der AMD Duron nur 11,99 Durchläufe pro Mhz schafft.


(4) Duron 750

Der Duron benötigt offensichtlich ebenfalls nur eine Modulo16 - Ausrichtung. Interessant wäre zu wissen, wie der Pentium sich da verhält. Ich habe gerade gegoogelt: der Pentium hat wohl für jede seiner beiden Pipelines eine 64-Byte Cacheline. Um das Anzutesten müßte man wohl die Testprocedure 256 Mal um ein Byte im Speicher verschieben, um da ein Muster zu erkennen. Mein Programm versagt da bereits an dem Punkt!


Interessant wäre für mich, wie das Ganze auf einem 286, einem 386dx mit Standard ISA-Bus und auf einem Pentium aussieht. Beim 286er wird ja immer gesagt, dass er 286er-Code ca. um 5-10% schneller ausführt als ein gleich getakteter 386sx. Wenn das stimmt, würde ich mal raten, dass der 286er seine Prefetch-Queue word-weise auffüllt und er deswegen bei kleinen OpCodes (1-3 byte) je nach Situation die Prefetch-Queue schneller nachladen als der 386sx. Denn der muß ja immer warten, bis 4 byte frei sind...


Praktische Relevanz hat das Ganze m.E. jedoch nicht. Zum einen genügt es in der Praxis ja nicht, den Procedureanfang zu alignen. Außerdem hat man auf jeder CPU auch noch ein anderes worst/best case - Szenario: z.B. hat der 386sx bei Modulo64=60 ein worst case, der 386dx dagegen hat dort genau ein best case usw. In einer praktischen Umsetzung würde man (jedenfalls ich) wahrscheinlich mehr verschlimmern als verbessern.
Benutzeravatar
Dosenware
DOS-Gott
Beiträge: 3745
Registriert: Mi 24. Mai 2006, 20:29

Re: Variablenbezüge in Assembler innerhalb Pascal

Beitrag von Dosenware »

Moin wobo,

im grunde genommen musst du nur das comp (alles was bei mir lauf* heißt) durch das gleiche Konstrukt ersetzen wie du es bereits schonmal gemacht hast... und unsere beiden Programme arbeiten fast gleich (meines macht 128 shifts) und WTF treibst du da mit bx?
dass ich das Type-Casting mindestens so gerne liebe wie Du das ABSOLUTE. Im Ergebnis ist es ja das gleiche
absulute ist kürzer *g*

Hier ist der letzte Stand (habe jetzt knapp 2Wochen nichts mehr dran gemacht (Urlaub))

Code: Alles auswählen

uses crt,dos,ustrings;

const startstop:word=1;                         {Startstopflag für die Messung}
const error:word=0;                             {Fehlerflag}

const intcount:word=0;                          {Zählvariable}
const lauf1:comp=0;                             {Laufvariablen}
const lauf2:comp=0;

var laufw1:array[0..3]of word absolute lauf1;
var laufw2:array[0..3]of word absolute lauf2;
var laufh0,laufh1,laufh2,laufh3:word;
var offsetTest1,offsetTest2:word;
var w:char;                                     {Tastaturinput}
var oInt:procedure;                             {alter Int}
var sizeofTest1,sizeofTest2:word;
var i:word;                                     {Zählvariable}
const switch:boolean=true;


(*#######################Insert Coin here#############################*)

const Ticks:word=20;                           {Anzahl der Interrupts}
const maxShift=128;                             {Anzahl der Verschiebungen im Speicher bei erweitertem Test}

Const NameTest1='Testdummy1';                   {für Übersichtlichkeit bitte auf gleiche Länge achten}
Const NameTest2='Testdummy2';                   {weiter unten Proceduren einsetzen}

Type Testproc=Procedure;                        {Muss dem Unterprogrammkopf der zu testenden Routine entsprechen}

procedure Testdummy1;far;var i:word;
 begin;for i:=0 to 65535 do begin;end;{writeln('it works');}end;

procedure Messdummy1;far;begin;end;

procedure Testdummy2;far;var i:word;
 begin;for i:=0 to 65535 do begin;end;end;

procedure Messdummy2;far;begin;end;
(*/*)

var testbase1,testbase2:Pointer;                {Zeiger auf Unterprogramme}
var test:^Testproc;
var testmem,testmembase:Pchar;                              {Speichervariable für UP}

type runTest=array[0..maxShift] of record
              Memaddr:longint;
              runs:comp;
             end;

var runTest1,runTest2:runTest;
var testpntrh:array[0..1]of word absolute test; {Hilfvariablen}
var testbase1pntrh:array[0..1]of word absolute testbase1;
var testbase2pntrh:array[0..1]of word absolute testbase2;
var testmempntr:array[0..1] of word absolute testmem;


procedure MInt;Interrupt;
 begin
 inc(intcount);
 if  intcount>=Ticks then startstop:=1;
 oInt;
 end;

procedure WaitForInt;Interrupt;
 begin
 startstop:=0;
 oInt;
 SetIntVec($1C,@MInt);
 end;

procedure Auswertung(var runtest1,runtest2:runtest);
var results:text;
var I:word;
var mHelp1,mHelp2:longint;
var wmHelp1:array [0..1] of word absolute mHelp1;
var wmHelp2:array [0..1] of word absolute mHelp2;
 begin
  assign(results, 'result.txt');
  rewrite(results);
  writeln(results,'AdresseTest1;DurchläufeTest1;AdresseTest2;DurchläufeTest2');
  for i:=0 to maxShift do
   begin;
   mhelp1:=runTest1[i].MemAddr;
   mhelp2:=runTest2[i].MemAddr;
   writeln(results,intToHexstr(wmHelp1[1]),intToHexstr(wmHelp1[0]),';',runTest1[i].runs:0:0,';',
                   intToHexstr(wmHelp2[1]),intToHexstr(wmHelp2[0]),';',runTest2[i].runs:0:0);
   end;
  close(results);
 end;

procedure UPMessung;
begin
  repeat;
  asm
   add laufh0,1
   adc laufh1,0
   adc laufh2,0
   adc laufh3,0
   jnc @end
   mov error,1
  @end:
  end;
(*#######################Insert Coin here#############################*)
  test^;                                        {Up Aufruf über die Variable Test}
(*/*)
 until startstop=1;
end;

begin
(*#######################Insert Coin here#############################*)
testbase1:=@Testdummy1;
testbase2:=@Testdummy2;
(*/*)
Messdummy1;Messdummy2;
if testbase1pntrh[1]=Seg(Messdummy1) then sizeOfTest1:=Ofs(Messdummy1)-testbase1pntrh[0] else error:=1;
if testbase2pntrh[1]=Seg(Messdummy2) then sizeOfTest2:=Ofs(Messdummy2)-testbase2pntrh[0] else error:=1;
getmem(testmem,16384); {max 16kb routinen messbar}
testmembase:=testmem;
writeln('(S)tandardtest oder (E)rweiterter Test?');
w:=readkey;
if w='s' then
 begin
 writeln('Starte Messung');
 laufh0:=0;laufh1:=0;laufh2:=0;laufh3:=0;
 move(testbase1,testmem^,sizeOfTest1);          {Verschieben der Testroutinen}
 testpntrh[1]:=testmempntr[1];                  {!!Addressübergabe}
 testpntrh[0]:=testmempntr[0];

 {Als UP leider nicht lauffähig, vmtl. Absturz bei setzen von Startstop}
 GetIntVec($1C,@oInt);
 SetIntVec($1C,@WaitForInt);
 repeat;until startstop=0;
 UPMessung;
 { }
 SetIntVec($1C,@oInt);
 laufw1[0]:=laufh0;laufw1[1]:=laufh1;laufw1[2]:=laufh2;laufw1[3]:=laufh3;
 laufh0:=0;laufh1:=0;laufh2:=0;laufh3:=0;intcount:=0;
 move(testbase2,testmem^,sizeOfTest2);          {Verschieben der Testroutinen}
 testpntrh[1]:=testmempntr[1];                  {!!Addressübergabe}
 testpntrh[0]:=testmempntr[0];

 {Als UP leider nicht lauffähig, vmtl. Absturz bei setzen von Startstop}
 GetIntVec($1C,@oInt);
 SetIntVec($1C,@WaitForInt);
 repeat;until startstop=0;
 UPMessung;
 { }
 SetIntVec($1C,@oInt);
 laufw2[0]:=laufh0;laufw2[1]:=laufh1;laufw2[2]:=laufh2;laufw2[3]:=laufh3;
 if error=1 then writeln('ERROR, ueberlauf');
 writeln('Ticks    : ',Ticks);
 writeln(NameTest1,' ',lauf1:0:0,' Durchlaeufe. Offset: ',offsetTest1,' Groesse: ',sizeofTest1);
 writeln(NameTest2,' ',lauf2:0:0,' Durchlaeufe. Offset: ',offsetTest2,' Groesse: ',sizeofTest2);
 writeln;
 if lauf1>lauf2 then
  begin
  lauf1:=lauf1-lauf2;
  writeln(NameTest1,' ',lauf1:0:0,' Durchlaeufe schneller.');
  end
 else
  begin
  lauf1:=lauf2-lauf1;
  writeln(NameTest2,' ',lauf1:0:0,' Durchlaeufe schneller.');
  end;
 sound(2000);
 delay(2000);
 nosound;
 readln;
 end {/standardtest}
else if w='e' then
begin; {erweiterter Test}
 writeln('Starte Messung');
 for i:=0 to maxShift do
  begin
  writeln('Lauf: ',i,' Addr.: ',intToHexstr(testmempntr[1]),':',intToHexstr(testmempntr[0]),' ',switch);
  repeat
   intcount:=0;laufh0:=0;laufh1:=0;laufh2:=0;laufh3:=0;       {Rücksetzen einiger Variaben}
   if switch then move(testbase1,testmem^,sizeOfTest1)        {Verschieben der Testroutinen}
   else           move(testbase2,testmem^,sizeOfTest2);
   testpntrh[1]:=testmempntr[1];                              {!!Addressübergabe}
   testpntrh[0]:=testmempntr[0];

   GetIntVec($1C,@oInt);                                      {Setzen der interrupts und warten auf den Startinterrupt}
   SetIntVec($1C,@WaitForInt);                                {Als Bestandteil eines UPs führt diese}
   repeat;until startstop=0;                                  {Warteschleife zu einem Absturz}
   UPMessung;                                                 {Aufruf des eigentlichen Messprogramms}

   SetIntVec($1C,@oInt);
   if switch then begin;laufw1[0]:=laufh0;laufw1[1]:=laufh1;laufw1[2]:=laufh2;laufw1[3]:=laufh3;end
   else           begin;laufw2[0]:=laufh0;laufw2[1]:=laufh1;laufw2[2]:=laufh2;laufw2[3]:=laufh3;end;
   switch:=not switch;
  until switch;
  runTest1[i].runs:=lauf1;
  runTest1[i].MemAddr:=4*testmempntr[1]+testmempntr[0];
  runTest2[i].runs:=lauf2;
  runTest2[i].MemAddr:=4*testmempntr[1]+testmempntr[0];
  inc(testmem);
  if error=1 then begin;writeln('ERROR, ueberlauf');i:=maxShift;end;
 end;
 Auswertung(runTest1,runTest2);
end; {/erweiterter Test}
writeln(intToHexstr(2000));
readln;
testmem:=testmembase;
dispose(testmem);
end.
PS. bei mir hatte ich nur eine Relevanz von Mod 16 festgestellt (486DX2 66, P120, K6 III 500)
wobo
DOS-Guru
Beiträge: 613
Registriert: So 17. Okt 2010, 14:40

Re: Variablenbezüge in Assembler innerhalb Pascal

Beitrag von wobo »

Dosenware hat geschrieben: im grunde genommen musst du nur das comp (alles was bei mir lauf* heißt) durch das gleiche Konstrukt ersetzen wie du es bereits schonmal gemacht hast... und unsere beiden Programme arbeiten fast gleich (meines macht 128 shifts) und WTF treibst du da mit bx?
Dass unsere beiden Programme fast geich arbeiten, habe ich auch erst festgestellt, als ich meines fertig hatte. Zuvor hatte ich nur Bahnhof verstanden. Irgendwie hatte mich das ständige absolute total verwirrt, und ich die ganze Zeit das TypeCasting im Kopf habe ... :-))

Das inc bx habe ich nur verwendet, weil es ein 1-byte-opcode ist, und ich deswegen durch Auszählen bestimmen kann, wie groß die procedure wird. Die procedure EndOfTestProc enthält z.B. 64* inc bx, so dass ich sicher weiß, dass diese Procedure mehr als 64 byte im CodeSegment umfasst. Ich kann deswegen gefahrlos die TestProc drüber schieben. So muß ich nicht auf den Heap ausweichen und kann TestProc immer im aktuellen CodeSegm halten. Ich hätte auch nop nehmen können, hatte aber am Anfang den Verdacht, dass das vielleicht von TP wegoptimiert werden könnte (was aber nicht der Fall sein dürfte).
Dosenware hat geschrieben: PS. bei mir hatte ich nur eine Relevanz von Mod 16 festgestellt (486DX2 66, P120, K6 III 500)
Das ist interessant. Zumindest ab dem 486dx2 haben also alle ein 16-byte alignment. (Dann liegt freecrac mit seiner ständigen Paragraphenreiterei mal wieder goldrichtig). Allerdings gibt es innerhalb des 16-byte alignments jedenfalls zwischen 486dx4 und duron erhebliche Unterschiede.
DOSferatu
DOS-Übermensch
Beiträge: 1220
Registriert: Di 25. Sep 2007, 12:05
Kontaktdaten:

Re: Variablenbezüge in Assembler innerhalb Pascal

Beitrag von DOSferatu »

Wegen "comp":

Um die Typen "single", "double", "extended" und "comp" einzuschalten, bzw möglich zu machen, muß man einfach nur den Compilerschalter {$N+} verwenden. Voraussetzung dafür ist das Vorhandensein eines 80x87 (oder der 80x87 Funktionalität in einem 80x86).
Ist kein 80x87 vorhanden, kann man zusätzlich {$E+} benutzen, der fügt Code ein, um die Funktionen des 80x87 softwaremäßig zu emulieren (ist dann natürlich langsamer, denke ich).

(Aber vielleicht habe ich das alles ja jetzt auch umsonst geschrieben.)
Antworten