Optimera C-kod för GCC
Optimera C-kod för GCC
Hejsan
Jag funderar lite över hur man bäst optimerar sin C-kod i GCC. Finns det någon guide eller liknande? Plattformen är en ARM7...
Är det någon skillnad i prestanda mellan:
var *= 2;
var = var<<1;
Är dubbelarrayer snabba?
for(a=0;...) {
for(b=0;...) {
var[a] = tjohej;
}}
eller bör jag optimera så att jag med en endimensionell array räknar upp den lite manuellt ?
for(a=0;...) {
tmp=a*8;
for(b=0;...) {
var[tmp++] = tjohej;
}}
Är det snabbast att använda unsigned int eller vanlig int ?
Slöar det ner att använda char istället för int på en 32-bitarsplattform, är char kanske snabbare ändå, eller är de rentav lika snabba ?
Sen har jag fått för mig att int är 32-bitarstal på GCC för ARM, vad är då long, 64 bitar ?
/Johan
Jag funderar lite över hur man bäst optimerar sin C-kod i GCC. Finns det någon guide eller liknande? Plattformen är en ARM7...
Är det någon skillnad i prestanda mellan:
var *= 2;
var = var<<1;
Är dubbelarrayer snabba?
for(a=0;...) {
for(b=0;...) {
var[a] = tjohej;
}}
eller bör jag optimera så att jag med en endimensionell array räknar upp den lite manuellt ?
for(a=0;...) {
tmp=a*8;
for(b=0;...) {
var[tmp++] = tjohej;
}}
Är det snabbast att använda unsigned int eller vanlig int ?
Slöar det ner att använda char istället för int på en 32-bitarsplattform, är char kanske snabbare ändå, eller är de rentav lika snabba ?
Sen har jag fått för mig att int är 32-bitarstal på GCC för ARM, vad är då long, 64 bitar ?
/Johan
Oftast anpassar kompilern själv optimeringen men såklart kan man lösa vissa funktioner på ett snabbare och/eller enklare sätt.
Jag vet att kompilern just med 2D variabler gör om den till 1D variabler som den indexerar i och jag tror att den gör det indexeringen snabbare själv.
Vad angår variablernas storlek är det inte så mycket man kan göra förutom att tänka till iblant. I "de gamla dagar" var en 'int' 16 bit lång och nu, då PC'n har kommit till 32 bit/64 bit i standart-ord är en 'int' just den storlek men bara 15 bit + signbit används. Likaså är det med de andra variabelstorleker: det finns en standart som följs men iblant tas det i, på PC (Borland C Builder) är en 'int' 32 bit stor, ganska enkelt för att det är den 'snabbaste' storlek.
Som jag har förstådd det är 'int' den "mest optimerade" vcariabel att använda från och med 16-bits system och uppåt men det beror mycket på arkitekturen osv. PÅ den Fujitsu jag använder flitigt betyder 'byte'-värden ofta att minnet läsas, de höga 8 bit sätts till noll och sen utförs uträkningen, vid att välja en 'int' till den variabel kan jag alltså spara 1 operation.
Den verkliga prestandaskillnad kan hittas vid att minimera loopar: det är långt mer lönt att ta bort 1 instruktion i en loop som utförs 10000 gånger per cyklus än en som utförs 2 gånger per cyklus.
Som så ofta förut: kan man sin maskinkod kan man optimera långt bättre.
Jag vet att kompilern just med 2D variabler gör om den till 1D variabler som den indexerar i och jag tror att den gör det indexeringen snabbare själv.
Vad angår variablernas storlek är det inte så mycket man kan göra förutom att tänka till iblant. I "de gamla dagar" var en 'int' 16 bit lång och nu, då PC'n har kommit till 32 bit/64 bit i standart-ord är en 'int' just den storlek men bara 15 bit + signbit används. Likaså är det med de andra variabelstorleker: det finns en standart som följs men iblant tas det i, på PC (Borland C Builder) är en 'int' 32 bit stor, ganska enkelt för att det är den 'snabbaste' storlek.
Som jag har förstådd det är 'int' den "mest optimerade" vcariabel att använda från och med 16-bits system och uppåt men det beror mycket på arkitekturen osv. PÅ den Fujitsu jag använder flitigt betyder 'byte'-värden ofta att minnet läsas, de höga 8 bit sätts till noll och sen utförs uträkningen, vid att välja en 'int' till den variabel kan jag alltså spara 1 operation.
Den verkliga prestandaskillnad kan hittas vid att minimera loopar: det är långt mer lönt att ta bort 1 instruktion i en loop som utförs 10000 gånger per cyklus än en som utförs 2 gånger per cyklus.
Kod: Markera allt
A = uppmätt tid (t.ex)
B = Referens (t.ex.)
#define D_ARRAY_SIZE 10000
int X;
int Y[D_ARRAY_SIZE];
for(X = 0;X < D_ARRAY_SIZE;X++)
{
Y[X] += (A - B);
}
går en "hel" del långsammare än:
A = uppmätt tid (t.ex)
B = Referens (t.ex.)
#define D_ARRAY_SIZE 10000
int X, Diff;
int Y[D_ARRAY_SIZE];
Diff = A - B;
for(X = 0;X < D_ARRAY_SIZE;X++)
{
Y[X] += Diff;
}
Den appnoten jag tänkte på var visst för AVR...
AVR035: Efficient C Coding for AVR
http://www.atmel.com/dyn/resources/prod ... oc1497.pdf
Men det går nog att lära sig något även från den.
AVR035: Efficient C Coding for AVR
http://www.atmel.com/dyn/resources/prod ... oc1497.pdf
Men det går nog att lära sig något även från den.
Huruvida 1d arrayer är snabbare än 2d arrayer beror på hur ramminnet accessas. I en "pagead" minnesmiljö är det alltid snabbare med 1d eftersom man får färst (alltså som i färre) page-swaps. Och säkerligen som Icecap säger så görs de juh om till 1d arrayer av kompilatorn, men det man ska vara noga med är då hur man indexerar elementet rad/col eller col/rad (kommer aldrig ihåg hur den lagrar sina arrayer, det kanske tom är olika beroende på kompilator).
Sen tror jag faktiskt att konstanta uttryck inuti loopar omptimeras bort ganska bra...men jag är inte helt säker...
peace
Sen tror jag faktiskt att konstanta uttryck inuti loopar omptimeras bort ganska bra...men jag är inte helt säker...
peace
Hur bra optimeringen är beror till stor del på hur duktig kompilerkonstruktören har varit.
Jag hade stor framgång då jag skulle kopiera en minnesblock, med memcpy() gick det snabbt men inte snabbt nog så jag gjorde en MemCpyW() som flyttade integers, samma data annrs som memcpy(). Det gjorde susen, jag fick kraftigt förbättret prestanda på hela lösningen, enkom för att denna rutin var så mycket snabbare och det var ju trevlig.
Exemplet jag gav förut var bara en klen beskrivning men faktum kvarstår: man kan spara 10000 instruktioner om man kan banta bort 1 i en cykel som upprepas 10000 gånger, att pilla bort 1 instruktion i något som utförs lite då och då är nära nog bortkastat tid.
Man kan även tänka på funktionen i helhet: ska man göra en LED-snurrar t.ex. kan man göra utläsningen så att man gör skickar ut styrningen av LED'en och därefter förbereder nästa dataklump så den ligger redo till nästa gång. Detta kan ge mycket bra realtidsprestanda men kräver såklart att datan kan förutsägas så att säga.
Jag hade stor framgång då jag skulle kopiera en minnesblock, med memcpy() gick det snabbt men inte snabbt nog så jag gjorde en MemCpyW() som flyttade integers, samma data annrs som memcpy(). Det gjorde susen, jag fick kraftigt förbättret prestanda på hela lösningen, enkom för att denna rutin var så mycket snabbare och det var ju trevlig.
Exemplet jag gav förut var bara en klen beskrivning men faktum kvarstår: man kan spara 10000 instruktioner om man kan banta bort 1 i en cykel som upprepas 10000 gånger, att pilla bort 1 instruktion i något som utförs lite då och då är nära nog bortkastat tid.
Man kan även tänka på funktionen i helhet: ska man göra en LED-snurrar t.ex. kan man göra utläsningen så att man gör skickar ut styrningen av LED'en och därefter förbereder nästa dataklump så den ligger redo till nästa gång. Detta kan ge mycket bra realtidsprestanda men kräver såklart att datan kan förutsägas så att säga.
När det gäller GCC behöver man nog inte bekymra sig speciellt mycket om hur man skriver koden, enligt min erfarenhet är den väldigt bra på att optimera. Flytta ut konstanta beräkningar ur loopar gör den nog garanterat, och den tar även bort "meningslösa" loopar (som man skrivit för att göra en kort delay eller testa minnesbandbredden) helt och hållet!
Stor skillnad jämfört med t.ex. LCC som är det enda jag har till en processor. Den kan skapa riktigt hemsk kod ibland, med helt överflödiga load/store och move mellan register i onödan osv.
När det gäller storlek på typer så är det praktiskt taget alltid såhär:
char 8bit
short 16bit
long 32bit
long long 64bit
int = 32bit på 32bits processor, 16bit på 16bits processor, och på 8bits system kan faktiskt int vara 8bit också (fast det går emot standarden).
long long stöds inte av alla (men GCC gör det).

Stor skillnad jämfört med t.ex. LCC som är det enda jag har till en processor. Den kan skapa riktigt hemsk kod ibland, med helt överflödiga load/store och move mellan register i onödan osv.
När det gäller storlek på typer så är det praktiskt taget alltid såhär:
char 8bit
short 16bit
long 32bit
long long 64bit
int = 32bit på 32bits processor, 16bit på 16bits processor, och på 8bits system kan faktiskt int vara 8bit också (fast det går emot standarden).
long long stöds inte av alla (men GCC gör det).