LonelyMan Skrevet 28. september 2012 Del Skrevet 28. september 2012 (endret) ....mens det kan ta fem minutter å skrive et ekvivalent program i C++ som tar deg en halvtime til en time å skrive i Assembly. Mikrooptimaliseringer på assembly output er ikke verdt innsatsen. Jeg er enig om du snakker om deler av programmet som er uviktig og som ikke brukes gjentakende. Men om du snakker om en loop i et spill f.eks, så er disse 30 minuttene verdt det om det øker hastigheten med en faktor av 2 eller 3. 30 minutter for den viktige loopen vil jeg si er verdt. Om en spillgruppe som FireAxis f.eks bruker 30 ekstra minutter på siste dag av utviklingen og øker hastigheten med en faktor av 2 eller 3 i en viktig del av spillet, så vil jeg påstå det er verdt det. Tenk deg selv, de har utviklet et spill i 2 år, og siste dagen for spillet er igang, folk kommer på jobb kl halv ni på morgenen og optimaliserer en viktig loop i spillet og er ferdig kl 9, deretter tar de en kaffepause. Resultat: Spillet kjører 3 ganger raskere. Endret 28. september 2012 av LonelyMan Lenke til kommentar
Gjest Slettet+9871234 Skrevet 29. september 2012 Del Skrevet 29. september 2012 Oppsummert så er resultatet dette: Drogun sin 64 bit kode ciphrer 2,27 GB per sekund Drogun sin 32 bit kode ciphrer 2,22 GB per sekund Min 32 bit assembler kode ciphrer 6,66 GB per sekund 64 bit kode enda ikke laget. Drogun satte c++ kompilatoren til maksimal optimalisering. Og jeg optimaliserte min kode for hånd. Dvs kompilatoren gjorde sitt beste know-how, og jeg kan ikke si jeg har gjort mitt beste, der er ting jeg enda kan se litt nærmere på. I tillegg vil jeg si at de tingene jeg har endret på i koden min er ikke en algoritme endring, det er en endring i bruk av maskininstruksjoner, og denne endringen kan ikke la seg gjenspeile ved å endre algoritmen i c++ varianten. Det er visse ting som ikke lar seg gjøre i c++. Det fins to felt i algoritme-verdenen, den ene er å endre algoritmen i en pseudo kode (noe som kan gjøres i c++) den andre er å endre algoritmen eller sammensetningen av instruksjoner og det kan kun gjøres i asm. Interessant. Drogun satte c++ kompilatoren til maksimal optimalisering. Og jeg optimaliserte min kode for hånd. Dvs kompilatoren gjorde sitt beste know-how, og jeg kan ikke si jeg har gjort mitt beste, der er ting jeg enda kan se litt nærmere på. Jeg venter spent på fortsettelsen og på 64 bit kode. Lenke til kommentar
LonelyMan Skrevet 29. september 2012 Del Skrevet 29. september 2012 (endret) Når det gjelder hvor raskt en kan skrive i assembly, så skal en heller ikke undervurdere det, der er mange direktiver og makroinstruksjoner som gjør ting ekstremt effektivt. Dette nibbles spillet jeg legger vedlagt har jeg skrevet på en dag. Det er ikke ferdig, og det er kun første level (ingen vegger på første level) Den er skrevet for 4 kjerner prosessorer og bør kjøres på en slik cpu. Last ned hvis du vil teste. Skrevet i helt ren assembler. Det hadde ikke vært nødvendig å bruke 4 kjerner da spillet er så lite krevende. Men det har betydning på keyboard polling. 1 kjerne rendererer grafikk 2 kjerne trekker taster fra tastaturet 3 kjerne genererer random tall for plassering av matbiter 4 kjerne genererer høypresisjons game-tick Nibbles.rar Endret 29. september 2012 av LonelyMan Lenke til kommentar
Gjest Slettet+9871234 Skrevet 29. september 2012 Del Skrevet 29. september 2012 (endret) Nå må du passe deg eller så blir du som DVD John kjøpt opp av et stort utenlandsk programvarehus. De beste komponister og musikere får opptre på de største sener. Du ser ut til å være godt i gang. Jeg ønsker deg all lykke i fortsettelsen. Hva med assembly på iPhone? Er det en mulighet? Endret 29. september 2012 av Slettet+9871234 Lenke til kommentar
GeirGrusom Skrevet 29. september 2012 Del Skrevet 29. september 2012 (endret) ....mens det kan ta fem minutter å skrive et ekvivalent program i C++ som tar deg en halvtime til en time å skrive i Assembly. Mikrooptimaliseringer på assembly output er ikke verdt innsatsen. Jeg er enig om du snakker om deler av programmet som er uviktig og som ikke brukes gjentakende. Men om du snakker om en loop i et spill f.eks, så er disse 30 minuttene verdt det om det øker hastigheten med en faktor av 2 eller 3. 30 minutter for den viktige loopen vil jeg si er verdt. Om en spillgruppe som FireAxis f.eks bruker 30 ekstra minutter på siste dag av utviklingen og øker hastigheten med en faktor av 2 eller 3 i en viktig del av spillet, så vil jeg påstå det er verdt det. Tenk deg selv, de har utviklet et spill i 2 år, og siste dagen for spillet er igang, folk kommer på jobb kl halv ni på morgenen og optimaliserer en viktig loop i spillet og er ferdig kl 9, deretter tar de en kaffepause. Resultat: Spillet kjører 3 ganger raskere. Hva hvis spillet også skal kjøre på x86, AMD64, Xbox360, Playstation 3 og Nintendo Wii, så må du i praksis gjøre dette fem ganger, mens du ikke trenger å gjøre noen ting dersom alt er skrevet i C++. Det som er vanlig praksis i spill, er å skrive utvidelsene som prosessoren støtter til vektor og matriseoperasjoner i assembly ved å benyttte Advanced Vector Extensions og SSE, for du tjener mye mer på dette enn å skrive main loopen i assembly som effektivt dreper portabilitet. Endret 29. september 2012 av GeirGrusom Lenke til kommentar
LonelyMan Skrevet 29. september 2012 Del Skrevet 29. september 2012 (endret) Stort sett alle assemblere har som regel et generelt bibliotek det shipper med, og dette all-purpose biblioteket har rutiner som er skrevet for å være portabel for de fleste systemer og arkitekturer, det er portabelt. I masm heter dette biblioteket masm32, i fasm heter det fasmlib og freshlib. Nå er AVX langt fra støttet på alle arkitekturer det kom først på sandy bridge som ikke er så veldig gammelt. Jeg er enig i at å fokusere på andre deler av optimaliseringer kan være gunstig, men dagens spill (spesielt RTS spill) sliter voldsomt helhetlig med slimaktig respons i spillet, og det henger nøye sammen med den bort prioriteringen du snakker om. Om du skal gjøre koden portabel i asm behøver du ikke skrive den dobbelt eller tredobbelt, du bruker kondisjonell programmering kombinert med makroer. f.eks: IF THIS=SUPPORTED DO THIS ELSE DO THAT END IF Og du lager bibliotekene slik, det blir ikke dobbelt med instruksjoner, men det blir litt ekstra. Her er et bittelite eksempel: mov eax,10 cmp eax,10 IF X=SUPPORTED cmovb eax,ecx ELSE jb @F END IF her bytter vi bort bare en enkelt instruksjon midt i en haug av instruksjoner for å støtte en annen arkitektur, man skriver ikke det dobbelt opp. Endret 29. september 2012 av LonelyMan Lenke til kommentar
Gjest Slettet+9871234 Skrevet 29. september 2012 Del Skrevet 29. september 2012 (endret) Hva hvis spillet også skal kjøre på x86, AMD64, Xbox360, Playstation 3 og Nintendo Wii, så må du i praksis gjøre dette fem ganger, mens du ikke trenger å gjøre noen ting dersom alt er skrevet i C++. Det er en selvfølgelighet og ikke noe argument for ikke å lære seg assembly som denne tråden dreier seg om. Det at man behersker instruksjonssettet for en populær prosessor, kan være mer enn godt nok. Noen ganger er godt nok best. Om du skal gjøre koden portabel i asm behøver du ikke skrive den dobbelt eller tredobbelt, du bruker kondisjonell programmering kombinert med makroer. f.eks: IF THIS=SUPPORTED DO THIS ELSE DO THAT END IF Vel også kalt betinget kompilering eller husker jeg feil? Endret 29. september 2012 av Slettet+9871234 Lenke til kommentar
LonelyMan Skrevet 29. september 2012 Del Skrevet 29. september 2012 Betinget kompilering ja Lenke til kommentar
Gjest Slettet+9871234 Skrevet 29. september 2012 Del Skrevet 29. september 2012 Har du vurdert å skrive en bok om dette? Lenke til kommentar
LonelyMan Skrevet 29. september 2012 Del Skrevet 29. september 2012 Det har ikke falt meg inn he-he. Er du assembler programmerer selv? Lenke til kommentar
Gjest Slettet+9871234 Skrevet 29. september 2012 Del Skrevet 29. september 2012 Jeg drev litt med assembly for ca 20 år siden og nå er resten av livet mitt for kort til å drive med det. Jeg synes det er svært interessant at noen i Norge driver med dette. Der skulle vært et assembly miljø her i landet. Tror der er mye å vinne på at assembly programmerere på ulike plattformer utveksler ideer, erfaringer og programmer. Lenke til kommentar
jonny Skrevet 29. september 2012 Del Skrevet 29. september 2012 (endret) Lurer på om ikke jeg blingsa da jeg skrev den koden ovenfor. Den var vel 2.27 GB/s, ikke 2.72GB/s. Men det var jo fortsatt noe raskere enn assembler-programmet. Når jeg kompilerer for 32bit, blir programmet mitt omtrent like raskt som ditt: 100MB tar 45-46ms. Det vil si et sted mellom 2.17 og 2.22GB/s. Hva slags prosessor bruker du? Jeg endret litt på koden (ANSI C) og kompilerer med gcc på Ubuntu 64-bit, men prosessoren min bruker mer enn 350 ms... (riktignok ikke den skarpeste kniven i skuffen, AMD A6-3410MX 1.6 GHz, men dog...). Kanskje kompilatoren gjør en dårlig jobb? Jeg kompilerer slik: gcc arraytester.c -O3 -o arraytester -lrt Koden ser slik ut: #include <stdlib.h> #include <stdio.h> #include <string.h> #include <time.h> #define CPU_CLOCK_ID CLOCK_REALTIME static long currentTimeMillis(void) { struct timespec now; clock_gettime(CPU_CLOCK_ID, &now); return now.tv_sec * 1000 + now.tv_nsec / 1000000; } static char lookup[256]; void randomStrGen(char *str, int length) { char *charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; srand(time(NULL)); int i; for (i = 0; i < length; i++) str[i] = charset[rand() % strlen(charset)]; } void stringRotation(char *text, int len) { int i; for (i = 0; i < len; i++) { int tmp = text[i]; text[i] = lookup[tmp]; } } void createLookup() { int c; for (c = 0; c < 256; c++) { lookup[c] = c; } for (c = 'A'; c <= 'Z' ; c++) { if (c <= 'M') { lookup[c] = c + 13; } else { lookup[c] = c - 13; } } for (c = 'a'; c <= 'z'; c++) { if (c <= 'm') { lookup[c] = c + 13; } else { lookup[c] = c - 13; } } } int main(int argc, char *argv[]) { // Build test string int size = 1000000 * 100; char *test = (char*) malloc(size); memset(test, 0, size); randomStrGen(test, size-1); // Build lookuptable createLookup(); // Start long before = currentTimeMillis(); stringRotation(test, size); long after = currentTimeMillis(); printf("100 Megabytes took %ld milliseconds.\n", (after - before)); // Done free(test); return 0; } Endret 29. september 2012 av jonny Lenke til kommentar
LonelyMan Skrevet 29. september 2012 Del Skrevet 29. september 2012 (endret) Når man bruker målere med millisekund så er det litt viktig å synkronisere tiden først. Om du kjører slik: TimeStart KjøreKodeHer TimeSlutt så vil resultatet bli litt unøyaktig. Man må først hente GetTickCount, så vente til neste GetTickCount er forskjellig fra første, sånn at timingen begynner akkurat idet tickeren går til et nytt tall, og ikke begynner med fraksjoner imellom. Den rette målingen blir da: a = GetTickCount b = GetTickCount WHILE a == b DO b = GetTickCount ENDW Kjør så kode her.... Kjør så kode her.... c = GetTickCount Resultat = c - b Men for å måle nøyaktig anbefaler jeg å bruke QueryPerformanceCounter og QueryPerformanceFrequency Endret 29. september 2012 av LonelyMan Lenke til kommentar
jonny Skrevet 29. september 2012 Del Skrevet 29. september 2012 Takk for det. Er vel egentlig klar over at målinga mi nok ikke er den beste, men de metodene du nevner vet jeg ingenting om. Jeg prøvde forøvrig å kompilere slik isteden: gcc arraytester.c -O3 -funroll-loops -o arraytester og da havner tiden på rett under 100 ms Lenke til kommentar
Paull Skrevet 30. september 2012 Del Skrevet 30. september 2012 Først og fremst vil jeg si det er rimelig useriøst å sammenligne throughput på forskjellige implementasjoner uten å kjøre det på samme hardware. Men mens vi nå først er der, her er min halv-optimaliserte implementasjon: Med utgangspunkt i jonny's kode og et par modifiseringer (hovedsaklig tidsmålingen + packing) har jeg kommet frem til følgende (ASM-Output fra VS2010): ; Listing generated by Microsoft (R) Optimizing Compiler Version 16.00.40219.01 TITLE C:\Users\xxx\documents\visual studio 2010\Projects\Rot13Test\Rot13Test\rot13.cpp .686P .XMM include listing.inc .model flat INCLUDELIB OLDNAMES PUBLIC ?PCFreq@@3NA ; PCFreq PUBLIC ?CounterStart@@3_JA ; CounterStart PUBLIC ??_C@_0DF@MEPDHLIP@abcdefghijklmnopqrstuvwxyzABCDEF@ ; `string' PUBLIC ??_C@_0BD@ONNLNDEL@QPC?5did?5not?5work?4?4?$AA@ ; `string' PUBLIC ??_C@_0CF@MOIGNLJO@100?5Megabytes?5took?5?$CFf?5millisecon@ ; `string' PUBLIC ??_C@_09IJINBEEB@MB?1s?5?$DN?5?$CFf?$AA@ ; `string' EXTRN __imp__QueryPerformanceFrequency@4:PROC EXTRN __imp__QueryPerformanceCounter@4:PROC EXTRN __imp__rand:PROC EXTRN __imp__srand:PROC EXTRN __imp___time64:PROC EXTRN __imp__free:PROC EXTRN __imp__malloc:PROC EXTRN __imp__printf:PROC ?PCFreq@@3NA DQ 01H DUP (?) ; PCFreq ?CounterStart@@3_JA DQ 01H DUP (?) ; CounterStart ; COMDAT ??_C@_09IJINBEEB@MB?1s?5?$DN?5?$CFf?$AA@ CONST SEGMENT ??_C@_09IJINBEEB@MB?1s?5?$DN?5?$CFf?$AA@ DB 'MB/s = %f', 00H ; `string' CONST ENDS ; COMDAT ??_C@_0CF@MOIGNLJO@100?5Megabytes?5took?5?$CFf?5millisecon@ CONST SEGMENT ??_C@_0CF@MOIGNLJO@100?5Megabytes?5took?5?$CFf?5millisecon@ DB '100 Megab' DB 'ytes took %f milliseconds.', 0aH, 00H ; `string' CONST ENDS ; COMDAT ??_C@_0BD@ONNLNDEL@QPC?5did?5not?5work?4?4?$AA@ CONST SEGMENT ??_C@_0BD@ONNLNDEL@QPC?5did?5not?5work?4?4?$AA@ DB 'QPC did not work..', 00H ; `string' CONST ENDS ; COMDAT ??_C@_0DF@MEPDHLIP@abcdefghijklmnopqrstuvwxyzABCDEF@ CONST SEGMENT ??_C@_0DF@MEPDHLIP@abcdefghijklmnopqrstuvwxyzABCDEF@ DB 'abcdefghijklmnop' DB 'qrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', 00H ; `string' CONST ENDS PUBLIC ?createLookup@@YAXXZ ; createLookup _lookup DB 0100H DUP (?) ; Function compile flags: /Ogtp ; COMDAT ?createLookup@@YAXXZ _TEXT SEGMENT ?createLookup@@YAXXZ PROC ; createLookup, COMDAT ; File c:\users\xxx\documents\visual studio 2010\projects\rot13test\rot13test\rot13.cpp ; Line 56 xor eax, eax $LL13@createLook: ; Line 57 mov BYTE PTR _lookup[eax], al inc eax cmp eax, 256 ; 00000100H jl SHORT $LL13@createLook ; Line 60 mov eax, 65 ; 00000041H $LL10@createLook: ; Line 61 cmp eax, 77 ; 0000004dH jg SHORT $LN7@createLook ; Line 62 lea ecx, DWORD PTR [eax+13] mov BYTE PTR _lookup[eax], cl ; Line 63 jmp SHORT $LN9@createLook $LN7@createLook: ; Line 64 lea edx, DWORD PTR [eax-13] mov BYTE PTR _lookup[eax], dl $LN9@createLook: ; Line 60 inc eax cmp eax, 90 ; 0000005aH jle SHORT $LL10@createLook ; Line 68 mov eax, 97 ; 00000061H npad 7 $LL5@createLook: ; Line 69 cmp eax, 109 ; 0000006dH jg SHORT $LN2@createLook ; Line 70 lea ecx, DWORD PTR [eax+13] mov BYTE PTR _lookup[eax], cl ; Line 71 jmp SHORT $LN4@createLook $LN2@createLook: ; Line 72 lea edx, DWORD PTR [eax-13] mov BYTE PTR _lookup[eax], dl $LN4@createLook: ; Line 68 inc eax cmp eax, 122 ; 0000007aH jle SHORT $LL5@createLook ; Line 75 ret 0 ?createLookup@@YAXXZ ENDP ; createLookup _TEXT ENDS PUBLIC ?stringRotation@@YAXPADH@Z ; stringRotation _lookupWide DW 010000H DUP (?) ; Function compile flags: /Ogtp ; COMDAT ?stringRotation@@YAXPADH@Z _TEXT SEGMENT ?stringRotation@@YAXPADH@Z PROC ; stringRotation, COMDAT ; _text$ = eax ; Line 45 inc eax mov ecx, 5000000 ; 004c4b40H $LL3@stringRota: ; Line 50 movsx edx, BYTE PTR [eax-1] and edx, 65535 ; 0000ffffH movzx edx, BYTE PTR _lookupWide[edx*2] mov BYTE PTR [eax-1], dl movsx edx, BYTE PTR [eax] and edx, 65535 ; 0000ffffH movzx edx, BYTE PTR _lookupWide[edx*2] mov BYTE PTR [eax], dl movsx edx, BYTE PTR [eax+1] and edx, 65535 ; 0000ffffH movzx edx, BYTE PTR _lookupWide[edx*2] mov BYTE PTR [eax+1], dl movsx edx, BYTE PTR [eax+2] and edx, 65535 ; 0000ffffH movzx edx, BYTE PTR _lookupWide[edx*2] mov BYTE PTR [eax+2], dl movsx edx, BYTE PTR [eax+3] and edx, 65535 ; 0000ffffH movzx edx, BYTE PTR _lookupWide[edx*2] mov BYTE PTR [eax+3], dl add eax, 5 dec ecx jne SHORT $LL3@stringRota ; Line 52 ret 0 ?stringRotation@@YAXPADH@Z ENDP ; stringRotation _TEXT ENDS PUBLIC ?GetCounter@@YANXZ ; GetCounter EXTRN __fltused:DWORD ; Function compile flags: /Ogtp ; COMDAT ?GetCounter@@YANXZ _TEXT SEGMENT tv73 = -8 ; size = 8 _li$ = -8 ; size = 8 ?GetCounter@@YANXZ PROC ; GetCounter, COMDAT ; Line 22 push ebp mov ebp, esp sub esp, 8 ; Line 24 lea eax, DWORD PTR _li$[ebp] push eax call DWORD PTR __imp__QueryPerformanceCounter@4 ; Line 25 mov ecx, DWORD PTR _li$[ebp] sub ecx, DWORD PTR ?CounterStart@@3_JA mov edx, DWORD PTR _li$[ebp+4] sbb edx, DWORD PTR ?CounterStart@@3_JA+4 mov DWORD PTR tv73[ebp], ecx mov DWORD PTR tv73[ebp+4], edx fild QWORD PTR tv73[ebp] fdiv QWORD PTR ?PCFreq@@3NA ; PCFreq ; Line 26 mov esp, ebp pop ebp ret 0 ?GetCounter@@YANXZ ENDP ; GetCounter _TEXT ENDS PUBLIC __real@408f400000000000 PUBLIC ?StartCounter@@YAXXZ ; StartCounter ; COMDAT __real@408f400000000000 CONST SEGMENT __real@408f400000000000 DQ 0408f400000000000r ; 1000 ; Function compile flags: /Ogtp CONST ENDS ; COMDAT ?StartCounter@@YAXXZ _TEXT SEGMENT _li$ = -8 ; size = 8 ?StartCounter@@YAXXZ PROC ; StartCounter, COMDAT ; Line 11 push ebp mov ebp, esp sub esp, 8 ; Line 13 lea eax, DWORD PTR _li$[ebp] push eax call DWORD PTR __imp__QueryPerformanceFrequency@4 test eax, eax je SHORT $LN2@StartCount ; Line 16 fild QWORD PTR _li$[ebp] ; Line 18 lea ecx, DWORD PTR _li$[ebp] push ecx fdiv QWORD PTR __real@408f400000000000 fstp QWORD PTR ?PCFreq@@3NA ; PCFreq call DWORD PTR __imp__QueryPerformanceCounter@4 ; Line 19 mov edx, DWORD PTR _li$[ebp] mov eax, DWORD PTR _li$[ebp+4] mov DWORD PTR ?CounterStart@@3_JA, edx mov DWORD PTR ?CounterStart@@3_JA+4, eax $LN2@StartCount: ; Line 20 mov esp, ebp pop ebp ret 0 ?StartCounter@@YAXXZ ENDP ; StartCounter ; Function compile flags: /Ogtp _TEXT ENDS ; COMDAT _time _TEXT SEGMENT _time PROC ; COMDAT ; File c:\program files (x86)\microsoft visual studio 10.0\vc\include\time.inl ; Line 133 push 0 call DWORD PTR __imp___time64 add esp, 4 ; Line 134 ret 0 _time ENDP _TEXT ENDS PUBLIC ?randomStrGen@@YAXPADH@Z ; randomStrGen ; Function compile flags: /Ogtp ; COMDAT ?randomStrGen@@YAXPADH@Z _TEXT SEGMENT ?randomStrGen@@YAXPADH@Z PROC ; randomStrGen, COMDAT ; _str$ = ebx ; File c:\users\xxx\documents\visual studio 2010\projects\rot13test\rot13test\rot13.cpp ; Line 31 push esi push edi ; Line 34 push 0 call DWORD PTR __imp___time64 push eax call DWORD PTR __imp__srand ; Line 36 mov edi, DWORD PTR __imp__rand add esp, 8 xor esi, esi npad 4 $LL3@randomStrG: ; Line 37 call edi cdq mov ecx, 52 ; 00000034H idiv ecx inc esi mov dl, BYTE PTR ??_C@_0DF@MEPDHLIP@abcdefghijklmnopqrstuvwxyzABCDEF@[edx] mov BYTE PTR [esi+ebx-1], dl cmp esi, 99999999 ; 05f5e0ffH jl SHORT $LL3@randomStrG ; Line 38 pop edi pop esi ret 0 ?randomStrGen@@YAXPADH@Z ENDP ; randomStrGen _TEXT ENDS PUBLIC __real@4059000000000000 PUBLIC __real@0000000000000000 PUBLIC _main EXTRN _memset:PROC ; COMDAT __real@4059000000000000 CONST SEGMENT __real@4059000000000000 DQ 04059000000000000r ; 100 CONST ENDS ; COMDAT __real@0000000000000000 CONST SEGMENT __real@0000000000000000 DQ 00000000000000000r ; 0 ; Function compile flags: /Ogtp CONST ENDS ; COMDAT _main _TEXT SEGMENT tv180 = -8 ; size = 8 _li$67111 = -8 ; size = 8 _li$67106 = -8 ; size = 8 _li$67101 = -8 ; size = 8 _durationMS$ = -8 ; size = 8 _argc$ = 8 ; size = 4 _argv$ = 12 ; size = 4 _main PROC ; COMDAT ; Line 125 push ebp mov ebp, esp and esp, -64 ; ffffffc0H sub esp, 52 ; 00000034H push ebx push esi push edi ; Line 129 push 100000000 ; 05f5e100H call DWORD PTR __imp__malloc add esp, 4 ; Line 130 push 100000000 ; 05f5e100H mov edi, eax push 0 push edi call _memset add esp, 12 ; 0000000cH ; Line 131 push 0 call DWORD PTR __imp___time64 add esp, 4 push eax call DWORD PTR __imp__srand mov ebx, DWORD PTR __imp__rand add esp, 4 xor esi, esi npad 7 $LL6@main: call ebx cdq mov ecx, 52 ; 00000034H idiv ecx inc esi mov dl, BYTE PTR ??_C@_0DF@MEPDHLIP@abcdefghijklmnopqrstuvwxyzABCDEF@[edx] mov BYTE PTR [esi+edi-1], dl cmp esi, 99999999 ; 05f5e0ffH jl SHORT $LL6@main ; Line 134 call ?createLookup@@YAXXZ ; createLookup ; Line 137 mov ebx, DWORD PTR __imp__QueryPerformanceFrequency@4 lea eax, DWORD PTR _li$67101[esp+64] push eax call ebx mov esi, DWORD PTR __imp__QueryPerformanceCounter@4 test eax, eax je SHORT $LN12@main fild QWORD PTR _li$67101[esp+64] lea ecx, DWORD PTR _li$67101[esp+64] push ecx fdiv QWORD PTR __real@408f400000000000 fstp QWORD PTR ?PCFreq@@3NA ; PCFreq call esi mov edx, DWORD PTR _li$67101[esp+64] mov eax, DWORD PTR _li$67101[esp+68] mov DWORD PTR ?CounterStart@@3_JA, edx mov DWORD PTR ?CounterStart@@3_JA+4, eax $LN12@main: ; Line 138 fld QWORD PTR ?PCFreq@@3NA ; PCFreq fldz fucompp fnstsw ax test ah, 68 ; 00000044H jp SHORT $LN1@main ; Line 140 push OFFSET ??_C@_0BD@ONNLNDEL@QPC?5did?5not?5work?4?4?$AA@ call DWORD PTR __imp__printf add esp, 4 ; Line 141 mov eax, 1 ; Line 159 pop edi pop esi pop ebx mov esp, ebp pop ebp ret 0 $LN1@main: ; Line 145 lea ecx, DWORD PTR _li$67106[esp+64] push ecx call ebx test eax, eax je SHORT $LN15@main fild QWORD PTR _li$67106[esp+64] lea edx, DWORD PTR _li$67106[esp+64] push edx fdiv QWORD PTR __real@408f400000000000 fstp QWORD PTR ?PCFreq@@3NA ; PCFreq call esi mov eax, DWORD PTR _li$67106[esp+64] mov ecx, DWORD PTR _li$67106[esp+68] mov DWORD PTR ?CounterStart@@3_JA, eax mov DWORD PTR ?CounterStart@@3_JA+4, ecx $LN15@main: ; Line 147 mov eax, edi call ?stringRotation@@YAXPADH@Z ; stringRotation ; Line 149 lea edx, DWORD PTR _li$67111[esp+64] push edx call esi mov eax, DWORD PTR _li$67111[esp+64] sub eax, DWORD PTR ?CounterStart@@3_JA mov ecx, DWORD PTR _li$67111[esp+68] sbb ecx, DWORD PTR ?CounterStart@@3_JA+4 mov DWORD PTR tv180[esp+64], eax mov DWORD PTR tv180[esp+68], ecx fild QWORD PTR tv180[esp+64] ; Line 152 mov esi, DWORD PTR __imp__printf sub esp, 8 fdiv QWORD PTR ?PCFreq@@3NA ; PCFreq fst QWORD PTR _durationMS$[esp+72] fstp QWORD PTR [esp] push OFFSET ??_C@_0CF@MOIGNLJO@100?5Megabytes?5took?5?$CFf?5millisecon@ call esi ; Line 153 fld QWORD PTR __real@4059000000000000 fdiv QWORD PTR _durationMS$[esp+76] ; Line 154 add esp, 4 fmul QWORD PTR __real@408f400000000000 fstp QWORD PTR [esp] push OFFSET ??_C@_09IJINBEEB@MB?1s?5?$DN?5?$CFf?$AA@ call esi add esp, 12 ; 0000000cH ; Line 157 push edi call DWORD PTR __imp__free add esp, 4 ; Line 159 pop edi pop esi xor eax, eax pop ebx mov esp, ebp pop ebp ret 0 _main ENDP _TEXT ENDS END På min maskin (med VS2010 som kompilator) tok jonnys implementasjon ~60+ ms. Min implementasjon tar ~15+ ms. Det er ingen inline assembly i implementasjonen, kun 100% C(og litt ++). Rosinen i pølsa #1 er at "vanlig" output på min hardware (i7-860) gir: 100 Megabytes took 14.746701 milliseconds.MB/s = 6781.177580 Men rosinen i pølsa #2 er: Ved å legge til èn linje i kildekoden ("#pragma omp parallel for" inni funksjonen stringRotation), og sette kompilatorsettingen "/ompenmp", får man følgende output på samme hardware: 100 Megabytes took 10.166695 milliseconds.MB/s = 9836.038596 Ytelsen har altså økt med ~45% med 15 sekunders innsats fra den som setter kompilatorflaggene. Samme endringen via assembly ville tatt flerfoldige antall ganger tidsinnsats, mens i C(++) er det snakk om sekunder. Lenke til kommentar
Paull Skrevet 30. september 2012 Del Skrevet 30. september 2012 Og, som en added bonus: Ved å endre kompilator-settingene (Platform) fra Win32 til x64, og gitt samme umodifiserte kildekode (uten openmp-direktiver - dvs "rosinen i pølsa #1" fra forrige post) er resultatet følgende output: 100 Megabytes took 6.804551 milliseconds. MB/s = 14696.047182 Med andre ord: Endring av kompilatorflagg (som tar ~5 sekunder) gir en ~216% økning i ytelse. Dette uten å endre kildekode, kun kompilatorflagg. Lenke til kommentar
LonelyMan Skrevet 30. september 2012 Del Skrevet 30. september 2012 (endret) Ja jeg ser resultatet, men jeg er skeptisk til bruk av streng som roteres. Jeg fyller 100 MB i min buffer med random data og ciphrer hele bufferen, ikke bare en streng som går fra A til Z eller a-z. Dere må fylle 100 MB med random data fra 0-255, ikke bare bokstaver, i den virkelige verden så kommer ikke data ferdig pakket i et alfabet, der er det lumske tegn som må kopieres uten å ciphres. Rot13 ciphrer kun alfabetet til z/Z, alle andre tegn skal beholdes som de er og kopieres tilbake igjen i original form. 1. Fyll 100 MB med random data med en pseudo random algoritme, den skal inneholde tilfeldige bytes fra 0-255 2. ciphre den samme bufferen med data 3. Skriv tilbake til bufferen de ciprede dataene. L1 cache er 30 ganger raskere enn RAM. Jeg er nødt til å skrive om min rutine til å likne på jonny sin for at det skal bli samme greien da strengen hans er en kombinasjon fra a-z og A-Z hvilket er 52 mulige kombinasjoner hvor jeg bruker 256 kombinasjoner, rutinen må ha mulighet for å slå opp data som ikke kan roteres også, ellers vil den alltid få treff med en gang, bra at dere legger frem selve c++ koden også, blir lettere å ha oversikten. Paul, instruksjonene dine akkumulerer opp nok sykluser til å ta en tur til sola og tilbake og fremdeles ha tid til å grille en pølse, der er noe som er alvorlig feil i koden din så jeg foreslår at du poster kildekoden så jeg kan se på algoritmen. Tallene dine overgår dagens DDR3 kapasitet ang read og write kombinert. DDR3 1800 så kan du skrive 100 MB med data på absolutt minimum 15 millisekund. Men så skal du også forurense cachen og LESE fra ram i tillegg. Når du nå har 6 millisekund og bruker en i7-860 med ddr3 1333 og instruksjoner som iallefall 4-dobler syklusene så er det noe som ikke bare er alvorlig galt, men noe som er riv ruskende galt. he-he. Jeg kan fortelle deg hvor forklaringen ligger. Koden din benytter L1 cache istedet for RAM, hvilket betyr at du ikke utfører algoritmen som den skal gjøres. (programmeringsfeil) Etter litt analysering av outputen din Paul så ser jeg at du roterer bare 25% av bufferen, der er programmeringsfeilen din. Og det er ikke en feilanalysering fra min side, du roterer bare 25%. I tillegg må du endre algoritmen til å skrive 256 bytes i bufferen, ikke bare 52. Med så få kombinasjoner kan det bli lettere for hardwaren å deale med dataene da du bruker nesten 5 ganger ferre kombinasjoner med data enn jeg gjør. Men hovedproblemet ditt er at rutinen din bare roterer 25% av bufferen. Etter hva jeg kan se, om du roterer hele bufferen havner du på 24 millisekund for å rotere 52 kombinasjoner med data. Om du øker til 256 kombinasjoner slik som jeg gjør og som du også må gjøre om funksjonen din skal fungere i virkeligheten, så antar jeg du havner noe rundt mellom 40 og 100 millisekunder. Du ser det er ikke alltid det bare er å putte en switch i kompilatoren og tro at ting løser seg, programmering er mer komplekst enn som så og din kode ligger fremdeles milevis unna speed-wise. Jeg ser nå egentlig at hver gang en c++ programmerer mener å ha slått en asm kode så poster de koden inn og erklærer victory, men det som er felles hver gang er at ingen av disse forstår sin egen kode, jeg må alltid vise de hvor feilene ligger i deres egen kode. Kanskje dette er en svakhet med c++ programmerere at ingen av de forstår sin egen kode. Endret 30. september 2012 av LonelyMan Lenke til kommentar
LonelyMan Skrevet 30. september 2012 Del Skrevet 30. september 2012 Og, som en added bonus: Ved å endre kompilator-settingene (Platform) fra Win32 til x64, og gitt samme umodifiserte kildekode (uten openmp-direktiver - dvs "rosinen i pølsa #1" fra forrige post) er resultatet følgende output: 100 Megabytes took 6.804551 milliseconds. MB/s = 14696.047182 Med andre ord: Endring av kompilatorflagg (som tar ~5 sekunder) gir en ~216% økning i ytelse. Dette uten å endre kildekode, kun kompilatorflagg. Her er faktumet, helt nøyaktig og feilfritt gjengitt hva koden din gjør: 1. Den leser 25 millioner bytes, ikke 100 millioner bytes. 2. De 6,8 millisekundene blir 27,2 millisekunder om du leser 100 mill bytes. 3. Du skriver kun 25 millioner bytes tilbake til minnet, ikke 100 mill. 4. Om du akkumulerer opp 75% ekstra tid på skrive-aktivitet til ram så vil du få en økning i kjøretid på koden din på flerefoldige faktorer, opp mot 100 ms er ikke utenkelig. Deler du 14969 på 4 havner du ned på 3674 MB/s, og dette tar ikke med i kalkuleringen din at du må legge på 75% ekstra skrivetid til RAM i programmet ditt. Du bruker widechar i programmet ditt, dvs den skipper annenhver byte og praktisk sett opererer den bare med halve bufferen din. I TILLEGG, så kjører ikke loopen engang gjennom hele widechar bufferen det er det verste av alt. Om ikke du var en nybegynner i c++ så ville jeg sagt at dette er det verste form for jukseforsøk jeg har sett i mitt liv. Lenke til kommentar
jonny Skrevet 30. september 2012 Del Skrevet 30. september 2012 (endret) Jeg rettet opp funksjonen som genererer random data, slik: static void randomStrGen(unsigned char *str, int length) { srand(time(NULL)); int i; for (i = 0; i < length; i++) str[i] = rand() % 256; } Tiden for roteringen med 100 mill bytes tar nå 109-110 ms på min prosessor. Endret 30. september 2012 av jonny Lenke til kommentar
LonelyMan Skrevet 30. september 2012 Del Skrevet 30. september 2012 (endret) jonny, goodie,, Så må du fjerne widechars og bruke ansi.. Nå vet jeg ikke om du bruker widechar, men han andre brukte det, og han sa han kopierte din kode med noen "modereringer". Endret 30. september 2012 av LonelyMan 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å