Soluzione alle ottimizzazioni non intuitive

Nel mio post precedente ho illustrato due varianti di codice C# chiedendo quale sia la più performante. Ho ricevuto diverse mail a riguardo e riassumo qui per tutti la soluzione.

  • Il codice più veloce è quello di CompareU2 rispetto a CompareU. In pratica, è più veloce fare ch = m1[i] in un ciclo dove i va da 0 a N, piuttosto che fare ch = *m1++ in un ciclo di N iterazioni.
  • Chi arriva dal C (o programma in assembler) avrebbe scommesso il contrario (e molti l’hanno fatto – io ero tra questi)
  • Normalmente l’accesso a un array attraverso un offset (m1[i]) è più lento dell’accesso attraverso un puntatore che viene spostato con l’aritmetica dei puntatori (*m1). In C# però il codice IL compilato nel primo caso lavora solo sullo stack, nel secondo deve aggiornare una variabile. Se il jitter usa solo registri di CPU per risolvere l’algebra di offset e va invece a scrivere in ram il valore del puntatore modificato… ecco una possibile spiegazione
  • Il codice esterno al loop for(…) non è molto rilevante ai fini dell’ottimizzazione (l’algoritmo confronta due stringhe usando ? come carattere “jolly”, richiedendo l’uguaglianza per tutti gli altri caratteri). Il numero di ? in una stringa è generalmente una piccola frazione dell’intera stringa usata come pattern di ricerca.
  • In realtà non sono andato a vedere il codice macchina generato dal jitter, ma i numeri misurati sono stati inequivocabili. Con alcune stringhe di riferimento per i dati tipici dello scenario in cui sarà usata, la CompareU2 è più veloce di quasi il 25% rispetto alla CompareU. Non si tratta di dettagli per funzioni chiamate milioni di volte al minuto.
  • Il motivo per cui si usano i puntatori è che il codice “safe” che usa direttamente le stringhe (non riportato nel post, ma di facile costruzione) ha un costo di esecuzione a metà tra CompareU2 e CompareU. Il che porta a pensare che sia necessario usare algoritmi simili solo in condizioni “estreme”.

La lezione è che nonostante le apparenze, rispetto al C++ è ben diversa la logica con cui pensare alle ottimizzazioni fatte sul codice C# da parte, rispettivamente, del compilatore e del jitter. Magari potremmo parlare di “mancate ottimizzazioni” del jitter, almeno in questo caso… ma questo è lo stato attuale (con compilatori e runtime di .NET 2.0). Prima di giungere a conclusioni, è sempre bene fare qualche misura – anche l’esperienza porta ad avere pregiudizi, talvolta non validi.

Ottimizzare con C# non è come con C++

Ogni tanto si scoprono cose interessanti. Cercando di ottimizzare una funzione, mi sono imbattuto in un dubbio che vi sottopongo: secondo voi (senza eseguire il codice) è più veloce il codice di CompareU o di CompareU2?

Se qualcuno è interessato a rispondere alla domanda, sarà interessante discutere privatamente sul perché quello più veloce sia più veloce… 🙂

        static bool CompareU(string match, string compared)
        {
            if (match.Length != compared.Length) return false;
            unsafe
            {
                fixed( char* _m1 = match, _m2 = compared )
                {
                    char* m1 = _m1;
                    char* m2 = _m2;
                    for (int i = 0; i < match.Length; i++)
                    {
                        char ch = *m1++; 
                        if (ch != '?')
                        {
                            if (*m2 != ch) return false;
                        }
                        m2++;
                    }
                }
            }
            return true;
        }

        static bool CompareU2(string match, string compared)
        {
            if (match.Length != compared.Length) return false;
            unsafe
            {
                fixed (char* m1 = match, m2 = compared)
                {
                    for (int i = 0; i < match.Length; i++)
                    {
                        char ch = m1[i];
                        if (ch != '?')
                        {
                            if (m2[i] != ch) return false;
                        }
                    }
                }
            }
            return true;
        }

 

Tool preziosi

Ultimamente mi sono trovato ad apprezzare dei tool che, oltre a essere utilissimi, hanno anche un rapporto qualità/prezzo eccellente.

Partiamo dall’ottimizzazione del codice. In una sola settimana dotTrace mi ha consentito di individuare e risolvere problemi prestazionali tutt’altro che banali. Il tool da solo non basta, bisogna saper interpretare i dati (soprattutto quando la routine che va lenta non è il problema ma la cosa da fare è fare in modo di chiamarla il meno possibile…). Ma senza il tool sarebbe impossibile venirne a capo. Oltre alla facilità e immediatezza di utilizzo, la funzione di Sampling è poco invasiva (a costo di dati meno accurati ma non per questo meno utili) e consente di usare fare profile anche quando il principio di indeterminazione di Heisenberg ce lo impedirebbe (lo so, potevo farla meno pomposa, ma è anche sabato…). Costo: 499$ (le prime versioni erano molto più economiche). Ma va detto che la demo è completamente funzionante per 10 giorni e quindi è come dire che i primi problemi li risolvete gratis… poi decidete se vale la pena.

Passiamo alle alternative a Team System. Nel senso che non tutti hanno Team System, ma chiunque sviluppi (anche da solo) ha alcune esigenze minime: gestione sorgenti, bug tracking e unit test. Nell’ordine quello che uso sono SourceGear Vault, Axosoft OnTime e NUnit. Tutti questi prodotti, per un singolo sviluppatore, sono gratuiti (pur essendo i primi due commerciali). Per una piccola software house come ce ne sono molte in Italia, il costo per le licenze di 5/10 sviluppatori sono veramente competitivi (1000/2000$ per Vault, 500/1500$ per OnTime – in pratica con meno circa 350$ per sviluppatore, cioè 260€ al cambio odierno). Siccome so che esiste ancora qualcuno che usa lo ZIP per archiviare i sorgenti e un documento Excel/Word (o una cartella di Outlook) per gestire i bug da risolvere… Non è una questione di moda, ma proprio solo di produttività.

Editor di testo: siamo tutti affezionati al Notepad… ma Notepad++ oltre che essere gratuito (ma una donazione se la meritano) è pure un editor “serio” (syntax highlighting, outlining del codice, code navigator e chi più ne ha più ne metta) ma resta un programma “relativamente” leggero in termini di consumo di risorse (5/7Mb di memoria virtuale impegnata senza file aperti, working set iniziale più alto ma sappiamo che conta relativamente poco in questa valutazione).

Infine… come dice Davide Mauri, un tool da very geek, o più semplicemente qualcosa che aiuta a capire come funziona internamente la memorizzazione dei dati in SQL Server: parlo di SQL Internals Viewer, software che ha ancora qualche bug di troppo ma che implementa un’idea stupenda che è quella di dare forma grafica a come i dati sono organizzati fisicamente su SQL Server. Didatticamente è un gioiello.

L’abuso di "by design"

Ho appena pubblicato un post sul mio blog in inglese dedicato alla BI, ma l’argomento è di interesse generale: riguarda l’abuso che ogni tanto viene fatto del “è così by design” per giustificare un bug o un comportamento anomalo.

L’esempio è relativo a SQL Server ed è più che altro un pretesto per affrontare l’argomento, sperando che qualcuno (a Redmond) legga e risponda… comunque se qualcuno avesse aneddoti o commenti, qua possiamo parlarne in italiano.

Piccole Software House crescono

Chi osserva il mercato del software spesso è portato a fare considerazioni semplicistiche sulla dinamica del mercato. La più comune è che Microsoft, per crescere, “fagocita” il mercato di software house esistenti (che chiamerò anche io ISV, Independent Software Vendor), acquisendole o entrando in diretta competizione con loro.

Questo è vero se si considerano le cose da un certo punto di vista: se Microsoft fa un suo antivirus, è inevitabile che vada a erodere quote di mercato dei player esistenti. Se Microsoft entra nel mercato del Master Data Management acquisendo uno dei player maggiori (Stratature) ecco che il resto del mercato si terrorizza.

Cambiando prospettiva, le cose appaiono diverse. Uno dei problemi di Microsoft è la sua dimensione. Crescendo ne è diminuita l’agilità e la rapidità di reagire alle richieste del mercato. Inoltre quello che è marginale per Microsoft rappresenta un mercato magari significativo per un’azienda più piccola.

Sì, perché Microsoft è cresciuta ma anche il mercato è diventato negli anni molto più grande. Questo ha consentito la creazione negli ultimi anni di aziende che sono nate praticamente da zero, hanno offerto un prodotto, hanno soddisfatto un’esigenza del mercato (magari a costi più bassi delle alternative esistenti), hanno dato un servizio ai clienti che è impossibile avere da un colosso come Microsoft (non ne sto facendo una colpa, è inevitabile che sia così).

Il fenomeno degli ISV, soprattutto dei “piccoli” ISV, è ancora più significativo in Italia, dove però si scontano due problemi: i limiti (numerici e “culturali”) del mercato italiano e la barriera linguistica (un software in Italiano si vende solo in Italia). Anche per le aziende che operano solo nel mercato nazionale, però, gran parte dell’esperienza di ISV di successo è qualcosa di utile da conoscere, per poterne trarre insegnamenti applicabili anche da noi, fatti i dovuti adattamenti dovuti alle diverse realtà di mercato (che però non può voler dire “sì, bello, ma da noi nessuna di queste cose si può fare).

Una conferenza su questi argomenti che varrebbe la pena andare a vedere è Business of Software, che si terrà in California il 29 e 30 ottobre 2007. Anche se non potete andare, andando sul sito dell’evento potete però scaricare il libro “Business of Software” di Eric Sink che è una lettura veramente interessante e istruttiva. Suggerisco poi (a chi vuole approfondire) di leggere blog e materiale degli speaker che interverranno.

Se 4Gb non bastano

Avere una workstation di sviluppo con 4Gb di RAM, due monitor grandi, schede video adeguate… può sembrare sufficiente per avere un ambiente efficiente con cui lavorare.

Con Windows XP era vero. Con Windows Vista non lo è più molto. Il Desktop Windows Manager “consuma” tranquillamente 600/700Mb quando sto lavorando “normalmente” (ok, non uso solo Word ed Excel) ma il problema è che non ho alcuna intenzione di disabilitare Aero. Le prestazioni di Vista (a parità di hardware di alto livello) sono nettamente migliori che con Windows XP. Purtroppo, le cose belle costano sempre qualcosa in più.

Per ora ho “tamponato” ampliando la memoria virtuale e lasciando 4Gb di paging file (volevo tenerlo a 2Gb ma esplode troppo facilmente). Sta tornando di moda avere un paging file grande 1.5 volte la RAM disponibile. Spero che non stiano facendo tutto questo solo per smentire alcune mie affermazioni sulla configurazione del paging file… 🙂

Comunque… la motherboard che ho non supporta più di 4Gb di RAM. Molte motherboard ancora oggi soffrono di questa limitazione, nonostante il supporto per i 64 bit. Tutto sommato però non sono qua a lamentarmi. Se uno decide che ha bisogno di qualche centinaio di Mb solo per fare cache di qualche bitmap e avere una grafica più fluida, non vedo perché glielo si debba negare. La conseguenza (indesiderata) è che con tutta questa RAM a disposizione uno si sente poi autorizzato a fare qualsiasi tipo di spreco immaginabile nei suoi programmi… Ricordatevi che esistono i profiler (anche della memoria)!