Avevo già parlato di efficienza del codice (in particolare con Python, linguaggio notoriamente non efficiente, anche un interessante esempio migliorativo Python + C assieme con la libreria CFFI), vediamo ora qualcosa di interessante: come usare le macro in C, perché risultano molto utili per l'efficienza del codice.
Nel linguaggio C/C++ le macro sono istruzioni fornite al preprocessore, ad esempio qualcosa di semplice come #define N 100
(l'analogo di una scrittura del tipo int N=100;
) oppure anche qualcosa di più interessante del tipo #define f(a,b) a*b+1
, l'equivalente di una funzione matematica, un'espressione del tipo a*b+1 in questo caso. Sfruttando l'operatore ternario è anche possibile fare uso di condizioni, ad esempio #define f(x) (x%2==0?1:0)
ritorna 1 se il numero è pari, 0 se il numero è dispari. In generale, le macro sono particolarmente adatte ad espressioni semplici (no cicli, ricorsioni) e ripetute molte volte, in modo tale da sfruttarne l'efficienza. Risultano infatti più performanti in termini di risorse, tempo di esecuzione. Poi in realtà ci sono anche altri aspetti che qui non approfondiamo, potenzialmente legati alla sicurezza, la leggibilità e manutenibilità, debug del codice.
Macro in C: un caso pratico, confronto performance
Vediamo ora un caso pratico, risoluzione di integrazione numerica di una semi-gaussiana, semplicemente per ripetere il codice N volte (in particolare N=100.000.000), in tre casi differenti:
- codice standard: senza macro, comunque ottimizzato (ad esempio: funzioni specifiche di <math.h>, allocazione delle variabili nel registro della CPU, vedi anche Le velocità dei diversi componenti del PC)
- MACRO 1: facciamo uso di due funzioni definite tramite macro, ovvero
#define ADD(x,y) x+y
, #define MULT(x,y) x*y
- MACRO 2: oltre a quanto fatto con MACRO 1, aggiungiamo anche
#define f(x) exp(-0.5*x*x)
Vediamo ora il codice completo nei tre casi:
- codice standard:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
int main(){
register unsigned int N=100000000;
register double ris=0;
register float a=0;
register float b=20;
register double h=(b-a)/N;
register unsigned int i;
clock_t start = clock();
for(i=0;i<N;i++){
ris+=exp(-0.5*(a+i*h)*(a+i*h));
}
ris*=h*sqrt(1/(2*M_PI));
clock_t end = clock();
printf("%f\n",(float)(end-start)/CLOCKS_PER_SEC);
return 0;
}
- MACRO 1:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#define ADD(x,y) x+=y
#define MULT(x,y) x*=y
int main(){
register unsigned int N=100000000;
register double ris=0;
register float a=0;
register float b=20;
register double h=(b-a)/N;
register unsigned int i;
clock_t start = clock();
for(i=0;i<N;i++){
ADD(ris,exp(MULT(-0.5*(a+i*h),(a+i*h))));
}
MULT(ris,h*sqrt(1/(2*M_PI)));
clock_t end = clock();
printf("%f\n",(float)(end-start)/CLOCKS_PER_SEC);
return 0;
}
- MACRO 2:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#define ADD(x,y) x+=y
#define MULT(x,y) x*=y
#define f(x) exp(-0.5*x*x)
int main(){
register unsigned int N=100000000;
register double ris=0;
register float a=0;
register float b=20;
register double h=(b-a)/N;
register unsigned int i;
clock_t start = clock();
for(i=0;i<N;i++){
ADD(ris,f(a+i*h));
}
MULT(ris,h*sqrt(1/(2*M_PI)));
clock_t end = clock();
printf("%f\n",(float)(end-start)/CLOCKS_PER_SEC);
return 0;
}
Ora veniamo ai risultati!
- codice standard: tempo di esecuzione del codice 0,565 secondi
- MACRO 1: tempo di esecuzione del codice 0,506 secondi
- MACRO 2: tempo di esecuzione del codice 0,486 secondi
Vale a dire che, tramite questo semplice accorgimento (NB il "codice standard" era di per sé già molto ottimizzato!!!) MACRO 2 risulta essere il 14% più efficiente rispetto al codice standard e MACRO 1 il 10,5% più efficiente. Sono valori medi, riferiti a questo esempio specifico, comunque serve per rendere l'idea.
Conoscevate l'uso delle macro nella programmazione C/C++? Avete visto quanto riescono ad essere potenti?
Qui parliamo di secondi e frazioni di secondo, ma il 14% del tempo di esecuzione di alcune ore (ad esempio per un caso molto complesso) non è certo un fattore trascurabile, in termini di tempo e risorse! 🙂