Gå til innhold

Anbefalte innlegg

Første steg i optimaliseringen, nå er koden 9% raskere og kjører på 858 ms. (100 millioner tall), det høres kanskje ikke mye ut til å begynne med, men denne økningen betyr at du kan prosessere 9 millioner ekstra tall i samme tidsmengde.

 

OPTION PROLOGUE:NONE
OPTION EPILOGUE:NONE
ALIGN 4
Multipler PROC
push ebx
push esi
push edi
push ebp
mov ebx, 100000000
mov esi, 3
mov edi, 5
xor ebp, ebp
ALIGN 16
n1:	mov eax, ebx
xor edx, edx
div esi
test edx, edx
jz n2
mov eax, ebx
xor edx, edx
div edi
test edx, edx
jnz n3
n2:	add ebp, ebx
n3:	sub ebx, 1
jnz n1
mov eax, ebp
pop ebp
pop ebx
pop edi
pop esi
ret
Multipler ENDP
OPTION PROLOGUE:PROLOGUEDEF
OPTION EPILOGUE:EPILOGUEDEF

 

Jeg har nettopp prøve koden din. Det er svært tungvindt å ta det i bruk - det er jo ikke en komplett program, bare en funksjon, og man må skrive resten rundt det. Og siden det er i assembly, er det ikke portable. Men med litt endring (endring av alle push og pop til "r??" istedenfor "e??" former) fikk jeg kjørt den på 64-bit Linux i7-920 systemet mitt. Den kjørte i 0.978s - ikke langt i fra på din PC. Hvilke cpu har du på den?

 

Jeg kan nevne at koden din ga feil svar, men det kan være bare at jeg ikke klarte å få linket den riktig med en C stub som skriver ut svaret (selv om det ser riktig ut på generte assembly).

 

Jeg prøvde også med en svært enkel C versjon av programmet (identisk til versjon på Torbjørn sin webside, bare med ekte C og ikke C#). Jeg har ikke brukt noe spesielt optimisering - og den kjørte på 0.254 s.

 

Det vil si, C versjonen kjørte fire ganger raskere enn assembly versjon. Det er også en bagatell å kompilere og kjøre på hvilket som helst system, var mye raskere å skrive enn assembly versjonen, og er mye klarerer. Som jeg sa tidligere, for alt annet enn hobby bruk og moro, er det langt viktigere å skrive klar og forståelig kode som man vet er riktig, enn å skrive noe som kjører fort. I dette tilfelle er C koden 4 ganger raskere, og langt klarerer.

 

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

  • Liker 1
Lenke til kommentar
Videoannonse
Annonse

Hvilken assembler bruker du? Kunne vært interessant å prøve ut koden din...

 

Her er C-kode for å gjøre det samme:

 

#include <stdio.h>

int main() {
 int sum = 0;
 int i;
 for(i=0; i<100000000; i++)
   if (i % 3 == 0 || i % 5 == 0) sum += i;
 printf("%d\n",sum);
}

 

Den bruker omtrent 1465 ms på maskinen min.

 

Ada-kode:

 

with Ada.Text_IO;

procedure Euler1 is
  Sum : Integer := 0;
begin
  for i in 0..99999999 loop
     if (i mod 3 = 0) or else (i mod 5 = 0) then
        Sum := Sum + i;
     end if;
  end loop;
  Ada.Text_IO.Put_Line(Integer'Image(Sum));
end Euler1;

 

Denne bruker 1281 ms. Prosessoren koden kjører på er en Intel Core 2 U7300 (1.3 GHz).

Lenke til kommentar

Hvilken assembler bruker du? Kunne vært interessant å prøve ut koden din...

 

Her er C-kode for å gjøre det samme:

 

#include <stdio.h>

int main() {
 int sum = 0;
 int i;
 for(i=0; i<100000000; i++)
   if (i % 3 == 0 || i % 5 == 0) sum += i;
 printf("%d\n",sum);
}

 

Den bruker omtrent 1465 ms på maskinen min.

 

Ada-kode:

 

with Ada.Text_IO;

procedure Euler1 is
  Sum : Integer := 0;
begin
  for i in 0..99999999 loop
     if (i mod 3 = 0) or else (i mod 5 = 0) then
        Sum := Sum + i;
     end if;
  end loop;
  Ada.Text_IO.Put_Line(Integer'Image(Sum));
end Euler1;

 

Denne bruker 1281 ms. Prosessoren koden kjører på er en Intel Core 2 U7300 (1.3 GHz).

 

Jeg tror han bruker MS sin assembler på 32-bit Windows, men det er bare antagelse ut fra tidligere poster. Da jeg prøvde med koden hans brukte jeg gcc (d.v.s., gas i praksis) på 64-bit Linux. Som sagt, fikk jeg koden til å kjøre men med feil svar.

 

Det er interessant at Ada var raskere enn C. Er det gcc/gnat du bruker? Og er det store forskjeller i versjonene? Har du litt optimisering på (-Os eller -O2)? Det er mulig at default optimisering er forskjellige mellom C og Ada.

Lenke til kommentar

Bruker GNAT 4.4.6 og gcc 4.6.1 (kjører Ubuntu 11.10, 64-bit). Har kjørt gcc og gnatmake uten noen form for optimaliseringer.

 

Oppdatert: C-programmet bruker ca. 920 ms med -O1 (-O2 og -O3 er litt tregere).

Endret av jonny
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å.

Endret av LonelyMan
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

Lenke til kommentar

Da må du ha coff objekt filen og linke den inn i ditt c program, deretter bruker du den i programmet ditt. Men det er nå litt tungvint for en kjapp test, jeg kan heller gi deg selve exe fila om du vil.

 

Jeg kan eventuelt legge den i en dll så kan du laste den.

Endret av LonelyMan
Lenke til kommentar

Dll fil er vedlagt, funksjonen heter Multipler og den har 1 dword parameter. I denne dword parameteren setter du Max. F.eks 9 som max, da vil den returnere 23.

 

Ordinær verdien for funksjonen er 1 of course.

 

Filen ble oppdatert i dag, det visste seg å være ett problem i forbindelse med overføringen av rutinen til dll, en bagatellmessig sak, men nå fungerer dll'en. Jeg oppdaget det i morges ved en tilfeldighet.

Dll.zip

Endret av LonelyMan
Lenke til kommentar

Det fins mange assemblere å velge mellom. Tasm er ett gammelt utgått på dato, Masm er microsoft sitt verk, nasm, goasm, poasm, fasm. Hvis jeg skulle anbefale en så ville det kanskje være fasm. http://en.wikipedia.org/wiki/FASM

 

Men om du vil ha en assembler som jeg foretrekker og som jeg bruker og har brukt i eksemplene jeg har skrevet på forumet, så kan du laste ned her:

http://website.assemblercode.com/masm32/m32v10r.zip

 

Det du må kjøre for å assemblere et vanlig asm program er:

 

ml.exe (denne overfører koden din til en ren coff objekt fil)

link.exe (Denne syr sammen objekt filene (eventuelt biblioteker .lib) til det endelige exe programmmet.

 

Det skulle ikke være mer enn det.

 

Her er ett eksempel:

Du skriver først asm koden og vanligvis lagrer i ett .asm format, men dog ikke nødvendig, så gjør du følgende:

 

ml /c /coff /Cp Navn.asm

link /SUBSYSTEM:WINDOWS /LIBPATH:c:\lib Navn.obj

 

Sett path etter libpath til adressen hvor din assembler har lagret alle .lib filer. Personlig bruker jeg makefiles for dette. Jeg kan legge ved ett eksempel på en makefile, så går du bare i path'en og skriver NMAKE for å kompilere hele prosjektet.

 

Bruker du en IDE for din c editor så har den integrert makefile system. Men hvis du ikke bruker en RAD type ide så kan du bruke microsoft sin nmake utility

MAKEFILE.zip

Endret av LonelyMan
  • Liker 1
Lenke til kommentar

Her er samme funksjonen bare at det er i ett statisk bibliotek slik at du kan bruke det i c programmet ditt.

 

Så bruke det i visual c++ kan du putte noe slikt som dette i programmet ditt:

 

extern int Multipler(int)

 

hvis du skal bruke det samme biblioteket i Pascal må du huske å legge til ;Stdcall på slutten av prosedyre prototypen, pascal bruker omvendt calling convention enn c og asm.

Multipler.zip

Endret av LonelyMan
Lenke til kommentar

... jeg kan heller gi deg selve exe fila om du vil.

Jeg kan eventuelt legge den i en dll så kan du laste den.

 

Takk, men jeg står over. Var mer interessert i hva som skulle til for å komme igang med bittelitt assembly selv :)

 

Jeg paster inn en referanse jeg har skrevet om div filtyper, det kan være greit å forstå filtypene først og fremst:

 

File.asm - Contains mnemonic codes, library references. This is your sourcefile.
File.obj - Contains opcodes (machine codes). This is basically your program before the final linking stage.
File.rc  - Windows resource scripts. Save your resource scripts with this format before compiling it.
File.RES - Compiled resource scripts. Feed this file to your linker to sew it into the final executable file.
File.inc - Include files, they contain proto types, equations to include in your source file.
File.lib - Libraries. Contains functions and constants referenced by your program.
File.dll - Dynamic link libraries. These can be statically or dynamically linked into your program.
File.def - Module definition files. They provide the linker with information about exports, attributes etc.
File.exe - Windows 32-bit portable executable file. This is your final program.

  • Liker 1
Lenke til kommentar

Så for å lage det enkleste asm programmet, studer det, bryn deg litt på det. Kopier det til en .asm fil og forsøk å kompilere det fra den 3-step prosessen over. Bli litt kjent med det før en begynner å kode.

 

.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

.DATA

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

.DATA?


.CODE

Start:

; 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

end Start

  • Liker 2
Lenke til kommentar

Ok så kan vi gå over til å prøve å bryte ned denne saken:

 

.386

 

Dette er ett direktiv.

 

For å unngå forvirring, la oss slå opp hva direktiv betyr i ordboka:

 

"I direk|ti'v n3 (gj ty. fra mlat, av dirigere) rettesnor, forskrift, påbud vi har våre d-er å gå etter"

 

Altså, dette er en forskrift, rettesnor eller et påbud. "Påbud" er antagelig det beste ordet å bruke i denne sammenhengen. Dette direktivet (påbudet) forteller assembleren (ml.exe er assembleren) at vi har ett påbud til den, påbudet er at vi skal kun tillate instruksjoner som er kompatibelt med 80386 prosessoren.

 

Hvis vi gir dette påbudet til assembleren, så vil den gi ut en error til oss hvis vi prøver å kompilere en kildekode som inneholder instruksjoner som var introdusert etter 80386. Så dette direktivet/påbudet sier til assembleren, "Gi meg beskjed hvis jeg bruker instruksjoner som ikke er tillatt"

 

Dette er en fin måte å finne ut hvor "lavt" programmet ditt er kompatibelt tilbake i årene. Jeg vil forøvrig nevne at om du ønsker å bruke SSE må du definere .686 eller vil selvfølgelig assembleren klage om du ikke har gjort det.

 

Om du definerer .686 så betyr ikke det at din kode ikke vil kjøre på en 80386, så det er lett å bli forvirret her. ALT dette direktivet gjør, er å gi deg beskjed når du bruker instruksjoner over den prosessoren definert. Men om du faktisk bruker instruksjoner som er over denne blir noe helt annet. Så det er bare en "advarselses" funksjonalitet i assembleren.

 

Så dette er ett greit og forståelig direktiv/påbud.

 

.model flat, stdcall

 

Hva betyr så dette. .model er også ett direktiv til assembleren. Jeg vil forøvrig nevne at du ikke behøver å ha alle direktiver direkte i .asm filen, du kan spesifisere de samme direktivene når du bruker ml.exe til å assemblere kildekoden, da kan du bruke visse parametere der for å spesifisere nøyaktig de samme direktivene.

 

"flat" betyr at du definerer en flat minnemodell, windows kjører nå under protected mode, og der bruker vi en flat minnemodell, den må alltid være flat, så alle programmene dine vil bruke den flate modellen.

 

stdcall betyr at vi benytter standard calling convention, asm og c har lik calling convention.

 

Hva betyr nå dette med calling convention. La oss slå opp ordet "konvensjon" i ordboka:

 

"(mellomfolkelig) avtale, traktat postk-, telegrafk-, Genèvek-en"

 

I vårt tilfelle vil "avtale" eller "traktat" passe godt.

 

stdcall er en avtale med assembleren om hvilken rekkefølge push og pop utføres når en kaller prosedyrer.

 

Standard avtale om dette i assembler er siste parameter først. Så du vil i de aller fleste tilfeller bruke stdcall

 

option casemap:none

 

option er også ett direktiv til assembleren (ml.exe)

 

option casemap:none kan også settes som parameter når du kjører ml.exe istedet for å ha det i kildekoden, så du har valgfrihet. casemap:none betyr at alle labeler, alle prosedyrer og alle variabler har case sensitive, dvs, du kan ikke kjøre en prosedyre slik:

 

INVOKE Prosedyren

 

hvis prosedyren er definert slik:

 

PRosedyre

 

casemap:none gjør at alle labeler er case sensitive.

 

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

 

Dette er include filer, dette er ingen ny oppfinnelse og eksisterer i alle språk så jeg behøver ikke utdype det. .inc filene inneholder funksjon prototyper, equates, strukturer, Typedef's og, ja alt mulig rart som en behøver å inkludere.

 

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

 

Og her er selve bibliotekene, disse er selvfølgelig helt nødvendig å ha med. Jeg vil forøvrig nevne at du behøver ikke ha /LIBPATH med i link.exe prosessen hvis du spesifiserer full path her. Dette er også en valgfrihet. Men grunnen til at vi har denne valgfriheten er fordi om du skal dele kildekoden med noen andre, og de har en annen libpath enn din, så kan de spesifisere denne libpath når de linker programmet (link.exe)

 

.DATA

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

.DATA?

 

Det fins to dataseksjoner i assembler programmer, det ene er initialisert data seksjon og den andre er uinitialisert data seksjon. Forskjellen mellom disse to ser du tydelig ved at den ene seksjonen heter .DATA og den andre heter .DATA? med ett spørsmålstegn etter. Spørsmålstegnet betyr at dataene er ukjente, uinitialisert.

 

Så hva skal vi med to slike data seksjoner. Vel, alt du definerer i den initialiserte seksjonen (.DATA) vil bli direkte kopiert rett inn i exe fila, og den øker i størrelse med en gang, den kan bli utrolig stor avhengig av hvor mye data du putter her.

 

Mens i den uinitialiserte varianten, så inkluderer ikke assembleren dataene og .exe fila blir mindre, den bare reserverer plass for fremtidig data som skal fylles ut der før eller senere.

 

Jeg vil forøvrig nevne at vi ikke bruker noen uinitialisert data i dette eksemplet her, så direktivet står tomt.

 

.CODE

 

Dette direktivet forteller assembleren (ml.exe) at vår kode begynner her.

 

Start:

 

Denne Start labelen kan være litt frustrerende å se på. Hvorfor vi bruker Start i begynnelsen av code section og "end Start" i slutten av programmet.

 

Du kan kalle denne start labelen akkurat hva du vil, du kan kalle den "Per" f.eks. Slik som dette:

 

Per:

... masse kode her

... masse kode her

end Per

 

linkeren (link.exe) bruker den første labelen den finner i programmet og definerer den som starten på programmet, med mindre du endrer start posisjonen ved å bruke parameteren /ENTRY når du kjører link.exe

 

Mer er det ikke om det.

 

; 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

 

Disse to funksjonene er rett-frem og enkel å forstå.

 

MessageBox er en windows API funksjon som tilhører User32.lib

ExitProcess er også en windows API funksjon som tilhører Kernel32.lib

 

Når det gjelder direktivet INVOKE, så er det slik du kaller prosedyrer i assembler. Men dette direktivet fins ikke i alle assemblere, det fins dog i microsoft sin assembler, den vi bruker nå. Og det dette direktivet gjør, er at det tar seg av push og pop når du kaller funksjoner. Den største feilen nybegynnere gjør når de koder assembler er at de pusher variabler i feil rekkefølge på stacken og programmet krasjer. Når en bruker invoke, så garanterer assembleren at parametere blir korrekt pushet.

 

Du kan dog gjøre det manuelt, la oss sammenligne INVOKE med manuell måte å kalle prosedyrer:

 

INVOKE Minfunksjon, parameter1, parameter2

 

Dette kan gjøres manuelt, men da er det viktig at du pusher den siste parameteren først:

 

push parameter2

push parameter1

CALL Minfunksjon

 

De 3 siste linjene utfører mer eller mindre det samme som INVOKE gjør. Så alltid bruk INVOKE, men når du føler deg mer trygg kan du bruke CALL også.

 

Vær obs på at når en bruker INVOKE, så er assembleren nødt til å vite parameterne på funksjonen for at den skal kunne gjøre dette automatisk for deg. Og hvis funksjonen fins i en helt annen plass utenom din kildekode så vet ikke assembleren noen verdens ting, så du er nødt å lage en prototype og plassere den øverst i programmet ditt. Du må kun skrive prototyper for dine egne personlige funksjoner, ikke for bibliotekene som allerede eksisterer i din assembler.

 

prototypen defineres slik:

 

Prosedyrenavn PROTO :DWORD,:DWORD

 

Du skriver ikke navnet på parameterne, men kun datatypen slik som over. Plasser den så øverst i .asm fila, over .DATA direktivet er en god plass å legge den.

Endret av LonelyMan
  • Liker 2
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...