Visto che di tanto in tanto mi arrivano domande con questo tipo di domande, ho pensato di bloggare domande e relative risposte:
Q: L'eseguibile generato dal compilatore è win32 o sempre IL?
A: Il compilatore C++ può generare tre tipi di output:
- solo codice nativo (quando non si usa /clr nelle opzioni e quindi niente C++/CLI)
- solo codice managed (cioè solo IL, usando /clr:pure)
- codice misto nativo/managed (usando /clr)
Q: Come posso controllare quale parte del codice verrà compilato come nativo o managed?
A: Il codice scritto nel "dialetto" CLI verrà compilato come IL (vedi ref class, etc.) mentre quello tradizionale in modo nativo (x86 o altro assembler).
Se si vuole forzare quali parti compilare in modo nativo e quali in modo managed, si può usare la direttiva #pragma managed / #pragma unmanaged:
http://msdn.microsoft.com/en-us/library/0adb9zxe(VS.80).aspx
Il pregio del compilatore MS C++ è che l'assembly risultante da una compilazione "mista" contiene sia codice nativo che managed. Il "trucco" viene effettuato nella fase di linking ed è anche fattibile "a mano" tra più .netmodule di compilatori managed. Sto dicendo in pratica che link.exe è in grado di unire listati generati da C#, Vb.net, C++, etc. in un unico assembly come ho fatto vedere in uno dei workshop UGIdotNET.
Q: Per applicazioni industriali posso ottenere performance migliori utilizzando C++/CLI ? Per usare componenti COM è più performante usare C++/CLI o C#?
A: Ovviamente dipende da cosa fanno queste applicazioni. Ci sono diverse false credenze rispetto alle performance del codice managed.
In generale un algoritmo che lavora in memoria può avere performance addirittura migliori in C# rispetto a C++ nativo grazie alle ottimizzazioni che vengono fatte "gratis" dal garbage collector che tenta di mettere l'Ephemeral Segment (il segmento di circa 16MB che contiene le generazioni 0 e 1) dentro la cache L2.
Ovviamente un buon listato C++ può avere performance uguali o magari migliori, ma certamente ad un costo (in termini di impegno per lo sviluppatore) decisamente superiore.
Se l'applicazione esegue tanta interoperabilità verso codice nativo (vedi COM o PInvoke, sia se effettuato esplicitamente dallo sviluppatore o internamente dalle librerie come nel caso di Windows Forms) il peso in performance del marshalling (copia di buffer di memoria da nativo a managed o viceversa) può essere certamente importante. Il costo effettivo dipende da:
- numero di transizioni (cioè di chiamate a proprietà, metodi, eventi)
- quantità di memoria spostata (in, out, in/out)
Se questi numeri sono bassi probabilmente non inficieranno sulle performance da C#.
Se l'applicazione accede a strutture di memoria a lunghezza variabile (come nel caso di elaborazione immagini), i puntatori del C hanno performance migliori e il grandissimo torto di avere una pessima parallelizzazione.
Q: Come posso valutare le performance in pratica?
A: Come dice Rico Mariani: "measure measure measure"
Il primo strumento in assoluto è il performance monitor (perfmon.exe) di Windows che offre decine e decine di counter per capire i problemi di performance, tra i quali il numero di transizioni native/managed per l'interoperabilità.
Poi ci sono i profiler come quello di Visual Studio Developer edition, Ants (che sono prodotti commerciali a pagamento) ed altri free che non ho avuto il piacere di provare.
Spesso è bene comparare i risultati con più profiler di performance, soprattutto quando viene usata la tecnica di instrumenting che altera l'eseguibile per raccogliere dati utili alla misurazione.
Da notare che spesso vedo fare grossolani errori nel codice managed che portano a performance povere. Quindi l'uso del profiler spesso mostra l'utilizzo di "worst practices" o banali errori e non tanto limitazioni in se del linguaggio C# o del Framework.net.