Gå til innhold

Anbefalte innlegg

....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 av LonelyMan
Lenke til kommentar
Videoannonse
Annonse
Gjest Slettet+9871234

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. :confused:

 

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

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 av LonelyMan
Lenke til kommentar
Gjest Slettet+9871234

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 av Slettet+9871234
Lenke til kommentar

....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 av GeirGrusom
Lenke til kommentar

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 av LonelyMan
Lenke til kommentar
Gjest Slettet+9871234

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 av Slettet+9871234
Lenke til kommentar
Gjest Slettet+9871234

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

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 av jonny
Lenke til kommentar

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 av LonelyMan
Lenke til kommentar

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

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

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

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. :ph34r:

Endret av LonelyMan
Lenke til kommentar

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. :yes:

Lenke til kommentar

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 av jonny
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å
  • Hvem er aktive   0 medlemmer

    • Ingen innloggede medlemmer aktive
×
×
  • Opprett ny...