2009-06-16 14 views
8

Ho un'implementazione di una classe X, che ha due puntatori a due informazioni. Ho scritto una nuova implementazione, classe Y, che ha un solo puntatore a una struttura che contiene le due informazioni insieme come membri adiacenti. I metodi di X e Y di solito hanno solo bisogno di manipolare una delle informazioni, ma forniscono un metodo get() che restituisce un puntatore al secondo pezzo (in questo caso la classe X restituisce semplicemente il puntatore a quel pezzo e la classe Y restituisce l'indirizzo del secondo membro della struttura). Nell'uso normale, le chiamate ai metodi di X e Y si alternano tra le chiamate a get() e il lavoro su quel secondo pezzo restituito.C++, modi per confrontare i miglioramenti nella localizzazione della cache?

Mi aspetto che nelle situazioni di vita reale ci dovrebbe essere un miglioramento delle prestazioni, ora che le due parti di informazioni sono l'una accanto all'altra in memoria nell'implementazione di classe Y (perché sono membri adiacenti di una struttura), ma io Non vedo alcuna differenza nei benchmark che ho scritto (intercalando le chiamate ai metodi di X e Y con il lavoro sui loro secondi pezzi in grandi loop). Ho il sospetto che ciò avvenga perché tutto si adatta alla cache in entrambi i casi nei miei test. Non voglio ancora provare questo nella mia app reale perché la semantica di X e Y si differenziano in altri modi sottili non correlati a questa ottimizzazione e il porting dell'applicazione usando sarà un po 'di lavoro, e questi benchmark dovrebbero aiutare a giustificare il fatto lavorare in primo luogo.

Qual è il modo migliore per osservare la differenza di prestazioni a causa di una migliore localizzazione della cache? Se faccio un mucchio di lavoro fittizio su un array uguale alla dimensione della cache tra una chiamata e l'altra è sufficiente? O voglio lavorare su un array leggermente meno della dimensione della cache, in modo che il lavoro sulle mie istanze della mia classe causi l'ingresso e l'uscita dalla cache? Non sono sicuro di come codificare qualcosa che sia robusto contro le ottimizzazioni del compilatore e le diverse dimensioni della cache.

risposta

0

Se sto capendo correttamente la situazione (e, per favore, correggimi se no), allora sono sei di uno, o mezza dozzina di altri.

Nella classe X, è necessaria una ricerca puntatore per ciascuna informazione. Nella classe Y, hai bisogno di una ricerca per il primo e due (ottieni il primo e poi offset) per il secondo. Questo sta sacrificando "locality" per un altro accesso alla memoria. I compilatori sono ancora, sfortunatamente, molto bravi a sprecare tempo per cercare le parole nella RAM.

Se è possibile, otterrete i risultati migliori tenendo i due pezzi di informazioni di destinazione direttamente all'interno della classe in questione (vale a dire ognuno di loro è membro della classe), piuttosto che utilizzare quei puntatori per indiretta non necessaria. Non vedendo alcun codice, questo è praticamente tutto quello che posso dire.

In ogni caso, otterrete un lotto lotto di prestazioni più di studiare la complessità algoritmica della vostra applicazione di quanto lo sarà mai con micro-ottimizzazione di due variabili in una definizione di classe. Inoltre, una buona idea è usare uno strumento di profilazione per vedere (oggettivamente) dove sono i colli di bottiglia (gprof è comune sui sistemi * nix). C'è una precisa ragione per cui stai cercando di aumentare specificatamente il caching delle località?

+0

'Perché' non è davvero il problema qui - la domanda è abbastanza chiaramente per valutare la localizzazione della cache. Non credo che "perché" aggiunga davvero qualcosa alla discussione, e il suo meglio è supporre che Joseph sappia quello che sta facendo. – Justicle

+0

Il "perché" è sempre importante, almeno IMHO. "Mi aspetto che nelle situazioni della vita reale ci dovrebbe essere un miglioramento delle prestazioni" che mi dice che Joseph sta cercando di accelerare le cose. "Non voglio ancora provarlo nella mia vera app", il che suggerisce ancora più pesantemente che il suo obiettivo finale è una performance migliore, e sta cercando di farlo attraverso una località migliorata - ecco perché ho consigliato altri corsi per migliorare le prestazioni. Comunque, @Joseph, se sono andato nella direzione sbagliata qui, per favore ignora. ;-) [E in tal caso, cachegrind è quello che vuoi] –

+0

Sto scrivendo una classe puntatore intelligente che è fondamentalmente senza algoritmo. L'ho ottimizzato con g-prof fino al punto in cui cose come se un ramo esiste (un if) o un attributo intero spuria possono determinare se la mia classe batte la vecchia implementazione. Questo è uno dei pochi casi in cui si applicano sicuramente le micro-ottimizzazioni;) –

8

Se si utilizza Linux, l'utilizzo di Cachegrind insieme a KCacheGrind potrebbe fornire informazioni più dettagliate sul comportamento della cache.

2

È possibile progettare un benchmark specifico per bloccare la cache. Ad esempio, allocare i blocchi di dati puntati in modo che siano tutti garantiti su linee di cache diverse (ad esempio, utilizzando un allocatore di memoria personalizzato che estrae le allocazioni per almeno alcune centinaia di byte). Quindi ripetete ripetutamente su un numero di oggetti troppo grande per adattarsi a tutto anche nella cache L2 (molto dipendente dalla piattaforma, poiché dipende dal numero di righe nella cache, ma 1 milione coprirebbe la maggior parte delle architetture e richiederà solo poche centinaia di mega RAM totale).

Questo vi darà un limite superiore al guadagno di prestazioni causato dal passaggio da X a Y. Ma lo fa degradando le prestazioni di X in basso a qualsiasi probabile utilizzo del mondo reale. E per dimostrare il tuo caso hai bisogno di una stima del limite inferiore, non di una stima del limite superiore. Quindi non sono sicuro che otterresti molto, a meno che non scoprirai che anche questo caso peggiore non fa ancora nessuna differenza significativa e non devi preoccuparti dell'ottimizzazione.

Anche se non si mira alle prestazioni teoriche del caso peggiore di X, qualsiasi benchmark progettato per superare la cache è solo scegliere un punto arbitrario di prestazioni scadenti di X e cercare di vedere se Y è migliore. Non è lontano da rigging il punto di riferimento per rendere Y un bell'aspetto. In realtà non importa come il tuo codice funziona in benchmark scomodi, tranne forse per scopi di marketing bugie letteratura.

Il modo migliore per osservare la differenza nel mondo reale delle prestazioni è misurare un cliente del mondo reale della classe. Tu dici che "la semantica di X e Y differisce in altri modi sottili non correlati a questa ottimizzazione", nel qual caso posso solo raccomandare di scrivere una classe Z che differisce da X solo in relazione a questa ottimizzazione, e usare quello nella tua applicazione come il confronto.

Una volta che i test tentano di rappresentare il peggior utilizzo realistico, se non si notano differenze nelle prestazioni probabilmente non si ottiene alcun guadagno in termini di prestazioni.

Tutto ciò detto, se ha senso logico (cioè, non rende il codice più stupefacente), quindi vorrei sostenere minimizzare il numero di allocazioni di heap in C++ semplicemente come regola empirica. Non tende a peggiorare la velocità o l'utilizzo della memoria totale e tende a semplificare la gestione delle risorse. Una regola empirica non giustifica una riscrittura del codice di lavoro, ovviamente.

Problemi correlati