Sunteți pe pagina 1din 33

Obiectiv capitol

Dezvoltarea aplicatiilor C (robuste) pentru sisteme incorporate.

Cuprins:
Tehnici de programare robusta
Optimizarea programelor C

Bibliografie
Joseph Lemieux, Programming in the OSEK/VDX Environment, Elsevier, 2001
Richard Zurawski, Embedded Systems Handbook, CRC Press, 2006
V. REALIZAREA APLICAŢIILOR SOFT
PENTRU SISTEME INCORPORATE
V. 1. Implementarea unor programe C robuste

Definitii

Anomalia unui program:


orice functionare (neasteptata) nedorita a unui program,
inclusiv produsa de conditii externe nefavorabile (defectiune hardware, date de
intrare eronate)
sau de conditii interne (depasire de stiva nedepistata).
Robustete:
proprietatea unui program de a asigura functionalitatile stiute in orice conditii de
functionare
(incarcare mare a procesorului, frecventa ridicata pe liniile de transmisie de date,
frecventa ridicata de aparitie a intreruperilor sincrone si asincrone)
>>> Un program robust este proiectat luând in consideratie ce anomalii pot apărea

Eficienta: o masura a gradului in care resursele de calcul sunt utilizate: timp executie,
consum de memorie – pentru cod si date (ROM, FLASH/ RAM, EEPROM).
Motivatia pentru o programare robusta:
- costuri!!!!
- siguranta!!

Observatii:
- multe anomalii pot fi evitate aplicand tehnici de programare deja stiute
- foloseste ANSI C pentru portabilitate (extensiile de limbaj conduc de regula la cod
neportabil)
Standard Motor Industry Reliability Association (MISRA)
– aprilie 1998
www.misra.org.uk

= reguli de programare in C pentru aplicatiile din industria automobilelor

>> cele mai recomandate bune practici

>> analiza statica Static Analysis: testare inainte de compilare

!!! de preferat ca aceste reguli sa fie respectate pe masura ce codul este scris (nu
doar sa fie refacut codul la sfarsit in baza MIRSA)

Carbody C Standard - C Coding Guidelines


Run LINT + Code_check inainte de compilare!
Tehnici de programare robusta

- este preferabil sa aplici aceste reguli in timp ce scrii codul (ar putea aparea erori la
modificari viitoare)

- verifica specificatiile (50% din erori sunt generate de folosirea gresita a specificatiilor –
presupuneri false, etc )

- inainte de a scrie codul sa stii cum/ce trebuie sa faci (pseudocod…)

- testeaza codul des, cam la fiecare 15 linii de cod scrise (in medie va aparea o eroare)

- foloseste un stil “defensiv”: verifica stările de eroare returnate de funcţii, verifica faptul ca ai
inclus default la switch, nu fa presupuneri nefondate (stari implicite, valori biti, intarzieri,
etc)
- evita codul eliptic, greu de inteles >> cod cat mai simplu de inteles

>> portabilitate, lizibilitate && intretinere/dezvoltare simpla

>> codul eliptic este adesea o sursa de erori

- foloseste identificatori sugestivi

>> lizibilitate – pentru oricine && depanare/extindere mai simpla

>> sugestii

functii: predicat+subiect
ex: GetID(…), CalculateResponse(…), TreatInputRequests(…)
nume usor de inteles, reguli consistente

- minimizeaza numarul de instructiuni if (atentie sa nu pierzi anumite cazuri)


- nu folosi conditii care sa contina asignari, deplasari pe biti etc

>> lizibiliate mai buna

>> exceptie: initializari pentru instructiunile de buclare

- nu folosi in exces secvente de cod scrise in limbaj de asamblare - MISRA Rule 3


(recomandare)

>> cod neportabil && compilatorul nu poate asigura optimizari

>> recomandare: izoleaza secventa intr-un macro, subrutina, modul

- declara variabilele pentru cel mai redus domeniu de valabilitate necesar (functie, fisier)
MISRA Rule 22 (recomandare) >> lizibilitate && reduce acces nepermis/conflict

>> observatie: mai simplu de asigurat la C++ prin incapsulare


- foloseste tipurile de date Carbody BSW in loc de tipurile C char, int, short, long, etc.

MISRA Rule 13 (recomandare)

>> control mai bun al spatiului de memorie ocupat

Tip Definire Dimens Domeniu valori


T_UBYTE unsigned byte 1 0..255
T_UWORD unsigned word 2 0..65535
T_ULONG unsig double word 4 0..FFFFFFFFh
T_SBYTE signed byte 1 -128..127
T_SWORD signed word 2 -32768..32767
T_SLONG signed double word 4 80000000h ...7FFFFFFFh
T_FLAG8 bit 1 0..1
T_FLAG16 bit 2 0..1

typedef T_SWORD int;


- constantele trebuie mapate catre un nume simbolic care este definit o singura data sau
incluse in niste tabele/ structuri adecvat intializate

>> valoarea constantei va putea fi usor modificata in tot codul

>> exceptie: 0

const <tip> <nume> = <valoare>;


const unsigned int t = 109U;
const long e = 1L;
const char c = 'c' ;

- variabilele partajate trebuie declarate volatile

Carbody C Coding Rule 132

- nu folosi aceeasi variabila globala pentru mai multe scopuri


>> lizibilitate && evitarea acces eronat
- gestionarea cu atentie a variabilelor globale prin intermediul ISR

>> asigura excludere mutuala pentru fiecare (atentie, intreruperile asincrone pot aparea
oricand)

>> tehnici:

- stabileste cine citeste si cine poate modifica variabila

- pentru excludere mutuala dezactiveaza temporar intreruperile la scrierea /citirea


variabilei

- la o executie a unui task – preferabil un singur acces la variabila

- foloseste copii locale ale variabilei


- foloseste prototipuri de functii int foo (int);

MISRA Rule 71

>> rezervare de spatiu in stiva

(altfel spatiul rezervat poate fi mai mare)

- actualizeaza starea/iesirea porturilor (nu presupune ca odata setata, starea este stabila)

>> pot parea evenimente care sa determine modificarea starii

>> aceste actualizari afecteaza eficienta

- codul sa asigure ca dupa un reset sistemul ajunge intr-o stare stabila

>> resetarea poate aparea oricand


V. 2. Optimizarea programelor C

o Unele sugestii NU sunt valabile pentru toate compilatoarele C

o Rezultatul este dependent de maşină

PAS1: Setează pe ON posibilitatea compilatorului de a face optimizări

PAS 2,..etc..: Începe cu optimizarea secvenţelor de instrucţiuni care

o sunt reluate de multe ori


o solicită timp mare de execuţie «bottleneck»

posibilitate de depistare: rulări ale programului pe date de test

exemple frecvente: acces la fisiere, sortare, căutare, copiere a unor blocuri


mari de date, startare noi procese
V.2.1 Optimizare algoritmi

- Trebuie sa foloseşti cei mai buni algoritmi- atenţie la ordinul de complexitate


Căutare: secvenţială => binară, look-up table
Ordonare: quicksort (qsort)

- Foloseşte cele mai potrivite tipuri de date:


Exemple:
- liste înlănţuite dacă trebuie să introduci sau să stergi elemente pe pozitii diferite, -
tablouri dacă nu inserezi /stergi elemente

V.2.2. Folosire secvenţe de cod în asamblare


– variantă neportabilă, greoaie, dar uneori mai rapidă

V.2.3 Optimizare cod C unele reguli au fost rezolvate implicit in unele compilatoare/PC
a) Declaraţii, tipuri de variabile

- Aritmetica pe întregi este mai rapidă decât aritmetica în virgulă mobilă. Aritmetica pe float
este mai rapidă ca pe double
o nu folosi float dacă o variabilă ia doar valori intregi
o poti folosi rotunjiri
 Ex: dacă doreşti 3 zecimale exacte, înmulţeşte toate valorile numerice cu 1000,
foloseşte calcul pe întregi şi la final rescalează prin împărţirea la 1000

!!! In prezenta FPU, calculele in float sunt rapid executate si poti folosi FLOAT!!!

- Dacă o variabila este număr natural foloseste unsigned int (astfel unele procesoare
lucrează mai rapid) şi chiar register unsigned int pentru variabilele des folosite
(acces mai rapid – dacă există registre libere)
- Foloseşte variabile cu lungime 2, 4 octeti (multiplu de cuvinte): int, float
Nu folosi des char, short, double, câmpuri de biţi etc. mai ales la variabilele
locale. La char compilatorul trebuie să reducă dimensiunea rezultatelor de tip int

o Pentru structuri, pentru o mai bună aliniere a datelor în memorie:


 grupează câmpurile astfel încât să obţii aliniere la cuvânt
 eventual introdu un câmp fals (de un octet), dacă dimensiunea totală nu este
multiplu de cuvânt
struct foo {

struct foo {
double b;
float a;
double b; double d;
float c; long f;
double d; long h;
short e; float a;
long f; float c;
short g; int j;
long h; int l;
char i; short e;
int j; short g;
char k; char i;
int l; }; char k; };
- Foloseste, de preferinta, variabile de tip numeric în locul caracterelor/sirurilor de
caractere (dacă această codificare este posibilă)

switch(c){ switch(c){
case 'A':{do something; case 0:{do something;
break;} break;}
case 'H':{do something; case 1:{do something;
break;} break;}
case 'Z'{do something; case 2:{do something;
break;} break;}

} }
- Accesul la tablourile unidimensionale este mai rapid ca accesul la tablouri
multidimensionale

o Accesaţi tablourile bidimensionale pe linii (optimizarea lucrului cu memoria cache)


– for-ul extern cu indexul pe linie// elementele de pe o linie ocupa locatii succesive
float array[20][100];
int i, j;
for (i = 0; i < 20; j++)
for (j = 0; j < 100; i++)
array[i][j] = 0.0;

o Dimensiune recomandată pentru tablourile bidimensionale: putere a lui 2 (în octeţi)

o Dacă o functie ce accesează mult memoria foloseste două tablouri diferite – acestea
pot fi concatenate într-un singur tablou mai mare

o Dacă dintr-un tabel numai o parte este frecvent folosită, aceasta poate fi separată
într-un tablou distinct (pentru a aduce tot tabloul util in memoria cache)
- Accesul la o structura de tablouri este mai rapid ca accesul la un tablou de structuri (utilizare
mai buna a memoriei cache de către compilator)

- In tablourile de structuri este bine ca o structură să aibă lungimea egală cu o putere a lui 2
(în octeţi), pentru a asigura o indexare optimă în tablou

- Atentie: alocările dinamice pot conduce la fragmentarea datelor

- Nu folosi variabile globale, dacă nu e necesar (variabilele globale nu sunt alocate unor
regiştri – cel mai rapid acces nu poate fi asigurat);

- O variabilă locală să aibă cel mai mic domeniu de valabilitate necesar (fără declaraţii în
exteriorul funcţiilor care o folosesc)
b) Instrucţiuni de test

- nu folosi teste inutile:

if (x != 0) x = 0; x = 0;

- în scrierea operaţiilor logice atenţie la cazul cel mai frecvent:


if(a>10 && b=4) dacă cel mai adesea a>10 este fals

- înlocuieste instructiuni if else if …. (preferabile if-uri fara else) cu switch


(eventual cu instrucţiuni switch imbricate dacă sunt multe situaţii case) sau cu accesari in
tablouri (mai rapid)
switch ( cod ) {
case 0 :letter = 'W';
break;
case 1 :letter = 'S';
break;
case 2 :letter = 'U';
if ( cod == 0 ) break;
letter = 'W';
else if ( cod == 1 ) }//situatia mai frecventă: cod=0
letter = 'S';
else
letter = 'U';
//situatia mai frecventă: cod=0 static char *classes="WSU";
letter = classes[cod];
Instrucţiuni repetitive

- Nu folosi mai multe bucle decât este necesar:


for(i=0; i<100; i++){ for(i=0; i<100; i++){
stuff();} stuff();
for(i=0; i<100; i++){ morestuff();}
morestuff();}

for (i = 0; i < MAX; i++) for (i = 0; i < MAX; i++) {


for (j = 0; j < MAX; j++) for (j = 0; j < MAX; j++)
a[i][j] = 0.0; a[i][j] = 0.0;
for (i = 0; i < MAX; i) a[i][i] = 1.0; }
a[i][i] = 1.0;
- Nu folosi instructiuni repetitive, dacă numarul de reluări este redus şi constant

for(i=0; i<3; i++){ something(0);


something(i);} something(1);
something(2);

- Poţi reduce numărul de reluari ale unei instrucţiuni repetitive la care numărul mare de reluări
nu este cunoscut a priori
#include<stdio.h>
#define BLOCKSIZE 8
void main(void){
int i = 0;
int limit = 33;
int blocklimit;
blocklimit = (limit/BLOCKSIZE)*BLOCKSIZE;
while( i < blocklimit ){
printf("process(%d)\n", i); printf("process(%d)\n", i+1);
printf("process(%d)\n", i+2); printf("process(%d)\n", i+3);
printf("process(%d)\n", i+4); printf("process(%d)\n", i+5);
printf("process(%d)\n", i+6); printf("process(%d)\n", i+7); i += 8;}
if( i < limit ){ switch( limit - i ){
case 7 : printf("process(%d)\n", i); i++;
case 6 : printf("process(%d)\n", i); i++;
case 5 : printf("process(%d)\n", i); i++;
case 4 : printf("process(%d)\n", i); i++;
case 3 : printf("process(%d)\n", i); i++;
case 2 : printf("process(%d)\n", i); i++;
case 1 : printf("process(%d)\n", i);}}}
- Foloseşte instrucţiuni nerepetitive echivalente, dacă este posibil

- Foloseste for cu decrementare!!!! (mai rapid)

for(i=0; i<30; i++){ for(i=30; i!=0; i--){


something(i);} something(i);}

- Evită folosirea ++ sau – - la condiţiile de test (ex: while(n- - ) {}) – sunt greu de
optimizat

- Foloseşte break sau alte variante pentru a ieşi din bucle dacă anumite condiţii sunt
îndeplinite (dar atentie, programele structurate pot fi in general mai rapide)
found = FALSE;
found = FALSE; for(i=0; i<10000; i++)
for(i=0;i<10000;i++) if( list[i] == -99 ){
if( list[i] == -99 ) found = TRUE;
found = TRUE;} break; }
if( found ) if( found )
printf("am gasit -99\n"); printf("am gasit -99\n");
//-99 gasit pentru i= 23
c) Funcţii

Argumentele funcţiilor
- Foloseşte maxim k argumente, k = nr reg generali (pasare parametri folosind registrii - mai
rapid)
o Grupează parametrii într-o structura + pasează pointerul la structură.
o Evită funcţiile cu număr variabil de parametri (pasarea parametrilor se face numai
prin stivă)
o Foloseşte puţine argumente long, double

- Nu pasa parametri intrare de tip pointeri pentru functii care nu altereaza continutul datelor
(compilatorul poate face anumite optimizări, daca nu este fortat ca la fiecare apel sa
recitească data respectiva din memorie)
void func1( int *data ) void func1( int *data )
{ {
int i; int i;
for(i=0; i<10; i++){ int localdata;
somefunc2( *data, i);} localdata = *data;
} for(i=0; i<10; i++){
somefunc2( localdata, i);}
//somefucn2 nu altereaza }
//info pointata de data
- Pasează structurile NUMAI prin referinţe/pointeri. Pasarea structurilor prin valoare
presupune copierea acestora în stivă (structurile pot fi de mari dimensiuni!!!!!!).
o Dacă funcţia nu schimbă conţinutul structurii, argumentul este bine să fie de tip
pointer la o structura constantă (astfel, compilatorul va proteja conţinutul structurii
de modificări nedorite).

void print_data (const bigstruct *data_pointer){


...printf contents of structure...}

Atenţie: Sortare un tablou de structuri –> sortare tablou de pointeri la structuri

- !!Nu copia blocuri de date, foloseşte pointer la ele

- Funcţiile să nu returneze valori care nu sunt utilizate


Alte sugestii

- Nu folosi excesiv de multe funcţii (deşi de obicei timpul necesar pentru a le preda controlul
<< timpul necesar pentru execuţia funcţiei)

- Cele mai des apelate funcţii să fie de tip frunză („leaf”) – să nu apeleze alte funcţii – pentru
acestea compilatorul poate face anumite optimizări

- Nu folosi în corpul unei funcţii multe variabile (permiţi astfel manipularea lor prin regiştri)

- Foloseşte şi macrouri #define….. (atenţie, astfel lungimea codului creşte şi poti reduce
viteza de execuţie prin depăşirea memoriei cash!!!)

int foo(a, b){


a = a - b;
b++;
a = a * b;
return a;
#define foo(a, b) (((a)-(b)) * ((b)+1))
}
// parantezele permit utilizarea lui foo in expresii

- Evită funcţiile recursive


- Nu separa funcţiile care au o anumită legatura (una o apelează pe alta) în fişiere diferite

- Declară functiile de tip static dacă e posibil (nu sunt folosite in alte fisiere - > module)

- Dacă este posibil, foloseste din biblioteci funcţiile care au timpi de execuţie mai mici:
o puts() este mai rapidă decât printf(), dar şi mai puţin flexibilă
o funcţiile care permit citirea binara, neformatată sunt mai rapide decât cele care
asigură citire formatată (de exemplu citire din fisiere)

- Evită apelul funcţiilor în interiorul unor bucle:

void func(int w,d) { void func(w) {


lots of stuff } for(i=0 ; i<100 ; i++) {
lots of stuff }
……… }
for(i=0 ; i<100 ; i++) {
func(t,i);} .............
func(t);
……………

- Acces dispozitive IO – preferabil în mod bufferat


Calcule matematice/logice

- adunarea este mai rapidă ca înmulţirea:


o foloseşte val + val + val în loc de val * 3

for (i = 0; i < MAX; i++) { for(i = h = 0; i < MAX; i++){


h = 14 * i; printf("%d", h);
printf("%d", h);} h += 14; }

- înmulţirea în virgulă mobilă este mai rapidă ca împărţirea


o foloseşte val * 0.5 în loc de val/2.0
o compară a>b*c în loc de a /b>c - pentru b pozitiv
o foloseşte val * (1.0/3.0) în loc de val/3.0 – constanta 1.0/3.0 poate fi
calculată la compilare
- forţează prin scalări adecvate ca la întregi împărţitorul să fie putere a lui 2 (în acest caz
compilatorul poate aplica deplasări pe biţi)

- operaţiile logice pe biţi sunt mai rapide decât modulo


o x%8 → x&7

- înmulţirea este mai rapidă decât ridicarea la putere


o pow(x,2) → x*x

- uneori poti înlocui înmultirea cu o deplasare si o adunare


o x*33 → x<<5+x

- evită folosirea sqrt() în bucle (timp execuţie mare) (cu FPU e rapid)

- evită folosirea exp, sin, log - timp execuţie mare (implementate folosind
descompunerea în serii) - (cu FPU e rapid)

- fă “manual” toate simplificările necesare (ex 3*x/3 să devină x)


- foloseşte “notaţii intermediare”:

total = struct animals *temp=a->b->c[4];


a->b->c[4]->aardvark + total =
a->b->c[4]->baboon + temp->aardvark +
a->b->c[4]->cheetah + temp->baboon +
temp->cheetah +
a->b->c[4]->dog;
temp->dog;

- rescrierea unor instrucţiuni de asignare poate permite optimizarea execuţiei de către


compilator:

float a, b, c, d, f, g; float a, b, c, d, f, g;
a = b / c * d; a = b / c * d;
f = b * g / c; f = b / c * g;
- dacă anumite calcule sunt reluate de multe ori (exemplu sin0, cos35), este bine să efectuati
aceste calcule o singură dată, să treceţi rezultatele în nişte tabele şi să le folosiţi ulterior (ex
tabele cu valori ale funcţiilor trigonometrice, log, exp, factorial etc)

- foloseşte expresii simple şi scurte pentru a permite manipularea doar prin registri, fără
salvări temporare în memorie.
Sugestii. Recomandari de imbunatatire a cursului.

S-ar putea să vă placă și