Seite 1 von 1

Frage zur Division durch Konstanten

Verfasst: 04.08.2012 09:33
von Gezuppel
Habe folgende Verständnisfrage:

Code: Alles auswählen

#FTTicks_per_Day = 864000000000
dummy1.q         = 864000000000
dummy2.q         = #FTTicks_per_Day

Debug dummy1.q / dummy2.q
Debug dummy1.q / #FTTicks_per_Day
Das erste Debug gibt mir wie erwartet 1 aus
Das Zweite 1214
Warum gibt es unterschiedliche Ergebnisse? :shock:

Re: Frage zur Division durch Konstanten

Verfasst: 04.08.2012 09:44
von Mok
Bei mir auch... aber nur bei der x64-Version.
Ich guck mir mal den Asm-Code an und meld' mich dann zurück.

Re: Frage zur Division durch Konstanten

Verfasst: 04.08.2012 09:45
von ts-soft
Gezuppel hat geschrieben:Warum gibt es unterschiedliche Ergebnisse? :shock:
Dein PC kaputt? Bei mir kommt immer 1 heraus.

Re: Frage zur Division durch Konstanten

Verfasst: 04.08.2012 10:06
von Gezuppel
Dein PC kaputt? Bei mir kommt immer 1 heraus.
Dann ist der von Mok wohl auch kaputt :wink:

Habe gerade die 32Bit (PB 4.61 & PB 4.70 Beta 1) Versionen auch getestet, da funktioniert es wie erwartet.
Also wie bei Mok nur die 64Bit Versionen liefern 1214 statt 1.

Re: Frage zur Division durch Konstanten

Verfasst: 04.08.2012 10:07
von Mok
Ooookay, das Ergebnis meiner fast forensischen, wenn auch nur ziemlich schnellen/kurzen(?) Untersuchungen sieht wie folgt aus:

Code: Alles auswählen

Assemblierung des x86-Codes

; #FTTicks_per_Day = 864000000000
; dummy1.q         = 864000000000
  MOV    dword [v_dummy1],711573504
  MOV    dword [v_dummy1+4],201
; dummy2.q         = #FTTicks_per_Day
  MOV    dword [v_dummy2],711573504
  MOV    dword [v_dummy2+4],201
; 
; var1 = dummy1.q / dummy2.q
  PUSH   dword [v_dummy1+4]
  PUSH   dword [v_dummy1]
  LEA    eax,[v_dummy2]
  PUSH   dword [eax+4]
  PUSH   dword [eax]
  PUSH   dword [esp+12]
  PUSH   dword [esp+12]
  CALL   Div64
  MOV    [esp],eax
  MOV    [esp+4],edx
  POP    eax
  POP    edx
  MOV    dword [v_var1],eax
; var2 = dummy1.q / #FTTicks_per_Day
  PUSH   dword [v_dummy1+4]
  PUSH   dword [v_dummy1]
  PUSH   dword 201
  PUSH   dword 711573504
  PUSH   dword [esp+12]
  PUSH   dword [esp+12]
  CALL   Div64
  MOV    [esp],eax
  MOV    [esp+4],edx
  POP    eax
  POP    edx
  MOV    dword [v_var2],eax

Code: Alles auswählen

Assemblierung des x86-64-Codes (x64)
; #FTTicks_per_Day = 864000000000
; dummy1.q         = 864000000000
  MOV    rax,864000000000
  MOV    qword [v_dummy1],rax
; dummy2.q         = #FTTicks_per_Day
  MOV    rax,864000000000
  MOV    qword [v_dummy2],rax

; var1 = dummy1.q / dummy2.q
  MOV    r15,qword [v_dummy1]
  PUSH   qword [v_dummy2]
  MOV    rax,r15
  POP    rcx
  CQO
  IDIV   rcx
  MOV    r15,rax
  MOV    qword [v_var1],r15
; var2 = dummy1.q / #FTTicks_per_Day
  MOV    r15,qword [v_dummy1]
  MOV    rax,r15
  MOV    rcx,711573504
  CQO
  IDIV   rcx
  MOV    r15,rax
  MOV    qword [v_var2],r15
Anmerkung: Nur die relevanten Ausschnitte wurden gepostet, kein vollständiger Code.

Wie unschwer zu erkennen ist, greift PB bei x86-Code auf eine externe Funktion zurück (Div64) - keine Ahnung warum und was damit erreicht wird, aber es funktioniert. Beim x64-Code sieht das anders aus (da wurde "direkt" also im IDIV dividiert), daher kann man beide nicht miteinander vergleichen.
Was allerdings beim x64-Code auffällt, ist, dass der Divisor beide Male in rcx gespeichert wird (IDIV rcx bedeutet: Ganzzahl-Division RAX / RCX), wenn man 2 Zeilen darüber schaut, lässt sich erkennen, dass bei der Division durch die Variable klarerweise der Wert der Variable in RCX gespeichert wird (PUSH dword [dummy2] {...} POP rcx). Bei der Division durch einen konstanten Wert wird RCX allerdings auf den Wert 711573504 gestellt, was garnicht funktionieren kann. Rechnet man 864000000000 / 711573504 kommen die erwarteten 1214 raus.
Was heißt das? Das heißt, dass beim x64 4.70 Beta-Release irgendjemand die Idee hatte, bei konstanten Division den vom Programmierer festgelegten Divisor durch die Zahl 711573504 zu ersetzen. (Die Zahl bleibt von Programm zu Programm scheinbar gleich).

Ein Mysterium, welches ich nicht lösen konnte ist, dass auch in der X86-Version auf den Wert 711573504 zurückgegriffen wurde. Warum es dennoch funktioniert, kann ich nicht sagen, da ich den Code für die Prozedur 'Div64' nicht kenne. Scheinbar ist 711573504 eine Art magische Zahl, welche das Dividieren auf unterster Ebene erleichtern soll, nur wurde (falls meine Annahmen stimmen) die Zahl bei 64-Bit-Code falsch verwendet.

Also @PB-Entwickerteam: Kloppt mal ganz schnell einen Patch raus.

Edit: Außerdem ist werden im Divisionscode überflüssige Zuweisungen gemacht. Jaja, es ist nur eine Beta, aber gesagt ist gesagt.

Code: Alles auswählen

; var2 = dummy1.q / #FTTicks_per_Day
  MOV    r15,qword [v_dummy1]
  MOV    rax,r15
  MOV    rcx,711573504
  CQO
  IDIV   rcx
  MOV    r15,rax
  MOV    qword [v_var2],r15
Wäre ersetzbar durch

Code: Alles auswählen

; var2 = dummy1.q / #FTTicks_per_Day
  MOV    rax,qword [v_dummy1]
  MOV    rcx,864000000000 ;Edit2: der Richtigkeit halber wurde der richtige Wert eingesetzt EndEdit2
  IDIV   rcx
  MOV    qword [v_var2],rax
CQO scheint eine Convert Quoad to O (?) Instruktion zu sein, allerdings hat das Löschen jener Instruktion bei mir keinen Unterschied gemacht.
EndEdit

Re: Frage zur Division durch Konstanten

Verfasst: 04.08.2012 10:12
von Gezuppel
Hallo Mok,
schneller gehts kaum! Danke dafür!

Re: Frage zur Division durch Konstanten

Verfasst: 04.08.2012 10:31
von ts-soft

Re: Frage zur Division durch Konstanten

Verfasst: 04.08.2012 11:29
von Helle
711573504 ist keine "magische" Zahl :mrgreen: , sondern 864.000.000.000 = 201*(2^32)+711.573.504. Wenn der Quadwert in 2 DoubleWords aufgeteilt wird ist Hi-DWord = 201, Lo-DWord = 711.573.504.
CQO kannste nicht einfach weglassen, das funktioniert nur wenn RDX zufälligerweise gerade Null ist: 64-Bit-(I)DIV = RDX:RAX div Reg/Mem64. CQO (Quad to Oct) setzt RDX, und zwar alle Bits RDX = MSB von RAX.
Trotzdem natürlich ein Bug.
Gruß
Helle

Re: Frage zur Division durch Konstanten

Verfasst: 04.08.2012 12:16
von Mok
@Helle: Alles klar, Chef. Dann hab ich mal wieder Schwachsinn geschrieben und 15 Minuten verkackt.