Procedure consigliate di ottimizzazione

Questo documento descrive alcune procedure consigliate per l'ottimizzazione dei programmi C++ in Visual Studio.

Opzioni del compilatore e del linker

Ottimizzazione PGO

Visual Studio supporta l'ottimizzazione pgo (Profile-Guided Optimization ). Questa ottimizzazione usa i dati del profilo dalle esecuzioni di training di una versione instrumentata di un'applicazione per favorire un'ottimizzazione successiva dell'applicazione. L'uso di PGO può richiedere molto tempo, quindi potrebbe non essere qualcosa che ogni sviluppatore usa, ma è consigliabile usare PGO per la build finale di un prodotto. Per altre informazioni, vedere Ottimizzazioni PGO.

Inoltre, l'ottimizzazione dell'intero programma (nota anche come Generazione di codice tempo di collegamento) e le /O1 ottimizzazioni e /O2 sono state migliorate. In generale, un'applicazione compilata con una di queste opzioni sarà più veloce rispetto alla stessa applicazione compilata con un compilatore precedente.

Per altre informazioni, vedere /GL (Ottimizzazione dell'intero programma) e/O1 , /O2 (Ridurre al minimo le dimensioni, ottimizzare la velocità).

Quale livello di ottimizzazione usare

Se possibile, le build di versione finale devono essere compilate con ottimizzazioni guidate profilo. Se non è possibile compilare con PGO, a causa di un'infrastruttura insufficiente per l'esecuzione delle build instrumentate o se non si ha accesso agli scenari, è consigliabile creare con l'ottimizzazione dell'intero programma.

Anche l'interruttore /Gy è molto utile. Genera un COMDAT separato per ogni funzione, offrendo al linker maggiore flessibilità quando si tratta di rimuovere COMDAT non referenziati e riduzione COMDAT. L'unico aspetto negativo dell'uso /Gy è che può causare problemi durante il debug. Pertanto, è in genere consigliabile usarlo. Per altre informazioni, vedere /Gy (Abilitare il collegamento a livello di funzione).

Per il collegamento in ambienti a 64 bit, è consigliabile usare l'opzione /OPT:REF,ICF del linker e in ambienti /OPT:REF a 32 bit. Per altre informazioni, vedere /OPT (Ottimizzazioni).

È anche consigliabile generare simboli di debug, anche con build di versione ottimizzate. Non influisce sul codice generato e rende molto più semplice eseguire il debug dell'applicazione, se necessario.

Commutatori a virgola mobile

L'opzione /Op del compilatore è stata rimossa e sono state aggiunte le quattro opzioni del compilatore seguenti relative alle ottimizzazioni a virgola mobile:

Opzione Descrizione
/fp:precise Si tratta della raccomandazione predefinita e deve essere usata nella maggior parte dei casi.
/fp:fast Consigliato se le prestazioni sono della massima importanza, ad esempio nei giochi. Ciò comporterà prestazioni più veloci.
/fp:strict Consigliato se si desiderano eccezioni a virgola mobile precise e comportamento IEEE. Ciò comporterà le prestazioni più lente.
/fp:except[-] Può essere usato insieme a /fp:strict o /fp:precise, ma non /fp:fasta .

Per altre informazioni, vedere /fp (Specificare il comportamento a virgola mobile).

Declspec di ottimizzazione

In questa sezione verranno esaminati due declspec che possono essere usati nei programmi per facilitare le prestazioni: __declspec(restrict) e __declspec(noalias).

Il restrict declspec può essere applicato solo alle dichiarazioni di funzione che restituiscono un puntatore, ad esempio __declspec(restrict) void *malloc(size_t size);

Il restrict declspec viene usato nelle funzioni che restituiscono puntatori nonliati. Questa parola chiave viene usata per l'implementazione della libreria C-Runtime di malloc perché non restituirà mai un valore puntatore già in uso nel programma corrente (a meno che non si stia eseguendo un'operazione non valida, ad esempio l'uso della memoria dopo che è stato liberato).

Il restrict declspec fornisce al compilatore altre informazioni per l'esecuzione di ottimizzazioni del compilatore. Uno degli aspetti più difficili per un compilatore è determinare quali puntatori alias altri puntatori e l'uso di queste informazioni aiuta notevolmente il compilatore.

Vale la pena sottolineare che si tratta di una promessa al compilatore, non qualcosa che il compilatore verificherà. Se il programma usa questo restrict declspec in modo inappropriato, il programma potrebbe avere un comportamento errato.

Per ulteriori informazioni, vedere restrict.

Il noalias declspec viene applicato anche alle funzioni e indica che la funzione è una funzione semi pure. Una funzione semi pure è una funzione che fa riferimento o modifica solo variabili locali, argomenti e indiretti di primo livello degli argomenti. Questo declspec è una promessa al compilatore e se la funzione fa riferimento a globals o indiretti di secondo livello di argomenti puntatore, il compilatore può generare codice che interrompe l'applicazione.

Per ulteriori informazioni, vedere noalias.

Pragma di ottimizzazione

Esistono anche diversi pragma utili per ottimizzare il codice. Il primo di cui discuteremo è #pragma optimize:

#pragma optimize("{opt-list}", on | off)

Questo pragma consente di impostare un determinato livello di ottimizzazione in base alla funzione. Questo è ideale per quelle rare occasioni in cui l'applicazione si arresta in modo anomalo quando una determinata funzione viene compilata con l'ottimizzazione. È possibile usare questa opzione per disattivare le ottimizzazioni per una singola funzione:

#pragma optimize("", off)
int myFunc() {...}
#pragma optimize("", on)

Per ulteriori informazioni, vedere optimize.

L'inlining è una delle ottimizzazioni più importanti eseguite dal compilatore e qui si parla di un paio di pragmas che consentono di modificare questo comportamento.

#pragma inline_recursion è utile per specificare se si vuole che l'applicazione sia in grado di inline una chiamata ricorsiva. Per impostazione predefinita, è disattivata. Per una ricorsione superficiale di piccole funzioni, è possibile attivarla. Per ulteriori informazioni, vedere inline_recursion.

Un altro pragma utile per limitare la profondità dell'inlining è #pragma inline_depth. Ciò è in genere utile nelle situazioni in cui si sta tentando di limitare le dimensioni di un programma o di una funzione. Per ulteriori informazioni, vedere inline_depth.

__restrict e __assume

In Visual Studio sono disponibili due parole chiave che consentono di ottenere prestazioni: __restrict e __assume.

Prima di tutto, va notato che __restrict e __declspec(restrict) sono due cose diverse. Anche se sono in qualche modo correlati, la loro semantica è diversa. __restrict è un qualificatore di tipo, ad esempio const o volatile, ma esclusivamente per i tipi di puntatore.

Un puntatore modificato con __restrict viene definito puntatore __restrict. Un puntatore __restrict è un puntatore accessibile solo tramite il puntatore __restrict. In altre parole, non è possibile usare un altro puntatore per accedere ai dati a cui punta il puntatore __restrict.

__restrict può essere uno strumento potente per Microsoft C++ Optimizer, ma usarlo con grande attenzione. Se usato in modo non corretto, l'utilità di ottimizzazione potrebbe eseguire un'ottimizzazione che interrompe l'applicazione.

Con __assume, uno sviluppatore può indicare al compilatore di fare ipotesi sul valore di una variabile.

Ad esempio __assume(a < 5); , indica a Optimizer che in corrispondenza di tale riga di codice la variabile a è minore di 5. Anche in questo caso si tratta di una promessa per il compilatore. Se a in realtà è 6 a questo punto nel programma, il comportamento del programma dopo che il compilatore ha ottimizzato potrebbe non essere quello previsto. __assume è particolarmente utile prima di cambiare le istruzioni e/o le espressioni condizionali.

Esistono alcune limitazioni per __assume. In primo luogo, come __restrict, è solo un suggerimento, quindi il compilatore è libero di ignorarlo. Inoltre, __assume attualmente funziona solo con disuguaglianze variabili rispetto alle costanti. Non propaga le disuguaglianze simboliche, ad esempio presupporre(a < b).

Supporto intrinseco

Gli intrinseci sono chiamate di funzione in cui il compilatore ha una conoscenza intrinseca della chiamata e, anziché chiamare una funzione in una libreria, genera codice per tale funzione. Il file <di intestazione intrin.h> contiene tutte le funzioni intrinseche disponibili per ognuna delle piattaforme hardware supportate.

Le funzioni intrinseche consentono al programmatore di approfondire il codice senza dover usare l'assembly. L'uso di oggetti intrinseci offre diversi vantaggi:

  • Il codice è più portabile. Molte delle funzioni intrinseche sono disponibili in più architetture della CPU.

  • Il codice è più facile da leggere, poiché il codice è ancora scritto in C/C++.

  • Il codice ottiene il vantaggio delle ottimizzazioni del compilatore. Man mano che il compilatore migliora, la generazione del codice per gli intrinseci migliora.

Per altre informazioni, vedere Funzioni intrinseche del compilatore.

Eccezioni

Si verifica un riscontro delle prestazioni associato all'uso delle eccezioni. Alcune restrizioni vengono introdotte quando si usano blocchi try che impediscono al compilatore di eseguire determinate ottimizzazioni. Nelle piattaforme x86 si verifica un calo delle prestazioni aggiuntivo dai blocchi try a causa di informazioni aggiuntive sullo stato che devono essere generate durante l'esecuzione del codice. Nelle piattaforme a 64 bit provare i blocchi non degradare le prestazioni, ma una volta generata un'eccezione, il processo di individuazione del gestore e la rimozione dello stack può essere costoso.

Pertanto, è consigliabile evitare di introdurre blocchi try/catch nel codice che non ne ha realmente bisogno. Se è necessario usare le eccezioni, usare le eccezioni sincrone, se possibile. Per altre informazioni, vedere Structured Exception Handling (C/C++).

Infine, genera eccezioni solo per casi eccezionali. L'uso di eccezioni per il flusso di controllo generale potrebbe comportare una riduzione delle prestazioni.

Vedi anche