Skip to Content
Nuova versione 12 disponibile 🎉

Prestazioni e tuning

L’offuscamento del flusso di controllo offre protezione in cambio di un po’ di tempo di esecuzione. Gli algoritmi leggeri sono praticamente gratuiti; gli algoritmi di appiattimento (flattening) aggiungono un dispatcher che viene eseguito a ogni chiamata e quindi costano di più. Questa pagina confronta gli algoritmi, spiega l’impostazione delle iterazioni e mostra come mantenere veloci i metodi eseguiti più di frequente.

Confronto tra gli algoritmi

AlgoritmoFamigliaCosto relativo in fase di esecuzioneIL verificabileEdizione
ifLeggeraTrascurabileSìTutte
gotoLeggeraTrascurabileSìTutte
switchAppiattimentoAltoSìTutte
caseAppiattimento (con switch)AltoSìTutte
callAppiattimento (con switch)AltoSìTutte
valueAppiattimento (con switch)AltoSìTutte
chain (Chained State)AppiattimentoAlto (≈ switch)SìUltimate
token—BassoNoTutte
underflow—BassoNoTutte

Come leggere la tabella.

  • if e goto aggiungono diramazioni e condizioni ma nessun dispatcher, quindi il loro costo in fase di esecuzione è trascurabile: puoi applicarli su larga scala.
  • La famiglia dell’appiattimento (switch, case, call, value) e chain ricostruiscono un metodo attorno a un dispatcher. In un metodo piccolo, chiamato decine di milioni di volte in un ciclo stretto, un solo passaggio di appiattimento aumenta di circa un ordine di grandezza il tempo per chiamata di quel metodo. Chained State resta entro pochi punti percentuali da switch, pur essendo molto più difficile da deoffuscare: la protezione aggiuntiva è praticamente gratuita rispetto all’appiattimento stesso.
  • token e underflow costano poco in fase di esecuzione ma producono IL non verificabile, quindi Babel li disabilita su .NET e .NET Core.

Questi sono valori del caso peggiore, ottenuti con un micro-benchmark che non fa altro che chiamare in un ciclo un unico metodo minuscolo appiattito. La maggior parte dei metodi non viene chiamata in cicli così intensi, quindi l’impatto reale dell’appiattimento è di solito ridotto. Conviene comunque riservare l’appiattimento ai metodi che contano e tenerlo lontano dai percorsi eseguiti più di frequente.

Iterazioni del flusso di controllo

L’effetto di offuscamento di qualsiasi algoritmo del flusso di controllo dipende soprattutto dal numero di iterazioni applicate a un metodo: più il numero è alto, più il risultato è complesso e, per gli algoritmi di appiattimento, più alto è il costo in fase di esecuzione, perché ogni passaggio aggiunge un altro livello di dispatch.

Babel ti permette di impostare il numero di iterazioni con --iterations <n> (o con ILIterations da MSBuild). Impostandolo a 0 disabiliti l’offuscamento del flusso di controllo. Esiste un limite superiore, che dipende dalla struttura del metodo, oltre il quale il flusso di un dato metodo non può più essere alterato: aumentare il numero non aggiunge quindi sempre altre istruzioni.

Limitare l’offuscamento del flusso di controllo

Poiché l’appiattimento aggiunge diramazioni a ogni metodo che tocca, applicarlo indiscriminatamente può rallentare il codice critico per le prestazioni. Usa le regole XML per limitare l’offuscamento del flusso di controllo ai metodi che ne hanno bisogno ed escludere gli altri, per esempio i metodi generati dal compilatore, che raramente contengono proprietà intellettuale da proteggere:

<Rules> <Rule name="rule" feature="control flow" exclude="true" applyToMembers="true"> <Target>Classes</Target> <Pattern>*</Pattern> <HasAttribute onEnclosingType="false">System.Runtime.CompilerServices.CompilerGeneratedAttribute</HasAttribute> </Rule> </Rules>

Questa regola si applica alla funzionalità control flow con exclude="true", e applyToMembers="true" la estende a tutti i membri delle classi corrispondenti. L’elemento <Target> seleziona il tipo di elemento (qui Classes), <Pattern> corrisponde ai loro nomi (* = tutti) e <HasAttribute> limita la corrispondenza alle classi che hanno l’attributo CompilerGeneratedAttribute.

Allo stesso modo puoi escludere qualsiasi metodo che sai essere critico per le prestazioni, bilanciando così protezione e velocità: appiattisci il codice sensibile e lascia intatti i percorsi eseguiti più di frequente.

Last updated on