Ottimizzazione del codice usando registri, ma cosa succede se non sono abbastanza in numero?

2

Un modo per ottimizzare il codice è minimizzare l'accesso agli array e utilizzare le variabili, perché in questo modo usiamo i registri invece di caricare i dati nella memoria cache.

Ad esempio, se in un ciclo userò 3 volte un dato da un array, per esempio raggio [i], farò meglio a dichiarare una variabile tmp e dargli il valore di raggio [i]. Ogni volta che ho bisogno di chiamare il raggio [i] userò invece tmp.

Il problema è che il numero di registri è limitato (a 8 in alcuni casi, dipende dall'architettura).

Cosa succede se ho più variabili del numero di registri? L'utilizzo di variabili in questo caso rallenterà il mio programma più dell'accesso alla memoria cache?

Sto codificando in c.

    
posta user2651062 07.08.2014 - 13:06
fonte

4 risposte

11

Avere più variabili rispetto ai registri non è necessariamente un problema. Se il valore di una variabile non viene utilizzato dopo un determinato punto della funzione, il compilatore può utilizzare quel registro per un'altra variabile. Anche quando ci sono più variabili in uso a un certo punto di quanto non ci siano registri, il compilatore farà probabilmente un lavoro migliore per capire l'ordine in cui devono essere spostati dentro e fuori dai registri rispetto a te.

Il punto di un linguaggio di alto livello è quello di astrarre quei dettagli. So che se programmi in C probabilmente ti preoccupi delle prestazioni, ma C è anche un linguaggio non sicuro, il che significa che un errore eliminerà l'esecuzione dai binari e determinerà un comportamento indefinito. Scrivi un codice semplice e facile da seguire. Dimentica quel tipo di micro-ottimizzazione a meno che tu non abbia un chiaro obiettivo di rendimento, hai profilato l'applicazione, confermato che non stai raggiungendo questo obiettivo e hai identificato la parte specifica del codice che deve essere ottimizzata. E in questo caso, non esiste una risposta definitiva che possiamo darti: dovrai provare diverse varianti del codice, profilo e vedere quale si finisce per essere più veloce nella tua particolare combinazione di compilatore, sistema operativo e architettura.

    
risposta data 07.08.2014 - 13:17
fonte
4

Questo è chiamato allocazione registro e il compilatore / ottimizzatore si prenderà cura di esso per te. Assicurati di attivare l'ottimizzazione. Se vuoi vedere cosa fa, basta compilare per assemblatore ... e poi imparare a leggere e capire l'assemblatore. La qualità dell'implementazione varierà, ma in generale non è necessario pensarci a meno che non si abbia un problema di prestazioni specifico.

L'unica volta che potrebbe valere la pena di riflettere su questo problema, è quando si sa che un array non può essere aliasato, ma sarà difficile per il compilatore dimostrarlo. Ad esempio,

double radius[3];
double *possible_alias = get_ptr();
...
for (int i=0; i<3; ++i) {
    *possible_alias *= radius[i];
    do_something_else(radius[i]);
}

se sai che l'assegnazione del puntatore non cambierà raggio [i], ma il compilatore potrebbe non essere in grado di fare affidamento su di esso, potresti scrivere

    double r = radius[i];
    *possible_alias *= r;
    do_something_else(r);

In generale, non preoccuparti di questo tipo di micro ottimizzazione a meno che non trovi uno specifico problema di prestazioni. Risolvi i tuoi sforzi per scrivere codice chiaro, facile da eseguire il debug e capire e scegliere le giuste strutture dati e algoritmi.

Per quanto riguarda come per profilare il tuo codice, questo è un argomento enorme e il supporto dipende dalla tua piattaforma e toolchain. Sono disponibili profilatori di campionamento compilati (come gprof, per la toolchain GCC), profiler post-hoc (come VTune di Intel e il valgrind gratuito), oppure è possibile solo il tempo di eseguire esecuzioni o scrivere manualmente i propri punti di temporizzazione. È improbabile che ne valga la pena, a meno che il tuo programma non sia oggettivamente troppo lento in primo luogo.

Nota per OP: la discussione nei commenti qui sotto riguarda le situazioni in cui la CPU può essere in grado di ottimizzare questo in fase di esecuzione, anche se il compilatore non può. Almeno su piattaforme x86 moderne, riduce ulteriormente il valore del pensiero su questo.

    
risposta data 07.08.2014 - 13:44
fonte
0

Sì, questo è vero per qualsiasi lingua, all'interno di una funzione o di uno spazio di codice confinato hai più cose in volo di registri per tenerli devi versare in memoria, e cadrai da una scogliera di prestazioni in quel punto.

Quando colpisci quel punto fino a che punto le prestazioni diminuiscono, ecc varia molto.

C'è un valore nella comprensione di questo ed è molto facile capirlo creando funzioni semplici, compilando e smontandole per vedere cosa sta realmente accadendo. Si vorrà utilizzare l'ottimizzatore in generale per evitare sempre solo un codice veramente lento, ma ciò amplifica il problema, poiché una sola operazione in una riga di codice può influire notevolmente sulle prestazioni. Perché, come stai chiedendo, colpirai l'operazione Nth che fa sì che il numero di registri necessari superi il numero se i registri liberi sono disponibili e ora devi riversare nella memoria. Allo stesso modo, raggiungi altri picchi di prestazioni quando le cache si riempiono o lo striping dei dati causa il thrash della cache, ecc.

Alla fine della giornata anche se questa è la natura dell'uso di linguaggi di alto livello, se hai intenzione di eseguire il porting di questo codice, puoi fare un po 'di tuning per evitare sbalzi di prestazioni palese su tutte le piattaforme, ma non hai intenzione di ottenere troppi benefici cercando di ottimizzare troppo per una piattaforma e possibilmente ferire gli altri. Se si dispone veramente di un problema di prestazioni, è necessario prima provare che si è verificato un problema di prestazioni relativo al codice in questione e quindi risolverlo utilizzando l'assembly per tale o qualsiasi target in cui tale codice sta causando un problema di prestazioni. E sì che crea un problema di leggibilità e manutenibilità.

In termini di leggibilità generale, manutenibilità, prestazioni, scegli due.

    
risposta data 07.08.2014 - 14:29
fonte
0

Ci sono buone risposte alla domanda che hai posto, ma è importante tenere presente che quando il software diventa più grande e più serio, questo tipo di ottimizzazione diventa sempre meno rilevante. Il motivo è che il programma traccia un albero delle chiamate, e il tempo viene principalmente speso nelle foglie, e le foglie hanno sempre meno probabilità di essere nel codice che il compilatore vede.

Invece, il tempo diventa sempre più probabile che venga speso nelle funzioni della libreria che il compilatore non vede, e alcuni di essi fanno I / O, che nessun compilatore può ottimizzare.

Un programma del genere può certamente essere ottimizzato, in gran parte, ma solo dal programmatore. Per fare ciò, è necessario un modo per dire cosa sta facendo il programma che potrebbe non essere strettamente necessario, come un sacco di allocazione di memoria. Ecco un esempio.

    
risposta data 13.08.2014 - 16:47
fonte

Leggi altre domande sui tag