Gå til innhold

Anbefalte innlegg

Videoannonse
Annonse

La oss se litt på hvordan vi kan skrive til minnet. Her er den samme koden fra forrige innlegg, bare at jeg har laget en funksjon som skriver over den første bokstaven i Tekst stringen.

 

.386
.model flat, stdcall
option casemap:none

INCLUDE \MASM32\INCLUDE\windows.inc
INCLUDE \MASM32\INCLUDE\kernel32.inc
INCLUDE \MASM32\INCLUDE\User32.inc

INCLUDELIB \MASM32\LIB\kernel32.lib
INCLUDELIB \MASM32\LIB\User32.lib
INCLUDELIB \MASM32\LIB\Masm32.lib

MinProsedyre PROTO

.DATA

Tekst db "Assembler er enkelt.",0
Overskrift db "Hei",0

.DATA?

.CODE

Start:

; Kall prosedyren vår
INVOKE MinProsedyre

; Sprett opp en messageboks
INVOKE MessageBox, 0, OFFSET Tekst, OFFSET Overskrift, MB_OK or MB_ICONINFORMATION

; Terminerer denne prosessen og detacher alle .DLL
invoke ExitProcess, eax


MinProsedyre PROC
mov eax, OFFSET Tekst
mov cl, 'X'
mov [eax], cl
ret
MinProsedyre ENDP

end Start

 

Hvis vi ser litt på MinProsedyre funksjonen. La oss gå gjennom hver instruksjon:

 

mov eax, OFFSET Tekst

 

Denne instruksjonen kopierer adressen til 'Tekst' (som ligger i data seksjonen), og lagrer den i eax. mov instruksjonen er motsatt enn hva som er naturlig, den første parameteren er target (eax) og den andre er kilde (OFFSET Tekst)

 

Så når man skriver mov eax, OFFSET Tekst så flyttes kilden (OFFSET til Tekst) i eax registeret. Og nå inneholder eax registeret adressen til strengen vår.

 

mov cl, 'X'

 

cl er ett 8 bits register og kan inneholde 1 byte om gangen. Her flytter vi bokstaven 'X' til registeret cl.

 

Assembleren gjenkjenner '' tegnene og erstatter det som er inni med integer tall. Bokstaven X har integer tall 88, så assembleren (ml.exe) erstatter 'X' med 88 før den assemblerer programmet ditt, så dette er noe som skjer før programmet blir laget. Denne funksjonen gjør det enklere å skrive ascii tall, uten å måtte slå det opp i ascii tabellen, så dette er en funksjon i assembleren for å gjøre det enklere å kode.

 

Så, vi flytter bokstaven X (Ascii 88) inn i cl registeret.

 

mov [eax], cl

 

Her skriver vi innholdet av cl registeret (Som nå inneholder bokstaven X), den skriver det til adressen som er i eax registeret. Du ser jeg har hakeparantes rundt eax, dvs da skriver vi til minneadressen som er i eax registeret, og ikke til registeret selv.

 

Hvis vi nå kjører programmet, så SKAL Tekst strengen (som er i .DATA seksjonen) nå ha første bokstav erstattet med en X.

 

Kjører det og sjekker

 

ve2Ic.png

 

:)

Endret av LonelyMan
  • Liker 1
Lenke til kommentar

La oss lage en buffer på 1 megabyte og skrive en million 'A' bokstaver i hele bufferen.

 

.386
.model flat, stdcall
option casemap:none

INCLUDE \MASM32\INCLUDE\windows.inc
INCLUDE \MASM32\INCLUDE\kernel32.inc
INCLUDE \MASM32\INCLUDE\User32.inc

INCLUDELIB \MASM32\LIB\kernel32.lib
INCLUDELIB \MASM32\LIB\User32.lib
INCLUDELIB \MASM32\LIB\Masm32.lib

MinProsedyre PROTO

.DATA

.DATA?

  Buffer DWORD ?

.CODE

Start:

; Kall prosedyren vår
INVOKE MinProsedyre


; Terminerer denne prosessen og detacher alle .DLL
invoke ExitProcess, eax


MinProsedyre PROC
INVOKE GlobalAlloc, GPTR, 1000000
mov Buffer, eax
mov edi, eax
mov ecx, 1000000
       mov al, 'A'
       rep stosb
INVOKE GlobalFree, Buffer
ret
MinProsedyre ENDP

end Start

 

Her gjør vi noe litt mer avansert. I den uinitialiserte dataseksjonen ser du at jeg har laget en handle som vi skal bruke når vi allokerer 1 MB med minne.

 

I min prosedyre bruker vi GlobalAlloc til å kreve en megabyte minne. GPTR betyr at windows skal automatisk fylle hele bufferen med 0.

 

Viktig å skjønne at alle funksjoner returnerer verdien i eax registeret. Selv i c++, så er dette gjemt unna for deg, alle funksjonene returnerer verdier i eax, men høynivåspråket skjuler denne detaljen for deg ved å gjemme det under andre variabelnavn. Men husk at alle funksjoner som returnerer en verdi, de returnerer verdien alltid i eax registeret.

 

Så du ser etter jeg har kjørt GlobalAlloc, så returnerer den en handle til minnet vårt i eax, denne flytter vi til Buffer variabelen som ligger i uninitialiserte data seksjonen.

 

rep er en instruksjon som står for "repeat". stosb står for "Store single byte", men hvis du putter en rep instruksjon foran stosb så vil den repetere så og så mange ganger. Du angir antall repetisjoner i ecx registeret, du ser jeg har puttet en million i ecx registeret.

 

Så skriver vi 1 million 'A' bokstaver i hele bufferen, og deretter frigjør vi minnet og avslutter.

Endret av LonelyMan
  • Liker 1
Lenke til kommentar

Fra nå av vil jeg ikke kvotere hele programmet, "skjelettet" på programmet kan sees i mine tidligere innlegg over her, det blir for langt å ha skjelettet på programmet med hver gang, så nå inkluderer jeg bare prosedyren.

 

Nå skal vi teste å lage en loop. denne loopen vil ikke være mest optimal, men den er likevel rask. Det er bare for at folk skal forstå hvordan en lager loops i assembler.

 

MinProsedyre PROC
mov ecx, 20
mov eax, 10
L1:	add eax, 5
loop L1
ret
MinProsedyre ENDP

 

la oss kjapt gå gjennom instruksjonene her:

 

mov ecx, 20

 

loop instruksjonen som du ser på linje 4, denne instruksjonen fungerer i samsvar med ecx registeret. Denne instruksjonen fungerer kun ved at du setter antall ganger i ecx registeret, loop count MÅ være i ecx registeret eller så fungerer ikke loop instruksjonen. Så her setter vi ecx til antall ganger vi ønsker å loope.

 

mov eax, 10

 

Før vi entrer loopen, så setter vi eax registeret til en fast verdi av 10 først. Bare for moro skyld, slik at vi ikke starter på 0.

 

L1:	add eax, 5

 

L1 er en label, alle labels må ettersluttes med kolon. I prosedyrer skal labels kun ha 1 kolon, men hvis du setter 2 kolon etter hverandre så blir dette en global label, dvs du kan kalle den fra alle plasser i programmet, uansett hvor det er. Men siden dette er en prosedyre så lager vi en lokal label ved å bruke 1 kolon.

 

Nå er vi inni selve loopen, for instruksjonen "add eax, 5" ligger rett på labelen, loopen vil gjentatte ganger hoppe tilbake til denne labelen.

 

add eax, 5 vil legge til 5 i eax registere hver gang loopen hopper hit. og vi satt ecx registeret til 20, så hvis den looper 20 ganger hit, og eax var satt til 10 før vi entret loopen så eax registeret inneholde tallet 110 når loopen er ferdig kjørt.

 

10 + (20*5) = 110 :)

Endret av LonelyMan
  • Liker 1
Lenke til kommentar

La oss teste conditional asm. I høynivå språk bruker man IF-THEN, DO-WHILE og REPEAT-UNTIL, men i asm instruksjonsverdenen fungerer dette annerledes. Der fins IF-THEN DO-WHILE i asm også, og de kan faktisk brukes på samme måten som i C++ eller andre høynivåspråk. Men la oss teste dette på instruksjonsnivå istedet.

 

mov eax, 10
cmp eax, 10
je L2
INVOKE MessageBox, 0, OFFSET LOL, OFFSET Caption, MB_OK
L2:	INVOKE MessageBox, 0, OFFSET ROFL, OFFSET Caption, MB_OK
ret

 

instruksjonen som er relevant i dette tilfellet er på linje 2, "cmp eax, 10".

cmp står for "compare". cmp eax, 10 sjekker om eax er likt 10. På neste linje ser du instruksjonen "je L2". Som står for "Jump if even". den forrige instruksjonen "cmp eax, 10" setter noen interne flags i cpu'en som baseres på resultatet fra denne compare instruksjonen. Og neste instruksjon "je L2" leser disse flaggene i cpu'en og bruker de basert på testen.

 

"cmp eax, 10" så er det jo åpenbart at eax er 10, så "je L2" vil hoppe til labelen L2 og da vil messagebox med offset "ROFL" kjøre, ellers så vil den første message boksen kjøre.

 

Disse to instruksjonene (der fins haugevis av andre) kan sies å være det samme som "IF-THEN" i høynivåspråk.

 

HVIS det viser seg at den første messageboksen skulle kjøre (hypotetisk) så ville den kjørt den andre messageboksen like etterpå, fordi den ligger like etter. Men det ville ikke skjedd om den hoppet til L2 labelen, der er ingen messagebox etter den.

 

Så det kunne lønnet seg å bruke JNE L2 istedet, som står for "Jump if NOT even"

Endret av LonelyMan
  • Liker 1
Lenke til kommentar

Kjører du på 64 bits med mitt program så blir det ikke det samme, og kompilerer du ett 64 bits c++ program så vil den fungere på en annen måte. Årsaken til at det muligens kan være raskere er fordi kompilatoren din benytter SSE til å kalkulere modulus og muligens kjører over flere tråder. "Feil" resultat skyldes at resultatet tolkes som signed dword, du kan ikke kalkulere 100 millioner tall og forvente at det skal akkumuleres opp til ett reelt tall, det fungerer kun for å måle hastigheten, ikke for å få ett rett resultat. Jeg kommer tilbake senere med en tilsvarende SSE variant, men har dårlig tid nå.

 

Koden laget av kompilatoren brukte ikke SSE, og selv om det var på 64-bit er det ingenting i den genererte assembly koden som bruker hverken 64-bit arithmetic eller de ekstra registerene som er tilgjengelige. Jeg regner med at koden fra 32-bit gcc ville være praktisk sett identiske.

Lenke til kommentar

Seien Sie so gut... 31 ms, 100 mill. :!:

 

OPTION PROLOGUE:NONE
OPTION EPILOGUE:NONE
ALIGN 4
Multipler PROC
       push ebx
       mov ebx, 3
       xor eax, eax
       mov ecx, 100000000
       mov edx, ebx
ALIGN 16
n1:	add eax, ebx
       add ebx, edx
       cmp ebx, ecx
       jbe n1
       mov ebx, 5
       mov edx, ebx
ALIGN 16
n2:	add eax, ebx
       add ebx, edx
       cmp ebx, ecx
       ja n3
       add eax, ebx
       add ebx, edx
       add ebx, edx
       cmp ebx, ecx
       jbe n2
n3:	pop ebx
       ret
Multipler ENDP
OPTION PROLOGUE:PROLOGUEDEF
OPTION EPILOGUE:EPILOGUEDEF

 

 

Et problem med den originale oppgaven er at det er åpent til bedre implementasjoner og algoritmer. For å være ihvertfall litt meningsfullt, er det derfor viktig å holde seg til samme struktur. Man skal kjøre gjennom alle tall opp til 1000 (eventuelt en annen tall), se om det kan deles ved 3 eller 5, og legge sammen tallene. Det gjør du ikke her. Det er klart at din algoritme her er raskere enn originalen, og gir samme resultantene, men det kan ikke sammenlignes med C koden. Dersom man får lov til å endre algoritmen, er min C++ template kode like gyldig - og der bruker man kompilatoren til å kalkulere resultantene på forhånd, slik at koden er bare en instruksjon. Det hadde også godt an å bruke en bedre generelle algoritme som fungere i O(0) tid istedenfor O(1) tid - men igjen så ville det ikke være sammenlignbar.

 

Enten må vi holde oss til akkurat samme algoritme, med optimisering av implementasjon men ikke metoden, ellers må vi finne et annet problem som ikke har samme forbedringsmuligheter.

 

Så for min del er ikke denne koden aksepterte som assembly implementasjon av oppgaven - du har optimisert algoritmen, ikke laget en assembly implementasjon som slå C implementasjon.

 

Forresten da jeg skrevet samme implementasjon som deg i C, laget kompilatoren omtrent identiske kode som kjørte på omtrent samme tid (34 ms på min PC). Men jeg kunne også gi kompilatoren en ekstra "-funroll-all-loops" flag som reduserte tiden til 26 ms - en 20% økning i hastighet for 10 sekunds arbeid.

  • Liker 1
Lenke til kommentar

Jeg lar deg lekke litt mer med assembly'en før jeg fortelle om trikset kompilatoren brukte for å lage raskere kode.

 

Nå kan du fortelle om trikset som gjorde koden din raskere enn min. :thumbup:

 

Deling er en ganske treg operasjon på x86 cpu'er (og de alle fleste andre cpu'er), og passer veldig dårlig inn i pipelines. Dermed bruker gode C kompilatorer (som gcc) ganging istedenfor deling ved en konstant. Det er ikke lett å finne ut de beste konstanter å bruke, og være sikkert på at det er riktig, spesielt dersom man bruker signed arithmetic - derfor er det best å la kompilatoren gjør jobben. Koden under er fra listing filen fra kompilatoren (jeg tok feil tidligere, da jeg sa at den ikke brukte ekstra amd64 register - men koden til 32-bit x86 hadde vært veldig likt):

 

123 test1000:

124 .LFB15:

125 .cfi_startproc

126 00c0 31FF xorl %edi, %edi

127 00c2 31C9 xorl %ecx, %ecx

128 00c4 41B85655 movl $1431655766, %r8d

128 5555

129 00ca 41B96766 movl $1717986919, %r9d

129 6666

130 .p2align 4,,10

131 .p2align 3

132 .L20:

133 00d0 89C8 movl %ecx, %eax

134 00d2 89CE movl %ecx, %esi

135 00d4 41F7E8 imull %r8d

136 00d7 C1FE1F sarl $31, %esi

137 00da 29F2 subl %esi, %edx

138 00dc 8D1452 leal (%rdx,%rdx,2), %edx

139 00df 39D1 cmpl %edx, %ecx

140 00e1 7410 je .L18

141 00e3 89C8 movl %ecx, %eax

142 00e5 41F7E9 imull %r9d

143 00e8 D1FA sarl %edx

144 00ea 29F2 subl %esi, %edx

145 00ec 8D1492 leal (%rdx,%rdx,4), %edx

146 00ef 39D1 cmpl %edx, %ecx

147 00f1 7502 jne .L19

148 .L18:

149 00f3 01CF addl %ecx, %edi

150 .L19:

151 00f5 83C101 addl $1, %ecx

152 00f8 81F9E803 cmpl $1000, %ecx

152 0000

153 00fe 75D0 jne .L20

154 0100 89F8 movl %edi, %eax

155 0102 C3 ret

Lenke til kommentar

Første poeng er at det er viktig at du poster kode når du legger ut tall når du sammenligner.

 

Neste poeng er at fordi jeg tenker ut bedre algoritmer enn deg, så blir ikke det automatisk juks, din algoritme var elendig og nå har du kopiert min, legger ut tall som ikke samsvarer med noen kode.

 

Det tredje poenget er at du ikke kan sammenligne 64 bits kode med 32 bits kode på noen måter.

 

Det fjerde poenget er at koden allerede ER rollet ut, det du sier høres veldig merkelig ut, det fins ikke flere registre å rolle ut i dette tilfellet. Så jeg er svært spent på å se koden. :thumbup:

 

De konstantene du snakker om kalles magic numbers. Jeg er godt kjent med de, men har ikke brukt de.

Lenke til kommentar

Poenget var ikke å lage en algoritme som løste problemet mest mulig effektivt, men å implementere gitt kode i forskjellige språk. (altså, bloggen til Torbjørn). At problemet har en bedre matematisk løsning er ikke poenget.

 

Her skriver vi innholdet av cl registeret (Som nå inneholder bokstaven X), den skriver det til adressen som er i eax registeret. Du ser jeg har hakeparantes rundt eax, dvs da skriver vi til minneadressen som er i eax registeret, og ikke til registeret selv.

Er det type register indirekte adressering og slikt? Vært borti lite assembly, bare litt i et fag nå i høst, og da var vi egentlig en del nivået under assembly.

 

 

Det hadde også godt an å bruke en bedre generelle algoritme som fungere i O(0) tid istedenfor O(1) tid - men igjen så ville det ikke være sammenlignbar.

Jeg kan big-O notasjon, men aldri vært borti noe annet enn konstant tid O(1), hva skal O(0) bety?

Lenke til kommentar

David Brown, som sagt du kjører et 64 bits der med 16 registre. Mitt program er ett 32 bits program med 8 registre, altså halvparten av hva du bruker der. Du kan jo på ingen måte sammenligne det der, men det mest tragiske var at jeg likevel hadde samme hastighet enn dog du har dobbelt så mange registre tilgjengelig, det er helt latterlig. Hva jeg kunne gjort med disse ekstra registrene :cool:

 

Min konklusjon er:

 

1: Din forrige kode sa du var 32 bits kode (men i det nye eksemplet ditt her så er det 64 bits "kode") jeg føler at du ikke snakker sant selv om du kaller det bakoverkompatibelt så er det dobbelt opp med registre og det kan nesten kvalifiseres som 64 bits, derfor sier jeg det for å unngå forvirring.

 

2: Du tar min algoritme og implementerer den.

 

3: Du implementerer den på ett hardware sett som har dobbelt så mange registre og er ikke engang et 32 bits program.

 

Det er helt klart og utvilsomt ett slag som er tapt fra din side, utvilsomt.

Endret av LonelyMan
Lenke til kommentar
Det hadde også godt an å bruke en bedre generelle algoritme som fungere i O(0) tid istedenfor O(1) tid - men igjen så ville det ikke være sammenlignbar.

Jeg kan big-O notasjon, men aldri vært borti noe annet enn konstant tid O(1), hva skal O(0) bety?

jeg antar han mente O(1) og O(n), ikke O(0) og O(1)... :|

 

O(0) ville i såfall bety en algoritme uten instruksjoner i det hele tatt :)

Lenke til kommentar

En algoritme som bruker konstant tid på å gjøre noe.

Slik som å utføre noen beregninger som tar like lang tid uansett input.

 

Er noe O (n) sier man det tar lineær tid. Da er antall operasjoner som må gjøres proporsjonal med input.

F. eks det å finne minste tall i en liste. Da må du gå igjennom hele listen, og blir listen større tar det tilsvarende lang tid. Altså O (n)

Skal du finne første element i en liste spiller det jo ingen rolle hvor mange elementer som kommer etterpå, så det er O (1)

Sorteringsproblemer er typisk O (n*n)

Endret av Matsemann
  • Liker 1
Lenke til kommentar
Det hadde også godt an å bruke en bedre generelle algoritme som fungere i O(0) tid istedenfor O(1) tid - men igjen så ville det ikke være sammenlignbar.

Jeg kan big-O notasjon, men aldri vært borti noe annet enn konstant tid O(1), hva skal O(0) bety?

jeg antar han mente O(1) og O(n), ikke O(0) og O(1)... :|

 

O(0) ville i såfall bety en algoritme uten instruksjoner i det hele tatt :)

 

Korrekt - beklager feilskrivingen min. Du kan tenke at jeg mente å skrive O(n^1) og O(n^0) - det var ihvertfall det som var tanken.

Lenke til kommentar

En algoritme som bruker konstant tid på å gjøre noe.

Slik som å utføre noen beregninger som tar like lang tid uansett input.

 

Er noe O (n) sier man det tar lineær tid. Da er antall operasjoner som må gjøres proporsjonal med input.

F. eks det å finne minste tall i en liste. Da må du gå igjennom hele listen, og blir listen større tar det tilsvarende lang tid. Altså O (n)

Skal du finne første element i en liste spiller det jo ingen rolle hvor mange elementer som kommer etterpå, så det er O (1)

Sorteringsproblemer er typisk O (n*n)

 

Sortering er vanligvis O(n.log(n)) tid, ihvertfall med vanlige effektive algoritmer (heap sort, quick sort, insertion sort). Det fins O(n^2) sorting algoritmer, som bubblesort, som er greit når du trenger noe liten og enkelt, og ikke skal sortere store lister. Det fins også algoritmer som nærmer seg O(n), som bucket sort, men de er kun bedre enn O(n.log(n)) i bestemte omstendigheter.

Lenke til kommentar

Første poeng er at det er viktig at du poster kode når du legger ut tall når du sammenligner.

 

Jeg trodde jeg hadde postet en god del av koden. Jeg har slurvet litt med å poste C koden - men det er bare når det er soleklart hvordan C koden skal være, eller andre har også postet koden. Men mangler du noe bestemt, så kan jeg godt poste den. Jeg er selvfølgelig enige med det at man må ha koden for å kunne kjøre det på samme maskin, for å kunne sammenligne tider.

 

Neste poeng er at fordi jeg tenker ut bedre algoritmer enn deg, så blir ikke det automatisk juks, din algoritme var elendig og nå har du kopiert min, legger ut tall som ikke samsvarer med noen kode.

 

 

Joda, det er "juks" i denne sammenhengen. Jeg har en betydelige bedre algoritme enn deg - og jeg så den med en gang jeg leste utfordringen på bloggen. Men som andre har sagt, var ikke det poenget her, fordi en raskere algoritme viser ingenting i sammenligning mellom C og assembly. Utfordringen var for å gi deg sjanse til å overbevise andre at du kan skrive koden raskere i assembly enn andre kan i C - ikke for å se hvem som kan finne en bedre algoritme.

 

Koden i C er:

 

static int sumn(unsigned int n) {

// Return sum of 1 + 2 + .. + (n-1)

return (n * (n - 1)) / 2;

}

 

static unsigned int sum3or5(unsigned int n) {

unsigned int n3 = (n + 2) / 3;

unsigned int n5 = (n + 4) / 5;

unsigned int n15 = (n + 14) / 15;

return 3 * sumn(n3) + 5 * sumn(n5) - 15 * sumn(n15);

}

 

int main(void) {

printf("%i\n", sum3or5(1000000000));

return 0;

}

 

Men optimisering på, er den genererte assembly koden en instruksjon "movl $2138801452, %esi". Det vil si, kompilatoren er smart nok til å gjøre alle kalkulasjonene på forhånd siden talene er konstant. Det går ikke an å lage noe bedre koden enn det.

 

Dersom jeg bruker ekstra "volatile" variabler og begrenset optimisering for å tvinge kompilatoren å generere koden til kalkulasjon, tok den hele 8 nanosekunder å kjøre "sum3or5" funksjonen.

 

Er det lov for deg å endre algoritme, så er det lov for meg å gjøre det. Og jeg gjør det på en måte som er lite avhengig av de faktiske tall som vi bruker her - det er kompilatoren som gjør grovarbeidet.

 

 

Det tredje poenget er at du ikke kan sammenligne 64 bits kode med 32 bits kode på noen måter.

 

Joda, vi kan sammenligne dem. For det første er de fleste instruksjonene nokså likt. Det ville ha vært noen forskjell dersom jeg hadde brukt en 32-bit kompilatoren, men strukturen i genererte koden hadde vært likt. Statistisk sett er det få programmer som kjører mer enn 10-20% fortere etter omkompilering til 64-bit sammenlignet på 32-bit på samme systemet.

 

For det andre har 64-bit vært den kurante arkitekturen i PC verden i mange år - jeg kan ikke gjøre noe for at du er begrenset til gammeldags teknologi.

 

For det tredje, handler dette tråden mye om hvor egnet eller uegnet assembly er til generelle programmering, og faktum at du er ikke i stand til å benytte kraften i prosessoren din p.g.a. begrensninger til assembly er et veldig viktig poeng. Du kan ikke påstå at du kan skrive det raskeste mulig programmer til PC'en din, når du faktisk ikke bruke prosessoren.

 

 

Det fjerde poenget er at koden allerede ER rollet ut, det du sier høres veldig merkelig ut, det fins ikke flere registre å rolle ut i dette tilfellet. Så jeg er svært spent på å se koden. :thumbup:

 

De konstantene du snakker om kalles magic numbers. Jeg er godt kjent med de, men har ikke brukt de.

 

Nei, koden er ikke rollet ut. Du har rullet ut litt i koden din, men man kan gjøre det mye mer. For å ta den første loopen din:

 

n1: add eax, ebx

add ebx, edx

cmp ebx, ecx

jbe n1

 

Da har du en backover hopp for hver rundt, noe som koster i pipelining ytelse. En mer effektivt koding hadde vært:

 

n1: add eax, ebx

add ebx, edx

cmp ebx, ecx

ja n2

add eax, ebx

add ebx, edx

cmp ebx, ecx

ja n2

add eax, ebx

add ebx, edx

cmp ebx, ecx

ja n2

add eax, ebx

add ebx, edx

cmp ebx, ecx

jbe n1

n2:

 

Det kalles for loop unrolling, og det er noe som er tungvindt å gjøre manuelt i assembly, men enkelt å gjøre i en kompilator. Det er ikke alltid at det øker hastighet - det er en balanse mellom pipelining, branch kostnader, branch prediction buffers, cache linjer, prefetch buffers, osv. Kompilatoren har en nokså god ide om dette for alle de forskjellige prosessorene, men det blir litt "trial and error". Fort gjort med en kompilator der man bare endre noen command line switcher, men ikke så morsomt i assembly verden.

 

Og dersom du noen gang prøve å jobbe med en annen cpu som ikke er like register-fattig som x86, finner du fort ut hvor flott det er å ha en kompilator som holde redde på alle register - dette type unrolling kan nemlig la kompilatoren kjøre interleaving på itterasjonene ved å bruke forskjellige registerer og dermed unngå stalls p.g.a. dependencies.

 

(Jeg beklager at det blir en del engelske uttrykk her - dette er ikke noe jeg pleier å snakke om på norsk.)

  • Liker 1
Lenke til kommentar

Opprett en konto eller logg inn for å kommentere

Du må være et medlem for å kunne skrive en kommentar

Opprett konto

Det er enkelt å melde seg inn for å starte en ny konto!

Start en konto

Logg inn

Har du allerede en konto? Logg inn her.

Logg inn nå
×
×
  • Opprett ny...