exponentielle pour le fun

Sujets variés concernant le développement en PureBasic
Dr. Dri
Messages : 2527
Inscription : ven. 23/janv./2004 18:10

exponentielle pour le fun

Message par Dr. Dri »

je me suis amusé à recoder la fonction asm que j'ai postée ici en pb
J'ai voulu être "gentil" en optimisant aussi la version PB mais ça me fait mal de constater que la différence de vitesse est flagrante ^^

la version PB est environ 5 fois plus rapide chez moi alors je la partage avec vous =)

Code : Tout sélectionner

Procedure.f Exp(x.f)
  Protected i.l = 1, n.d, u.d, m.d = 1, t.d = 1, r.d
  
  n = Round(Abs(x) / Log(2), 0)
  u = Abs(x) - n * Log(2)
  
  While t > 0
    t * u / i
    i + 1
    m + t
  Wend
  
  r = m
  
  While n > 0
    r + r
    n - 1
  Wend
 
  If x < 0
    r = 1.0 / r
  EndIf
 
  ProcedureReturn r
EndProcedure
Dri :cry:
erix14
Messages : 480
Inscription : sam. 27/mars/2004 16:44
Contact :

Message par erix14 »

Pour moi c'est 3 fois plus rapide :
Intel(R) Pentium(R) 4 CPU 2.40GHz
2.52 GHz, 768Mo de RAM
NVIDIA GeForce4 Ti 4800 SE - 128Mo
Microsoft Windows XP Professionnel Service Pack 2
-----------------------------------
Méthode ASM : Exp_ASM(23) = 9744803840.000000
89116 cycles CPU (minimum)
89640 cycles en moyenne
X 1.00
-----------------------------------
Méthode PB : Exp_PB(23) = 9744803840.000000
29332 cycles CPU (minimum)
30188 cycles en moyenne
X 3.04
-----------------------------------
A vide
296 cycles CPU (minimum)
308 cycles en moyenne
X 301.07
-----------------------------------
Dr. Dri
Messages : 2527
Inscription : ven. 23/janv./2004 18:10

Message par Dr. Dri »

erix14 a écrit :Pour moi c'est 3 fois plus rapide
Effectivement en terme de cycles processeur c'est 3 fois plus rapide, après à l'exécution tout dépend de ce qui tourne en même temps sur la machine.

Dri ^^
erix14
Messages : 480
Inscription : sam. 27/mars/2004 16:44
Contact :

Message par erix14 »

Quelque chose clochait, ce n'était pas normal que ton code ASM soit plus lent. En en réalité il est 3.5 fois plus rapide que celui en PureBasic. J'ai cerné le problème, mais je n'ai pas encore la solution. Coche et décoche la ligne 78, et tu verras...

Code : Tout sélectionner

Procedure.f Exp_PB(x.f)
	Protected i.l = 1, n.d, u.d, m.d = 1, t.d = 1, r.d
	n = Round(Abs(x) / Log(2), 0)
	u = Abs(x) - n * Log(2)
	While t > 0
		t * u / i
		i + 1
		m + t
	Wend
	r = m
	While n > 0
		r + r
		n - 1
	Wend
	If x < 0
		r = 1.0 / r
	EndIf
	ProcedureReturn r
EndProcedure
Procedure.f Exp_ASM(x.f)
	Protected i.l
	!NewCW equ p.v_i+0
	!OldCW equ p.v_i+2
	;charge x sans s'occuper du signe
	!FLD dword [p.v_x]
	!FABS
	;n = x / Log(2)
	!FLDLN2
	!FDIVR ST0, ST1
	;récupère le CW
	!FNSTCW word [NewCW]
	!FNSTCW word [OldCW]
	;crée le CW pour arrondi par défaut
	!AND   word [NewCW], $F3FF
	!OR    word [NewCW], $0400
	!FLDCW word [NewCW]
	;n = Floor(n)
	!FRNDINT
	;restore le CW
	!FLDCW word [OldCW]
	;u = x - n * Log(2)
	!FXCH
	!FLD ST1 ;charge n
	!FLDLN2
	!FMULP
	!FSUBP
	
	;m = Exp(u)
	!FLD1 ;m
	!FLD1 ;t
	!MOV  dword [p.v_i], 1
exp_loop:
	;tant que t > 0
		!FTST
		!FNSTSW ax
		!TEST   ah, $40
		!JNE    l_exp_end_loop
		;t * u / i
		!FMUL  ST0, st2
		!FIDIV dword [p.v_i]
		;i + 1
		!INC   dword [p.v_i]
		;m + t
		!FADD  ST1, ST0
		!JMP l_exp_loop
	exp_end_loop:
	!FSTP ST0 ;retire t
	!FSTP ST1 ;retire u
	!FSCALE   ;r = m * Pow(2, n)
	!FSTP ST1 ;retire n
	;si x négatif
	!TEST dword [p.v_x], $80000000
	!JE   l_exp_end
		;r = 1.0 / r
		!FLD1
		!FDIVRP
exp_end:
	!FLD1
	!ADD Esp, 4
	!RET 4
EndProcedure

;- Debut du test
Global r.f
Global MonTest.ITest = New_Test(0)
For t=0 To #ITest
	MonTest\Start(1)
	r = Exp_PB(23)
	MonTest\Stop(1)
Next
MonTest\SetTitle(1, "Méthode PureBasic = "+StrF(r))
;/
For t=0 To #ITest
	MonTest\Start(2)
	r = Exp_ASM(23)
	MonTest\Stop(2)
Next
MonTest\SetTitle(2, "Méthode ASM = "+StrF(r))
;/
For t=0 To #ITest
	MonTest\Start(3)
	MonTest\Stop(3)
Next
MonTest\SetTitle(3, "A vide")

MonTest\Display(1)
Dr. Dri
Messages : 2527
Inscription : ven. 23/janv./2004 18:10

Message par Dr. Dri »

Avec ce FLD1 mon code est 40 fois plus rapide!

Du coup je me rend compte que ton comptage de cycles présente une faille : les appels de fonctions. Tu comptes le nombre de cycles de chaque instruction mais tu ne comptabilise pas les cycles générés par les autres fonctions (dans cet exemple Round et Log).

Avec un test sans debugger j'obtiens 20ms pour la version asm et 893ms pour la version PB. Le seul hic c'est que le résultat retourné est 1.0 au lieu de celui escompté. Le truc c'est que je ne comprend vraiment pas pourquoi ça ralenti à ce point ni pourquoi le FLD1 dope le code. T'as mis le doigt sur un sacré mystère.

[EDIT]
j'ai essayé de virer le "1" chargé à la fin avec un "FSTP st0" mais quand je fais ça j'ai à nouveau une perte de performance. Par contre avec un "FXCH" je retourne le bon résultat sans perte sauf que ça laisse un "1" dans la pile en sortant de la fonction.

Dri 8O
erix14
Messages : 480
Inscription : sam. 27/mars/2004 16:44
Contact :

Message par erix14 »

Je crois que je viens de comprendre, quand on laisse des valeurs sur la pile, les instructions de chargement ne sont pas exécutées, d'où un gain de temps. Chaque registre à une étiquette qui spécifie si d'emplacement est vide. Ces étiquettes sont modifiées lorsque l'on ajoute ou retire une valeur de la pile ou avec un "FFREE registre". J'ai donc cherché ailleurs... J'ai compté le nombre d'itérations des boucles "While t > 0": pour la version PureBasic 126 et pour la version ASM 1374. Donc c'est normal que PureBasic est plus rapide. La version PureBasic utilise une variable 64 bits alors que la version ASM utilise un registre 80 bits. C.Q.F.D.

Code : Tout sélectionner

Procedure.f Exp_PB(x.f)
	Protected i.l = 1, n.d, u.d, m.d = 1, t.d = 1, r.d, cmp.l = 0
	n = Round(Abs(x) / Log(2), 0)
	u = Abs(x) - n * Log(2)
	cmp = 0
	While t > 0
		cmp + 1
		t * u / i
		i + 1
		m + t
	Wend
	Debug cmp
	r = m
	While n > 0
		r + r
		n - 1
	Wend
	If x < 0
		r = 1.0 / r
	EndIf
	ProcedureReturn r
EndProcedure
Procedure.f Exp_ASM(x.f)
	Protected i.l, cmp.l=0
	!NewCW equ p.v_i+0
	!OldCW equ p.v_i+2
	;charge x sans s'occuper du signe
	!FLD dword [p.v_x]
	!FABS
	;n = x / Log(2)
	!FLDLN2
	!FDIVR ST0, ST1
	;récupère le CW
	!FNSTCW word [NewCW]
	!FNSTCW word [OldCW]
	;crée le CW pour arrondi par défaut
	!AND   word [NewCW], $F3FF
	!OR    word [NewCW], $0400
	!FLDCW word [NewCW]
	;n = Floor(n)
	!FRNDINT
	;restore le CW
	!FLDCW word [OldCW]
	;u = x - n * Log(2)
	!FXCH
	!FLD ST1 ;charge n
	!FLDLN2
	!FMULP
	!FSUBP
	
	;m = Exp(u)
	!FLD1 ;m
	!FLD1 ;t
	!MOV  dword [p.v_i], 1

exp_loop:
	;tant que t > 0
		!INC   dword [p.v_cmp]
		!FTST
		!FNSTSW ax
		!TEST   ah, $40
		!JNE    l_exp_end_loop
		;t * u / i
		!FMUL  ST0, st2
		!FIDIV dword [p.v_i]
		;i + 1
		!INC   dword [p.v_i]
		;m + t
		!FADD  ST1, ST0
		!MOV Eax, dword [p.v_cmp]
		!CMP Eax, 126
		!JNZ l_exp_loop
	exp_end_loop:
	Debug cmp
	!FSTP ST0 ;retire t
	!FSTP ST1 ;retire u
	!FSCALE   ;r = m * Pow(2, n)
	!FSTP ST1 ;retire n
	;si x négatif
	!TEST dword [p.v_x], $80000000
	!JE   l_exp_end
		;r = 1.0 / r
		!FLD1
		!FDIVRP
exp_end:
	!ADD Esp, 8
	!RET 4
EndProcedure
Debug Exp_PB(23)
Debug "------"
Debug Exp_ASM(23)
;End

;- Debut du test
Global r.f
Global MonTest.ITest = New_Test(0)
For t=0 To #ITest
	MonTest\Start(1)
	r = Exp_PB(23)
	MonTest\Stop(1)
Next
MonTest\SetTitle(1, "Méthode PureBasic = "+StrF(r))
;/
For t=0 To #ITest
	MonTest\Start(2)
	r = Exp_ASM(23)
	MonTest\Stop(2)
Next
MonTest\SetTitle(2, "Méthode ASM = "+StrF(r))
;/
For t=0 To #ITest
	MonTest\Start(3)
	MonTest\Stop(3)
Next
MonTest\SetTitle(3, "A vide")

MonTest\Display(1)
P.S : Le compteur d'instruction du processeur ne fait pas la différence entre les fonctions. Il compte toutes les instructions qu'il exécute. Les différences viennent du multitâche...
Dr. Dri
Messages : 2527
Inscription : ven. 23/janv./2004 18:10

Message par Dr. Dri »

je pensais que le compteur d'instruction se basais sur le code asm mais apparament non (c'est mieux fichu que je pensais)

Pour le nombre d'itérations ca pourrait expliquer que le code PB soit plus rapide mais pourquoi un gain de temps si on ajoute un "FLD1" à la fin ? surtout que l'ajout se fait après la boucle!

Dri
erix14
Messages : 480
Inscription : sam. 27/mars/2004 16:44
Contact :

Message par erix14 »

Et bien avec l'ajout de l'instruction "FDL1", la pile du coprocesseur arithmétique se remplit et cela gène l'instruction "FTST", la boucle n'est parcourue qu'une seule fois. Comme je l'ai expliqué précédemment, les registres (la pile) ont une étiquette pour indiquer s'ils sont libres. "FTST" fait une comparaison avec zéro, il doit certainement avoir besoin d'un registre de libre pour mettre son zéro...

Code : Tout sélectionner

Global CmpBouclePB.l
Procedure.f Exp_PB(x.f)
	Protected I.l = 1, n.d, u.d, m.d = 1, t.d = 1, r.d, cmp.l = 0
	n = Round(Abs(x) / Log(2), 0)
	u = Abs(x) - n * Log(2)
	cmp = 0
	While t > 0
		cmp + 1
		t * u / I
		I + 1
		m + t
	Wend
	CmpBouclePB = cmp
	r = m
	While n > 0
		r + r
		n - 1
	Wend
	If x < 0
		r = 1.0 / r
	EndIf
	ProcedureReturn r
EndProcedure
Global CmpBoucle.l, Res.f
Procedure.f Exp_ASM(x.f)
	Protected Var1.l, cmp.l=0
	!NewCW equ p.v_Var1+0
	!OldCW equ p.v_Var1+2
	;charge x sans s'occuper du signe
	!FLD dword [p.v_x]
	!FABS
	;n = x / Log(2)
	!FLDLN2
	!FDIVR ST0, ST1
	;récupère le CW
	!FNSTCW word [NewCW]
	!FNSTCW word [OldCW]
	;crée le CW pour arrondi par défaut
	!AND   word [NewCW], $F3FF
	!OR    word [NewCW], $0400
	!FLDCW word [NewCW]
	;n = Floor(n)
	!FRNDINT
	;restore le CW
	!FLDCW word [OldCW]
	;u = x - n * Log(2)
	!FXCH
	!FLD ST1 ;charge n
	!FLDLN2
	!FMULP
	!FSUBP
	
	;m = Exp(u)
	!FLD1 ;m
	!FLD1 ;t
	!MOV  dword [p.v_Var1], 1
	
	exp_loop:
	;tant que t > 0
		!FTST
		!FNSTSW ax
		!TEST   ah, $40
		!JNE    l_exp_end_loop
		;t * u / i
		!FMUL  ST0, st2
		!FIDIV dword [p.v_Var1]
		;i + 1
		!INC   dword [p.v_Var1]
		;m + t
		!FADD  ST1, ST0
		!INC   dword [p.v_cmp]
		!JMP l_exp_loop
	exp_end_loop:
	CmpBoucle = cmp
	!FSTP ST0 ;retire t
	!FSTP ST1 ;retire u
	!FSCALE   ;r = m * Pow(2, n)
	!FSTP ST1 ;retire n
	;si x négatif
	!TEST dword [p.v_x], $80000000
	!JE   l_exp_end
	;r = 1.0 / r
	!FLD1
	!FDIVRP
	exp_end:
	!FST dword [v_Res]
	
	!FLD1 ; le code mystère...
	
	!ADD Esp, 8
	!RET 4
EndProcedure

;- Debut du test
Global r.f
Global MonTest.ITest = New_Test(0)
For t=0 To #ITest
	MonTest\Start(1)
	r = Exp_PB(23)
	MonTest\Stop(1)
Next
MonTest\SetTitle(1, "Méthode PureBasic = "+StrF(r)+"  CmpBoucle = "+Str(CmpBouclePB))
;/
For t=0 To #ITest
	MonTest\Start(2)
	r = Exp_ASM(23)
	MonTest\Stop(2)
Next
MonTest\SetTitle(2, "Méthode ASM = "+StrF(Res)+"  CmpBoucle = "+Str(CmpBoucle))
;/
For t=0 To #ITest
	MonTest\Start(3)
	MonTest\Stop(3)
Next
MonTest\SetTitle(3, "A vide")

MonTest\Display(1) 
Répondre