David Brown Skrevet 16. desember 2011 Del Skrevet 16. desember 2011 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. 1 Lenke til kommentar
jonny Skrevet 16. desember 2011 Del Skrevet 16. desember 2011 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
David Brown Skrevet 16. desember 2011 Del Skrevet 16. desember 2011 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
jonny Skrevet 16. desember 2011 Del Skrevet 16. desember 2011 (endret) 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 16. desember 2011 av jonny Lenke til kommentar
LonelyMan Skrevet 16. desember 2011 Del Skrevet 16. desember 2011 (endret) 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 16. desember 2011 av LonelyMan Lenke til kommentar
LonelyMan Skrevet 16. desember 2011 Del Skrevet 16. desember 2011 Jeg er nå nede i 141 ms for 100 millioner uten å implementere sse, men jeg kommer som sagt tilbake senere, jeg har mye å gjøre. Lenke til kommentar
LonelyMan Skrevet 16. desember 2011 Del Skrevet 16. desember 2011 (endret) Nå er jeg nede i 31 ms. Endret 14. mai 2012 av LonelyMan Lenke til kommentar
LonelyMan Skrevet 16. desember 2011 Del Skrevet 16. desember 2011 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
LonelyMan Skrevet 16. desember 2011 Del Skrevet 16. desember 2011 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. Lenke til kommentar
torbjørn marø Skrevet 16. desember 2011 Del Skrevet 16. desember 2011 Ok nå har jeg skrevet en rutine, men den er på ingen måte optimalisert, men det er bare for å vise hva som skal til. ... Kan du gi meg en veldig kjapp veiledning i hva som skal til for at jeg får testet ut koden din selv? Hva må jeg installere, hva må jeg kjøre? Lenke til kommentar
LonelyMan Skrevet 16. desember 2011 Del Skrevet 16. desember 2011 (endret) 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 16. desember 2011 av LonelyMan Lenke til kommentar
torbjørn marø Skrevet 16. desember 2011 Del Skrevet 16. desember 2011 ... 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 Lenke til kommentar
LonelyMan Skrevet 16. desember 2011 Del Skrevet 16. desember 2011 (endret) 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 17. desember 2011 av LonelyMan Lenke til kommentar
LonelyMan Skrevet 16. desember 2011 Del Skrevet 16. desember 2011 (endret) 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 17. desember 2011 av LonelyMan 1 Lenke til kommentar
LonelyMan Skrevet 17. desember 2011 Del Skrevet 17. desember 2011 (endret) 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 17. desember 2011 av LonelyMan Lenke til kommentar
LonelyMan Skrevet 17. desember 2011 Del Skrevet 17. desember 2011 ... 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. 1 Lenke til kommentar
LonelyMan Skrevet 17. desember 2011 Del Skrevet 17. desember 2011 Så kan det være greit å forstå prosessen fra Kildekode -> til kjørbart program: Lenke til kommentar
LonelyMan Skrevet 17. desember 2011 Del Skrevet 17. desember 2011 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 2 Lenke til kommentar
LonelyMan Skrevet 17. desember 2011 Del Skrevet 17. desember 2011 (endret) 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 17. desember 2011 av LonelyMan 2 Lenke til kommentar
torbjørn marø Skrevet 17. desember 2011 Del Skrevet 17. desember 2011 Takk, LonelyMan. Mye info her, skal gå tilbake til det når jeg finner litt tid. Lenke til kommentar
Anbefalte innlegg
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 kontoLogg inn
Har du allerede en konto? Logg inn her.
Logg inn nå