Seite 1 von 2
SUB vs. DEC
Verfasst: 07.05.2009 17:28
von Batze
Dachte immer DEC wäre schneller als sub ...
Code: Alles auswählen
Timer = ElapsedMilliseconds()
! MOV esi, $8FFFFFFF
! Do_X_Times:
! MOV ebx, $0000FFFF
! DEC ebx ; Alg 1
! DEC ebx ; Alg 1
! DEC ebx ; Alg 1
! DEC ebx ; Alg 1
! DEC ebx ; Alg 1
! DEC ebx ; Alg 1
! DEC ebx ; Alg 1
! DEC ebx ; Alg 1
! DEC ebx ; Alg 1
! DEC ebx ; Alg 1
! DEC ebx ; Alg 1
; ! SUB ebx, 1 ; Alg 2
; ! SUB ebx, 1 ; Alg 2
; ! SUB ebx, 1 ; Alg 2
; ! SUB ebx, 1 ; Alg 2
; ! SUB ebx, 1 ; Alg 2
; ! SUB ebx, 1 ; Alg 2
; ! SUB ebx, 1 ; Alg 2
; ! SUB ebx, 1 ; Alg 2
; ! SUB ebx, 1 ; Alg 2
; ! SUB ebx, 1 ; Alg 2
; ! SUB ebx, 1 ; Alg 2
! DEC esi
! JNZ Do_X_Times
MessageRequester("Time = ", Str(ElapsedMilliseconds()-Timer))
; Alg 1: 10172, 10422, 10328
; Alg 2: 7125, 7265, 7188
Habe ich vielleicht irgendwas falsch gemacht, oder ist mein PC komisch? Oder ist Sub tatsächlich viel schneller als dec?
Verfasst: 07.05.2009 19:17
von cxAlex
Dec: 4484
Sub: 4485
Ziemlich gleichschnell würde ich sagen...
Verfasst: 07.05.2009 19:51
von Batze
Dein Ergebnis erscheint mir auch viel realistischer. Warum ist meins dann so eigenartig? Die Asm-Gurus hier vielleicht eine Antwort ?
Edit: Kann man Code eigentlich während der Laufzeit verändern? Also zb. aus einem ADD eax, 2 ein ADD eax, 100595 machen oder sowas?
Verfasst: 07.05.2009 21:38
von Helle
Die Tests relativ kurzer Code-Stücke sind schon ´ne Sache mit vielen Unwägbarkeiten (Cache, Memory-Verwaltung) und hängen sowieso von der jeweiligem CPU ab. Für Intel gilt schon seit langem, dass SUB nicht langsamer ist als DEC. Hier ein Code, der u.a. den Cache-Einfluss eleminieren sollte:
Code: Alles auswählen
Global Mem.i = $1000000 ;Speicher reservieren, hier 16 MB
Global Loop1.i = 10000
Global Loop2.i = 10000
Global CodeMem.i
Global CodeMemUr.i
Global CodeA.i
Global CodeL.i
Global TimeA.q
Global TimeE.q
Global Ticks1.q
Global Ticks2.q
Global TicksZ.q
Macro REG32(Befehl) ;z.B. DEC
!Befehl ebx
!Befehl ebx
!Befehl ebx
!Befehl ebx
!Befehl ebx
!Befehl ebx
!Befehl ebx
!Befehl ebx
!Befehl ebx
!Befehl ebx
EndMacro
Macro REG32_IMMFEST(Befehl, Wert) ;z.B. SUB, 1
!Befehl ebx,Wert
!Befehl ebx,Wert
!Befehl ebx,Wert
!Befehl ebx,Wert
!Befehl ebx,Wert
!Befehl ebx,Wert
!Befehl ebx,Wert
!Befehl ebx,Wert
!Befehl ebx,Wert
!Befehl ebx,Wert
EndMacro
Macro Messung(TestBefehl)
CodeMem = CodeMemUr
!mov esi,[v_CodeMem]
!mov [v_CodeA],10 ;Länge des nachfolgenden Befehls
!add [v_CodeA],$
TestBefehl ;hier einfügen
!mov eax,$
!sub eax,[v_CodeA]
!mov [v_CodeL],eax
LDTicks()
EndMacro
Procedure LDTicks()
CodeMem = CodeMemUr
For i = 1 To Loop1 ;das Macro vom Code-Teil in den Speicher kopieren (duplizieren)
CopyMemory(CodeA, CodeMem, CodeL)
CodeMem + CodeL
Next
PokeB(CodeMem, $C3) ;zum Schluss Opcode für Return setzen
FlushInstructionCache_(GetCurrentProcess_(), #Null, #Null) ;(bisherigen) Befehls-Cache löschen
!cpuid ;für Serialisierung (Pipe(s) leerfegen) Na ja...
!rdtsc ;Time-Stamp auslesen
!mov dword[v_TimeA],eax ;EAX = Low-DWord; diese Ticks drücken wir in den Skat
!mov dword[v_TimeA+4],edx ;EDX = High-DWord
For i = 1 To Loop2
!call [v_CodeMemUr] ;Code im Speicher ausführen
Next
!rdtsc
!mov dword[v_TimeE],eax
!mov dword[v_TimeE+4],edx
TicksZ = TimeE - TimeA
EndProcedure
hThread = GetCurrentThread_()
SetThreadAffinityMask_(hThread, 1) ;Thread nur von Core0 ausführen lassen (wenn Multi-Core-CPU)
SetThreadPriority_(hThread, #THREAD_PRIORITY_TIME_CRITICAL)
CodeMem = VirtualAllocEx_(GetCurrentProcess_(), #Null, Mem, #MEM_COMMIT, #PAGE_EXECUTE_READWRITE)
CodeMem + 16 - (CodeMem % 16) ;um sicher Alignment16 zu erreichen
CodeMemUr = CodeMem
Messung(REG32(DEC))
Ticks1 = TicksZ
Messung(REG32_IMMFEST(SUB, 1))
Ticks2 = TicksZ
VirtualFreeEx_(GetCurrentProcess_(), CodeMemUr, Mem, #MEM_DECOMMIT)
E1$ = "1.Test: " + Str(Ticks1) +" Taktzyklen"
E2$ = "2.Test: " + Str(Ticks2) +" Taktzyklen"
G$ = " Loops: " + Str(Loop1 * Loop2 * 10) ;10-mal im Macro
MessageRequester("Benötigte Taktzyklen", E1$ + #LFCR$ + E2$ + #LFCR$ + G$)
Der Testcode wird ins Memory kopiert und dort als lange Code-"Kette" ausgeführt (allerdings auch mehrfach). Als Maß dienen die verbratenen Taktzyklen. Deine Werte würden mich mal interessieren.
Den Code während der Laufzeit zu verändern geht im Ring3-Modus (normaler Windows-Modus) nicht. Es gibt zwar spezielle Befehle (z.B.MOV mit Opcode $8E) dafür, aber die laufen nur im Ring0-Modus.
Gruß
Helle
Verfasst: 07.05.2009 21:50
von cxAlex
---------------------------
Benötigte Taktzyklen
---------------------------
1.Test: 1011846915 Taktzyklen
2.Test: 1024620156 Taktzyklen
Loops: 1000000000
Verfasst: 07.05.2009 21:51
von Fluid Byte
---------------------------
Benötigte Taktzyklen
---------------------------
1.Test: 1109235247 Taktzyklen
2.Test: 1049033918 Taktzyklen
Loops: 1000000000
---------------------------
OK
---------------------------
Verfasst: 07.05.2009 22:21
von alter Mann
1.Test: 1094186336 Taktzyklen
2.Test: 1075291860 Taktzyklen
Loops: 1000000000
Verfasst: 07.05.2009 22:27
von Batze
1.Test: 1830372984 Taktzyklen
2.Test: 1167026544 Taktzyklen
Loops: 1000000000
Edit: es ist wohl nicht immer so extrem (hab noch ein par durchläufe probiert), aber immer ein merklicher Unterschied von über 50%
Naja, also ist meine CPU tatsächlich seltsam. Werde es auch nochmal auf dem Laptop testen. Aber ich kann wohl davon ausgehen dass ich da eher die Ausnahme bin.
Vielen Dank für diesen tollen Asm-Testcode, der ist sehr praktisch.
Verfasst: 07.05.2009 22:28
von X360 Andy
Mal andere Zahlen
1. Test 2206120985
2. Test 2127548610
Und was haben diese Werte zu bedeuten ?
EDIT
Bei 2ten mal ist Test 2 um einiges Schneller als Test 1
Verfasst: 08.05.2009 17:49
von edel
---------------------------
Benötigte Taktzyklen
---------------------------
1.Test: 1002612719 Taktzyklen
2.Test: 1002463007 Taktzyklen
Loops: 1000000000
---------------------------
OK
---------------------------
Sagt mir jetzt aber auch nichts
