2013-04-30 12 views
5

Ho la seguente funzione:È una buona ragione per usare alloca?

double 
neville (double xx, size_t n, const double *x, const double *y, double *work); 

che esegue l'interpolazione di Lagrange a xx base alle n punti memorizzati nel x e y. L'array work ha la dimensione 2 * n. Dal momento che questo è l'interpolazione polinomiale, n è nel campo da baseball di ~ 5, molto raramente più di 10.

Questa funzione è aggressivo ottimizzata, e dovrebbe essere chiamato in cicli stretti. La profilazione suggerisce che l'heap che alloca l'array di lavoro nel ciclo non è corretto. Sfortunatamente, dovrei comprimerlo in una classe simile alla funzione, e i client devono essere inconsapevoli della matrice di lavoro.

Per ora, utilizzare un argomento di template intero per il grado e std::array evitare l'allocazione dinamica della matrice work:

template <size_t n> 
struct interpolator 
{ 
    double operator() (double xx) const 
    { 
     std::array<double, 2 * n> work; 
     size_t i = locate (xx); // not shown here, no performance impact 
           // due to clever tricks + nice calling patterns 

     return neville (xx, n, x + i, y + i, work.data()); 
    }   

    const double *x, *y; 
}; 

Sarebbe stato possibile memorizzare la matrice lavoro come membro mutevole della classe, ma si suppone che operator() venga utilizzato contemporaneamente da più thread. Questa versione è OK se conosci n in fase di compilazione.

Ora, ho bisogno del parametro n da specificare in fase di esecuzione. Mi chiedo a qualcosa di simile:

double operator() (double xx) const 
{ 
    auto work = static_cast<double*> (alloca (n * sizeof (double))); 
    ... 

Alcuni campane suonano quando si utilizza alloca: sto ovviamente intenzione di avere un tetto n per evitare la chiamata alloca traboccare (in ogni caso è abbastanza stupido da usare grado 100 interpolazione polinomiale).

Sono abbastanza scomodo con l'approccio però:

  • Mi sto perdendo qualche pericolo evidente di alloca?
  • C'è un modo migliore per evitare l'allocazione dell'heap qui?
+2

Non si può semplicemente scrivere questa funzione in C e utilizzare i VLA C99? –

+1

@KonradRudolph 'double neville (double xx, size_t n, const double * x, const double * y, double * work);' - è necessario l'overloading dell'operatore per scrivere questa funzione? Wow, non l'ho mai saputo! –

+2

@ H2CO3 Hehe, mi ha preso. Bene, il mio ultimo argomento è che non mi piace il collegamento tra codice C e C++. Ovviamente non c'è alcun problema reale (se fatto correttamente! E ho incontrato molte librerie C che hanno sbagliato e mi ha causato molto dolore). Ma poi, non vedo alcun vantaggio nell'usare VLA su 'alloca' ma forse mi manca qualcosa ...? –

risposta

5

Sono abbastanza scomodo con l'approccio però:

  • Mi sto perdendo qualche pericolo evidente di alloca?

lei ha l'unico vero pericolo out: comportamento overflow dello stack non è definito per alloca. Inoltre, alloca non è in realtà standardizzato. Ad esempio, Visual C++ ha invece _alloca e GCC by default defines it as a macro. Questo problema può essere aggirato abbastanza facilmente, tuttavia, fornendo un involucro sottile attorno alle poche implementazioni esistenti.

  • C'è un modo migliore per evitare l'allocazione dell'heap qui?

Non proprio. C++ 14 avrà un (potenzialmente!) Tipo di array a lunghezza variabile assegnato allo stack.Ma fino ad allora, e se consideri che std::array non sia una buona idea, vai su alloca in casi come il tuo.

Minore nitpick però: nel codice manca un cast del valore restituito di alloca. Non dovrebbe nemmeno compilare.

+1

Sono ferito dai downvotes anonimi. Qualcuno ha intenzione di lenire le mie ferite? –

+0

Solo un'ipotesi sui downvotes: suggerire l'uso di funzionalità non standard, specialmente senza dire che (nella tua versione originale), e forse suggerire l'uso di ciò che è fondamentalmente una funzione di libreria C in C++ ha il potenziale per attirare downvotes puristi, e hai fatto entrambi . – hyde

2

ci sono sempre un sacco di note da aggiungere a qualsiasi uso della memoria stack. Come fai notare, le pile hanno dimensioni finite e comportamenti scorretti abbastanza gravi quando lo spazio si esaurisce. Si spera che l'overflow dello stack si arresti in modo anomalo se sono presenti pagine di protezione, ma su alcune piattaforme e ambienti di threading a volte può trattarsi di un danneggiamento non presidiato (danneggiato) o di un problema di sicurezza (peggio).

Ricordare inoltre che lo stack allocazione è molto veloce rispetto a malloc, (è solo una sottrazione dal registro puntatore dello stack). Ma lo utilizza la memoria di quella memoria potrebbe non esserlo. L'effetto collaterale di spingere la cornice dello stack verso il basso di grandi quantità è che le linee della cache delle funzioni foglia che stai per chiamare non sono più residenti. Pertanto, qualsiasi utilizzo di tale memoria deve passare all'ambiente SMP per riportare le linee cache in uno stato esclusivo (nello stato MESI). Il bus SMP è un ambiente molto (!) Più limitato rispetto alla cache L1 e, se si sta inviando spam ai propri stack, questo può rappresentare un vero problema di scalabilità.

Inoltre, per quanto riguarda la sintassi, si noti che sia gcc che clang (e il compilatore Intel credo) supportano la sintassi dell'array di lunghezza variabile C99 come estensione C++. Potrebbe non essere necessario chiamare effettivamente la routine libc alloca().

Infine, notare che malloc non è poi così lento. Se hai a che fare con singoli buffer del regno di dozzine di kilobyte o più grandi, la larghezza di banda della memoria necessaria per riparare qualsiasi lavoro tu stia facendo su di loro sta per inondare qualsiasi overhead da malloc.

Fondamentalmente: alloca() è carino, e ha i suoi usi, ma a meno che tu non abbia un benchmark pronto a dimostrare che ne hai bisogno, probabilmente non lo farai e dovresti semplicemente attenersi all'assegnazione tradizionale.

+1

Stai facendo particolari ipotesi sull'associatività della cache? Perché non vedo perché la memoria dinamica dovrebbe portare un numero inferiore di pagine nella cache - in effetti dovrebbe toccare molto di più, perché deve accedere alle strutture di dati interne dell'heap. Quindi è più probabile, e non meno, provocare l'espulsione delle pagine utilizzate dalle funzioni foglia. Se sei preoccupato per quelle pagine che non sono state nella cache per cominciare, non vedo perché. Nei programmi che fanno un uso pesante di allocazioni di stack di grandi dimensioni, quelle pagine di stack saranno calde nella cache. –

+0

La funzione 'neville' è piccola e non chiama nessuno. mallocando l'array di lavoro ogni volta il runtime del mio vero 'operator()' (con 'std :: array'), dice profiler. Anche 'lavoro' ha una dimensione di poche decine di byte al massimo. Grazie comunque per l'intuizione. –

+0

Ben: non è il footprint della cache, è lo stato della linea della cache. La memorizzazione o il caricamento da una linea cache L1 non richiede traffico al di fuori della CPU locale finché la linea si trova in uno stato E. Quindi chiamare una funzione foglia in cima a quelle linee può essere veloce, in cui non è possibile chiamare la stessa funzione dopo aver lasciato cadere 14k in pila. Invece, la CPU deve prima trasmettere l'operazione a tutte le altre CPU per consentire alla logica dello snoop di vederlo. Per le funzioni foglia rapidamente chiamate, questo può essere non banale. –

1

ne dite di questo:

double operator() (double xx) const 
{ 
    double work_static[STATIC_N_MAX]; 
    double* work = work_static; 
    std::vector<double> work_dynamic; 

    if (n > STATIC_N_MAX) { 
     work_dynamic.resize(n); 
     work = &work_dynamic[0]; 
    } 

    ///... 

Nessuna caratteristica non portatili, sicuro rispetto alle eccezioni, e degrada con grazia quando n è troppo grande. Naturalmente, è possibile effettuare work_static a std::array, ma non sono sicuro di quale vantaggio si possa vedere in questo.

+0

A volte mi sfugge l'ovvio ... Sto considerando di non accettare valori di 'n' maggiore di dire 20 (o qualche costante del preprocessore, le mie funzioni reali sono basate sul parametro' y', quindi il codice sorgente completo è disponibile). Questo vale la pena, se l'allocazione di '320' byte sullo stack ogni volta non peggiora le prestazioni. –

+0

@AlexandreC .: Si potrebbe immaginare un'implementazione di 'std :: vector' con una" ottimizzazione di stringa piccola "che avvolge questa bruttezza. –

+0

@AlexandreC. Se si pensa che l'allocazione dell'heap possa essere un collo di bottiglia, l'impatto della cache della CPU di uno array inutilmente grande nello stack potrebbe non essere desiderabile. Ma, benchmark sotto reale carico multithread. – hyde

Problemi correlati