Nei titoli e nei testi troverete qualche rimando cinematografico (ebbene si, sono un cinefilo). Se non vi interessano fate finta di non vederli, già che non sono fondamentali per la comprensione dei post...

Di questo blog ho mandato avanti, fino a Settembre 2018, anche una versione in Spagnolo. Potete trovarla su El arte de la programación en C. Buona lettura.

mercoledì 28 novembre 2012

C'era una volta l'Ottimizzazione
come ottimizzare il codice C

To be, or not to be, that is the question: Whether 'tis Nobler in the mind to suffer... oops, forse ho sbagliato blog. Non é questo il blog di letteratura inglese? È un blog di programmazione C? Va beh, dato che ci sono, basta modificare un po' la domanda: Ottimizzare, o non ottimizzare, questo è il dilemma...

La domanda in realtà sarebbe: "quando scrivo codice devo farlo in maniera naturale, o meglio, istintiva (beh, sempre se si possiede l'istinto del programmatore...) o devo seguire linee un po' astruse ed ermetiche per rendere il programma più efficiente ?" E, ovviamente, non sto parlando di algoritmi: in questo caso è evidente che un buon algoritmo è preferibile a uno cattivo. Sto parlando a livello di istruzioni e gruppi di istruzioni.

Facciamo una premessa: siamo nel 2012 (quasi 2013). E di conseguenza dobbiamo comportarci. Anticamente i computer erano (rispetto a ora) lenti e con poche risorse, e i compilatori erano molto più semplici di quelli odierni. E, a quei tempi, usando la parola magica register era possibile velocizzare un loop e, se si riusciva a scrivere qualche linea di codice in meno (vedi nota esplicativa), si ottenevano programmi molto più performanti. Ma ora? Ha ancora senso scrivere codice così? Il compilatore ha veramente bisogno del nostro aiuto ?

nota esplicativa  
(...a 6 anni di distanza dalla pubblicazione ho scoperto, grazie alla segnalazione del mio ottimo collega Andrea Simone Costa, che l'espressione "...qualche linea di codice in meno..." si può facilmente fraintendere. Beh, come tutti ben sanno, l'efficienza del codice non è inversamente proporzionale al numero di linee scritte (e questo si può ben vedere sia negli esempi successivi di questo post sia in un post più recente che vi invito a leggere) ma dipende da ben altri fattori. Il senso della sfortunata espressione "...qualche linea di codice in meno..." è da intendere come: "...scrivere un codice ridotto all'osso omettendo qualsiasi operazione (apparentemente) superflua...", il che portava, nella migliore delle ipotesi, a scrivere in assembly le parti critiche e/o ad usare espressioni "criptiche" come quelle che vedremo tra poco (che miglioravano l'efficienza rendendo illeggibile il codice). E nella peggiore delle ipotesi... vabbe', potete immaginarvi cosa si poteva arrivare a fare per ridurre (in tutti i sensi) un programma che doveva girare su un vecchio sistema embedded con risorse inesistenti... Ecco, questo è quanto, spero che questa nota abbia chiarito l'equivoco...)

La faccenda è complessa, e credo che richiederà piú di una puntata. In questa, tanto per cominciare, analizziamo un esempio facile facile (?), l'ottimizzazione di un loop, che è, come noto, una parte (anzi, La Parte) critica per le prestazioni di un programma. Cominciamo:
// myFunc1()
// versione con moltiplicazione, array e indice
int myFunc1(int array[], int nelems)
{
    int i;
    for (i = 0; i < nelems; i++)
        array[i] = i * 2;
}
La funzione è semplicissima, però contiene un loop, e se nelems è molto grande potrebbe diventare dispendiosa. Proviamo a ottimizzarla usando due tecniche, la Strength Reduction e la Induction Variable: vediamo, attraverso vari passaggi cosa possiamo ottenere:
// myFunc2()
// versione con incremento invece di moltiplicazione
int myFunc2(int array[], int nelems)
{
    int i, incr;
    for (i = 0, incr = 0; i < nelems; i++) {
        array[i] = incr;
        incr += 2;
    }
}

// myFunc3()
// versione con incremento e pointer invece di array
int myFunc3(int array[], int nelems)
{
    int *ptr = array;
    int i, incr;
    for (i = 0, incr = 0; i < nelems; i++, ptr++) {
        *ptr = incr;
        incr += 2 ;
    }
}

// myFunc4()
// versione con incremento, pointer e senza indice
int myFunc4(int array[], int nelems)
{
    int *ptr = array;
    int limit = nelems * 2;
    int incr;
    for (incr = 0; incr < limit; incr += 2, ptr++)
        *ptr = incr;
}
Come promesso e premesso, in questo post non voglio limitarmi a proporre tecniche di ottimizzazione senza fornire dati, per cui ho scritto un programma di test e, sulla mia macchina, con un valore di nelems abbastanza grande (0xFFFFFFF) i risultati sono questi:
- compilazione senza ottimizzazione
myFunc1 - Tempo trascorso: 1.720000 secondi.
myFunc2 - Tempo trascorso: 1.340000 secondi.
myFunc3 - Tempo trascorso: 1.160000 secondi.
myFunc4 - Tempo trascorso: 0.920000 secondi.
Quindi, sembrerebbe che le tecniche di ottimizzazione funzionano. Proviamo, però, a compilare ottimizzando, ovvero lasciamo fare al compilatore: useremo la opzione -O2 di GCC. Vediamo i nuovi risultati:
- compilazione con ottimizzazione (-O2)
myFunc1 - Tempo trascorso: 0.940000 secondi.
myFunc2 - Tempo trascorso: 0.540000 secondi.
myFunc3 - Tempo trascorso: 0.540000 secondi.
myFunc4 - Tempo trascorso: 0.540000 secondi.
Ohhh, che sorpresa! Pare che il compilatore sia più bravo di noi! Usando l'opzione -O2, la versione non ottimizzata (myFunc1) si comporta, più o meno, come quella ottimizzata da noi (myFunc4) senza usare -O2. E non è una sorpresa: semplicemente vuol dire che il compilatore ha eseguito (probabilmente) le nostre stesse ottimizzazioni manuali. Notiamo poi che, usando una qualsiasi delle versioni ottimizzate manualmente, il compilatore riesce a aggiungere un ulteriore plus di miglioramento.

Tanto per confonderci ulteriormente le idee, facciamo un altro test, sostituendo la moltiplicazione per due con uno Shift Logico:
// myFunc2Bis()
// versione con shift logico invece di moltiplicazione
int myFunc2Bis(int array[], int nelems)
{
    int i;
    for (i = 0; i < nelems; i++)
        array[i] = i << 1;
}
E vediamo la velocità senza ottimizzazione del compilatore:
- compilazione senza ottimizzazione
myFunc2Bis - Tempo trascorso: 0.950000 seconds.
Toh, pare che usando lo shift logico da solo si ottiene lo stesso risultato che si ottiene applicando tutte le tecniche illustrate (myFunc4). E se usiamo anche l'opzione -O2 cosa succede ?
- compilazione con ottimizzazione (-O2)
myFunc2Bis - Tempo trascorso: 0.540000 seconds.
otteniamo lo stesso risultato ottimizzato visto sopra. Forse abbiamo scoperto il segreto del compilatore...

Prima conclusione (ma seguiranno altri capitoli): vale la pena di trasformare un codice leggibile e chiaro come quello di myFunc1() in uno molto più criptico, come ad esempio quello di myFunc4(), solo perché non ci fidiamo del compilatore? O, aggiungerei, perchè siamo così presuntuosi da pensare di poter fare meglio del compilatore? Mi ripeto: siamo nel 2012 (quasi 2013), e bisognerebbe comportarsi di conseguenza.

Per quello visto fin'ora, pare che l'unico aiutino manuale che possiamo dare è usare (quando possibile, e solo in punti veramente critici) shift logici invece di moltiplicazioni. In questo caso raccomando però l'aggiuntina di un commento, per evitare maledizioni da parte di chi legge il nostro codice. E se siete impazienti di scoprire altre utili informazioni sull'ottimizzazione, e non volete aspettare le mie prossime puntate (traditori!), vi consiglio di farvi il solito giro su Google, oppure di leggervi direttamente un ottimo articolo dell'altrettanto ottimo Carlo Pescio.

Io tornerò sull'argomento, cercando di aggiungere dati reali e lavoro di testing (visto che ho mantenuto la promessa!) agli studi solo teorici disponibili in rete. E se no che ci sto a fare io?

Ciao e al prossimo post.

domenica 18 novembre 2012

L'ultimo degli Apache II - La vendetta
come scrivere un modulo Apache in C - pt2

Ok, lo ammetto, il titolo è preoccupante, già che somiglia moltissimo a questo, ma, essendo la seconda parte di un discorso aperto, non mi è venuto in mente nulla di meglio. Forse sto invecchiando. E se in uno dei prossimi post mi auto-plagerò anche nel testo (oltre che nel titolo) segnalatemelo, così chiudo il blog prima che sia troppo tardi.

Comunque, come promesso, oggi vi spiegherò come scrivere, installare e provare un modulo Apache minimale. Così se qualcuno si appassiona all'argomento potrà, con questa base (funzionante!), divertirsi a scriverne di più complessi, alla faccia di quelli che "Il C non serve nello sviluppo Web".

Il nostro modulo basico lo chiameremo, ad esempio myapmod. Cominciamo con il codice, ovviamente:
#include <httpd.h>
#include <http_protocol.h>
#include <http_config.h>

// handler del modulo
static int myapmodHandler(
    request_rec *reqrec)
{
    // test handler
    if (! reqrec->handler || strcmp(reqrec->handler, "myapmod"))
        return DECLINED;

    // test metodo http
    if (reqrec->method_number != M_GET)
        return HTTP_METHOD_NOT_ALLOWED;

    // html output
    ap_set_content_type(reqrec, "text/html;charset=ascii");
    ap_rputs("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\">\n", 
                        reqrec);
    ap_rputs("<html>",  reqrec);
    ap_rputs("<head>",  reqrec);
    ap_rputs("<title>Modulo Apache Elementare</title>", reqrec);
    ap_rputs("</head>", reqrec);
    ap_rputs("<body>",  reqrec);
    ap_rputs("<h1>myapmod: il mio Modulo Apache elementare</h1>", 
                        reqrec);
    ap_rputs("</body>", reqrec);
    ap_rputs("</html>", reqrec);

    // esco con OK
    return OK;
}

// register hooks del modulo
static void myapmodHooks(
    apr_pool_t *pool)
{
    // set hook handler
    ap_hook_handler(myapmodHandler, NULL, NULL, APR_HOOK_MIDDLE);
}

// struttura globale del modulo
module AP_MODULE_DECLARE_DATA myapmod_module = {
    STANDARD20_MODULE_STUFF,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    myapmodHooks
};
Semplice no ? E ho inserito gli opportuni commenti nel codice (un po' stringati, devo ammetterlo) così non devo neanche spiegarvi come funziona. E, come noterete quando lo userete la prima volta, il codice HTML ivi contenuto viene correttamente visualizzato dal browser (ohhhh... miracolo).

Ora passiamo alla compilazione e installazione. Per comodità e gusti personali vi propongo una guida per Linux, ma vi assicuro che il modulo basico si può realizzare (se proprio volete soffrire) anche sotto Windows.

Il requisito preliminare è, ovviamente, aver installato Apache sul sistema. Cercate, con il nostro amico Google, una delle milioni di guide per trasformare il proprio pc in un server LAMP (o WAMP) (non vi descrivo io la procedura per non dilungarmi troppo, ma, vi garantisco, è abbastanza semplice). Una volta installato il server Apache assicuratevi che il vostro sistema sia anche preparato per compilare i moduli Apache: deve essere disponibile il pacchetto apache2-prefork-dev, quindi, per accertarvene, eseguite:
dpkg -l | grep apache2
E, se nella lista di pacchetti che vi verrà mostrata non c'è il pacchetto suddetto, installatelo con:
sudo apt-get install apache2-prefork-dev
E, adesso possiamo, finalmente, compilare e installare con:
sudo apxs2 -c mod_myapmod.c 
sudo apxs2 -i mod_myapmod.la
Poi, dobbiamo creare ed editare due nuovi file nella directory /etc/apache2/mods-available. Creiamo/apriamo il primo con:
sudo gedit /etc/apache2/mods-available/myapmod.load
e gli scriviamo dentro:
LoadModule myapmod_module /usr/lib/apache2/modules/mod_myapmod.so
Quindi creiamo/apriamo il secondo con:
sudo gedit /etc/apache2/mods-available/myapmod.conf
e gli scriviamo dentro:
<Location /myapmod>
SetHandler myapmod
</Location>
A questo punto non ci resta che abilitare il nostro nuovo modulo e far ripartire il Server Apache (che fará partite tutti i moduli abilitati, compreso il nostro myapmod):
sudo a2enmod myapmod
sudo /etc/init.d/apache2 restart
Se tutto é stato fatto correttamente, navigando con un browser alla url http://localhost/myapmod verrà visualizzato il codice HTML inserito nel nostro modulo. E se non funziona riprovate tutti i passi cominciando  dal primo, verificando che non vi sia sfuggito qualche errore segnalato dal sistema. Buona fortuna... (io, ovviamente, ho provato tutta la procedura prima di pubblicare il post, e sono sicuro che funziona).

Ovviamente con questo post non pretendo di scoprire l'acqua calda, visto che, se cercate su Internet come costruire un modulo Apache minimale, scoprirete che moltissimi altri hanno pubblicato ben prima di me guide analoghe. Ma volete mettere con leggere la stessa cosa scritta dal vostro C-blogger favorito?

Ciao e al prossimo post.

domenica 11 novembre 2012

L'ultimo degli Apache
come scrivere un modulo Apache in C - pt.1

Va bene: con un titolo come quello sopra, questo non può essere, sicuramente, il post (promesso) sull'ottimizzazione del codice. Ci sto ancora lavorando, e non so quando lo finirò, per cui, nel frattempo, pubblicherò qualche post più semplice. Ribadisco il consiglio di non trattenere il fiato in attesa di quel post (potrebbe passare molto tempo...).

Veniamo al dunque: anche questa volta cercherò di sfatare un mito (ci risiamo): quello del C e della programmazione Web.

Mi e capitato di sentir dire "Il C è un linguaggio universale, permette di fare quasi tutto (blah, blah, blah...) però nello sviluppo web non serve a nulla, lì servono altri linguaggi". Risata (controllata). Altra risata (lunga, questa volta).

Secondo me, in realtà, un (buon) programmatore C può entrare nel mondo Web per la porta grande: tanto per cominciare, buona parte dei linguaggi usati nello sviluppo Web sono C-like (ovvero i loro creatori si sono ispirati alla sintassi del C quando li hanno progettati): PHP, Java, C#, JavaScript, tanto per citarne solo alcuni. E se sono C-like saranno facilmente assimilabili da un esperto di C. E se il nostro esperto conosce (almeno un po') anche il C++ (cosa frequente tra i buoni programmatori C) anche il lato OOP dei linguaggi citati sarà facilmente interpretabile.

Quindi, un (buon) programmatore C può entrare nel mondo web molto rapidamente (non foss'altro per la forma mentis posseduta), e ve lo dico per esperienza personale, visto che ci sono passato anch'io (ma allora sto dicendo che sono un buon programmatore C ?). E, per puntualizzare e completare il discorso (e senza voler offendere nessuno), non credo che il passaggio inverso sia altrettanto semplice (un programmatore Web puro che si trasforma rapidamente in un buon sviluppatore C). Su questa mia ultima affermazione sono, comunque, molto aperto a commenti, obiezioni e impressioni diverse dalle mie. Magari mi sbaglio.

Uscendo, poi, dal discorso del linguaggio puro e duro, passo all'argomento applicativo (e, così, spieghiamo anche il misterioso titolo di questo post): il web vive su Apache e Apache vuol dire C. È noto che circa il 70% dei server web sono basati su Apache (è un dato di fatto), e, anche se è vero che e possibile lavorare a lungo nel mondo Web senza mai scrivere un modulo Apache, è anche vero che qualcuno deve aver pur scritto i moduli esistenti, e, prima o poi (e per svariati motivi), qualcuno avrà bisogno di un modulo ad-hoc per una attività particolare o dovrà toccare il codice di un modulo già esistente: in quel caso ci vogliono buoni programmatori C. Anche nel mondo Web. E ho detto tutto.

Nel mio prossimo post  vi presenterò un modulo Apache minimale (ce l'ho già quasi pronto), cosi vedremo in pratica cosa significa quanto detto sopra.

Ciao e al prossimo post.