Quali parole chiave sono importanti per velocizzare il codice in C ++?

5

Sto eseguendo il porting su C ++ e aggiungendo molte funzionalità a un'applicazione numerica scritta in Fortran 77. Mentre odio F77, devo ammettere che la cosa va molto veloce. Ora, sto implementando praticamente la stessa struttura e algoritmo, ma tuttavia la differenza nel tempo di esecuzione è notevole. Non grande, ma è lì. E mi chiedo, potrebbe essere che ho bisogno di passare informazioni extra al compilatore? Le uniche informazioni correlate che sono riuscito a trovare sono le raccomandazioni per utilizzare la parola chiave restrict supportata da g ++, ma questo è tutto. Quindi, la mia domanda: ci sono più parole chiave importanti per accelerare il codice in C ++ in applicazioni numeriche? o forse direttive del compilatore?

Grazie!

    
posta cauchy 31.10.2011 - 21:28
fonte

7 risposte

16

Non credo che tu debba concentrare le tue ottimizzazioni alla ricerca di parole chiave magiche, ma ecco alcuni suggerimenti:

  1. " inline " potrebbe fare miracoli nelle app numeriche se hai molte funzioni numeriche corte

  2. Fai attenzione quando copi i valori ovunque quando puoi usare un riferimento. Questo suona semplice, ma se hai molte classi compatibili con copia / compito, il tuo codice potrebbe nascondere alcune copie. Assicurati che i tuoi costruttori siano definiti con la parola chiave " esplicito ", in questo modo il compilatore mostrerà dove sono state eseguite le copie implicite.

  3. Dai una prova a C ++ 11. Ci sono alcune ottimizzazioni interessanti. Ci sono più cose che sono risolte staticamente. Inoltre, una cosa molto interessante per il tuo caso è l'ottimizzazione rvalue reference . È un'ottimizzazione molto importante sul valore di ritorno delle funzioni. Lo noterai molto se sovraccarichi gli operatori, cosa che farai se utilizzi unità diverse e provi a strutturare il tuo vecchio codice.

  4. Ci sono molte cose che possono essere fatte con STL e metaprogrammi di template davvero interessanti, ma questo è un livello completamente diverso da quello a cui la maggior parte della gente è abituata.

  5. Non fare troppo affidamento sulle ottimizzazioni del compilatore. La sintassi C ++ è ben nota per semplificare la vita dei programmatori di basso livello e un incubo per i progettisti di compilatori. Questo è uno dei motivi per cui i buoni analizzatori statici hanno impiegato così tanto tempo a comparire. So che Scott Mayers ha fatto questo commento e come i miglioramenti di C ++ 11 aiuteranno a ottenere strumenti migliori, ma non possiamo citare dove ha fatto.

  6. Sulla linea con i compilatori ... non aspettarti di ottenere SIMD e le ottimizzazioni generali del paralel. I compilatori sono davvero cattivi e, a meno che non ti sporchi le mani con qualche libreria, il tuo codice avrà come target solo un core, un thread della tua macchina.

  7. Potrebbe sembrare sciocco, ma la parola chiave " const " è davvero importante. Se si rende chiaro che alcuni valori non cambiano, il compilatore applicherà una serie di ottimizzazioni aggiuntive. Rilevare se una variabile può essere considerata const richiede parsing extra che la maggior parte dei compilatori non eseguono nemmeno con le ottimizzazioni al massimo. In questo caso, ad esempio, se i tuoi loop non sembrano avere una fine chiara / costante, il compilatore non li srotoli (o forse non è buono come potrebbe)

Tuttavia mi chiedo perché stai migrando da Fortran77 a C ++. Fortran è sicuramente una vecchia lingua, ma è il re per la matematica, non importa quanto brutto possa sembrare, il modo migliore per gestire il vecchio codice è costruire un buon livello e gestirlo dall'esterno con la nuova infrastruttura e il linguaggio che desideri. È più facile a dirsi che a farsi, perché sicuramente dovrai ancora correggere qualche codice Fortran per rendere più semplice l'utilizzo dall'esterno.

Se decidi di andare avanti con il C ++, avrai a disposizione molto più belle librerie per il multithreading (e questo è uno dei più grandi miglioramenti di C ++ 11) dove Fortran resta indietro. Cerca di evitare CUDA se non è assolutamente necessario, è davvero buono e maturo ma focalizzato su NVIDIA. Terrò d'occhio OpenCL

Tieni presente che esiste un'enorme differenza per Math. In C ++ (come la maggior parte delle lingue), i valori di una matrice vengono memorizzati come un insieme di righe mentre Fotran li memorizza come un insieme di colonne. Questo fa una grande differenza sul miglior utilizzo della memoria dei tuoi algoritmi. Quindi, se i tuoi algoritmi sono ottimizzati per Fortran, i loro loop avranno come target colonne invece di righe. ( guarda qui ) Questo è quanto seriamente Fortran è ottimizzato per la matematica.

Un'ultima preoccupazione. Vuoi usare C ++ e non Fotran77. Perché puoi ottenere una migliore astrazione e progettazione? Bene, questa è una buona ragione, ma per favore, diffidare dell'ossessione del mercato con la programmazione orientata agli oggetti. È una buona cosa, ma non una pallottola d'argento. Ad esempio, una GUI dovrebbe essere sempre orientata agli oggetti, ma se fai molta matematica e muovi grandi quantità di memoria, uno stile orientato all'aspetto sarebbe bello. Ancora meglio per il tuo problema di dominio, dai un'occhiata a design orientato ai dati in alternativa alla progettazione orientata agli oggetti (e vedi un caso dove fallisce OOP)

    
risposta data 31.10.2011 - 22:01
fonte
12

Se dovessi ridurlo a una sola parola chiave, probabilmente sarebbe template . Sfortunatamente, l'applicazione di questo (per niente bene) richiederà qualche studio. Applicarlo nel miglior modo possibile (ad es. Usando modelli di espressioni) richiederà un bel po 'di studi.

Per alcune idee su come scrivere codice numerico competitivo in C ++, potresti voler esaminare alcune librerie esistenti come blitz ++ e Boost uBLAS .

    
risposta data 31.10.2011 - 21:53
fonte
10

Il tuo codice difficilmente diventerà magicamente più veloce da spruzzando alcune parole chiave qua e là . E semplicemente copiare gli algoritmi da una lingua all'altra (se è davvero quello che stai facendo) non sarà mai di aiuto. La velocità è correlata con

  1. un buon design e
  2. ha applicato selettivamente ottimizzazioni in base all'attenta profilazione .

Per quanto riguarda la progettazione: l'unica cosa che ha reso C ++ uguale alla prestazione di FORTRAN è stata l'invenzione di modelli di espressione di Todd Veldhuizen per la sua blitz ++ library .

    
risposta data 01.11.2011 - 15:57
fonte
9

La prima cosa da fare è il profilo, per vedere quale specifico pezzo di codice è responsabile del rallentamento.

Per quanto riguarda le parole chiave, inline potrebbe essere una buona da usare.

    
risposta data 31.10.2011 - 21:30
fonte
5

Una cosa da ricordare quando si esegue il porting da Fortran a C ++ è il diverso layout di array: Fortran, per impostazione predefinita, utilizza il layout principale della colonna, C e C ++ row-major. Quando il tuo programma si basa pesantemente sull'algebra lineare (come tutti i programmi Fortran sembrano fare), il diverso comportamento della cache farà la differenza.

Se questo sembra essere il problema (leggi: il tuo profiler mostra che stai spendendo molto tempo nell'algebra lineare), prova a usare una libreria LA decente come Eigen con matrici nel layout principale della colonna.

    
risposta data 01.11.2011 - 12:55
fonte
1

Queste sono buone risposte, ma è abbastanza probabile che entrambe le versioni di Fortran e C ++ contengano spazio sostanziale per l'accelerazione, quindi non ci si aspetterebbe che abbiano esattamente la stessa velocità ora.

Supponendo che uno dei due sia il più veloce possibile per una determinata macchina e il carico di input, esiste un infinito numerabile di programmi che eseguono il lavoro e alcuni programmi che impiegano meno tempo di tutti gli altri. Quello che vuoi fare è rimuovere le attività non necessarie fino a quando il programma si avvicina a quella brevità ottimale di tempo, indipendentemente dalla lingua.

Ecco come lo faccio , ed ecco un esempio di ottimizzazione delle prestazioni seriamente aggressiva .

Se posso solo dare esempi, in C ++ (usando MFC) questo è un caso in cui qualcosa di cui normalmente non ti preoccupi, l'indicizzazione dell'array, risulta costare il 40% delle volte e può essere fatto meglio. In un esempio ho usato i vettori di stl, il costo era ancora più alto.

Analogamente in Fortran, stavo sintonizzando un codice che utilizzava la libreria LAPACK, che si direbbe essere ottimizzato il più possibile. È probabile che lo sia, per le matrici di grandi dimensioni e un numero limitato di chiamate. Tuttavia, per le piccole matrici e un numero elevato di chiamate, indovina cosa stava spendendo una pesante frazione del suo tempo. Stava chiamando una funzione per fare confronti di stringhe per vedere quali erano le opzioni nel chiamare le funzioni matematiche. Ad esempio, DGEMM è una routine generale per fare moltiplicazione e amplifica matrix; scaling. I primi due argomenti sono i flag CHARACTER * 1 che indicano se una matrice di input viene trasposta. Per le piccole matrici, chiamare una funzione per testare tali flag è una percentuale maggiore del tempo e potrebbe essere chiaramente eliminata scrivendo una routine più specializzata.

L'unica generalità che puoi trarre da questi esempi è che hai bisogno di una diagnostica efficace (come la pausa casuale) per dire qual è il problema in ogni fase della messa a punto delle prestazioni. I problemi che troverai saranno indubbiamente diversi da questi esempi, ma in ogni caso l'unico modo per sapere cosa risolvere è avere il programma stesso che ti dice.

Una volta che ti sei liberato delle cose extra fatte nel programma che esiste per rendere più semplice una codifica generale non specificata, quindi puoi disattivare l'ottimizzatore del compilatore per eseguire il proprio livello di spremitura del ciclo (supponendo che la velocità sia la tua principale preoccupazione).

    
risposta data 01.11.2011 - 16:44
fonte
1

La maggior parte di esso sarà nel design della riscrittura. Ho fatto da F a C e da F a C ++ diverse volte per prodotti commerciali e i più grandi boogymen erano:

  • Fai attenzione alle operazioni di allocazione negli iteratori di libreria standard basati su nodo.
  • Elimina qualsiasi passaggio per riferimento (puntatori), ricorda che Fortran ha la regola di non-aliasing per i puntatori. C e C ++ no, quindi se si finisce con una funzione simile a questa:

    int some_function(int* a, int* b)
    {
    
         ...code...
         int some_intermediate_value = *a + *b;
         int other_value = *a + 7;
         ...and other code referencing b and a...
    
    }
    

... recupererà a e b ogni volta (a causa delle regole di aliasing). Risolvibile modificando la firma e gli usi delle funzioni, oppure copiando i temporari locali all'interno della funzione (utile quando si esegue un referee GRANDE in cui è difficile ottenere tante modifiche contemporaneamente).

    
risposta data 02.11.2011 - 01:45
fonte

Leggi altre domande sui tag