Quando dovrei usare i modelli di espressione C ++ nella scienza computazionale e quando * non * usarli?


24

Supponiamo che sto lavorando su un codice scientifico in C ++. In una recente discussione con un collega, è stato affermato che i modelli di espressione potrebbero essere davvero una brutta cosa, rendendo potenzialmente il software compilabile solo su alcune versioni di gcc. Presumibilmente, questo problema ha interessato alcuni codici scientifici, come indicato nei sottotitoli di questa parodia di Downfall . (Questi sono gli unici esempi che conosco, quindi il link.)

Tuttavia, altre persone hanno sostenuto che i modelli di espressione sono utili perché possono produrre miglioramenti delle prestazioni, come in questo articolo nel SIAM Journal of Scientific Computing , evitando la memorizzazione dei risultati intermedi in variabili temporanee.

Non so molto sulla metaprogrammazione dei modelli in C ++, ma so che si tratta di un approccio utilizzato nella differenziazione automatica e nell'aritmetica degli intervalli, ed è così che sono entrato in una discussione sui modelli di espressioni. Dati sia i potenziali vantaggi in termini di prestazioni sia i potenziali svantaggi nella manutenzione (se questa è anche la parola giusta), quando dovrei usare i modelli di espressione C ++ nella scienza computazionale e quando dovrei evitarli?


Ah, il video è troppo divertente. Non sapevo che esistesse. Chi l'ha fatto, lo sai?
Wolfgang Bangerth,

Nessuna idea; un paio di persone della PETSc mi hanno inviato link a un certo punto. Penso che uno sviluppatore FEniCS ce l'abbia fatta.
Geoff Oxberry,

Il collegamento video è interrotto e muoio dalla curiosità. Nuovo link?
Prassolitico

Oh drat, non importa, vedo che YouTube è arrivato per i nostri video di Hitler.
Prassolitico il

Risposte:


17

Il mio problema con i modelli di espressione è che sono un'astrazione che perde molto. Trascorri molto lavoro a scrivere codice molto complicato per svolgere un compito semplice con una sintassi migliore. Ma se vuoi cambiare l'algoritmo, devi pasticciare con il codice sporco e se scivoli con tipi o sintassi, ricevi messaggi di errore completamente incomprensibili. Se la tua applicazione si associa perfettamente a una libreria basata su modelli di espressioni, potrebbe valere la pena prendere in considerazione, ma se non sei sicuro, consiglierei di scrivere semplicemente il codice normale. Certo, il codice di alto livello è meno carino, ma puoi semplicemente fare ciò che deve essere fatto. Come vantaggio, il tempo di compilazione e le dimensioni binarie diminuiranno e non dovrai far fronte a una grande variazione nelle prestazioni a causa della scelta del compilatore e della bandiera di compilazione.


Sì, ho visto alcuni dei lunghi messaggi di errore in prima persona quando ho dovuto trasferire il codice da gcc 2.95 a gcc 4.x, e il compilatore stava lanciando ogni sorta di errori sui template. Un mio compagno di laboratorio sta sviluppando una libreria basata su modelli per l'aritmetica degli intervalli in C ++ (aggiungendo nuove funzionalità che non sono in Boost :: Interval per compiere ulteriori ricerche), e non voglio vedere il codice diventare un incubo compilare.
Geoff Oxberry,

12

Altri hanno commentato il problema di quanto sia difficile scrivere programmi ET e la complessità della comprensione dei messaggi di errore. Permettetemi di commentare il problema dei compilatori: è vero che qualche tempo fa uno dei grandi problemi era trovare un compilatore abbastanza conforme allo standard C ++ per far funzionare tutto e farlo funzionare in modo portabile. Di conseguenza, abbiamo trovato molti bug: ho 2-300 segnalazioni di bug nel mio nome, distribuite su gcc, Intel icc, IBM xlC e Portland pgicc. Di conseguenza, lo script di configurazione di deal.II è un repository di un gran numero di test di bug del compilatore, principalmente nell'area di template, dichiarazioni di amici, spazi dei nomi, ecc.

Ma si scopre che i produttori di compilatori hanno davvero messo insieme le loro azioni: oggi, gcc e icc oggi superano tutti i nostri test ed è facile scrivere codice che sia portatile tra loro. Direi che l'IGP non è molto indietro ma ha una serie di stranezze che non sembrano andare via nel corso degli anni. xlC, d'altra parte, è una storia completamente diversa: risolvono un bug ogni 6 mesi, ma nonostante la presentazione di segnalazioni di bug per anni, i progressi sono estremamente lenti e xlC non è mai stato in grado di compilare l'affare. II con successo.

Ciò significa tutto questo: se rimani con i due grandi compilatori, puoi aspettarti che funzionino solo oggi. Poiché la maggior parte dei computer e dei sistemi operativi oggi in genere ne ha almeno uno, è sufficiente. L'unica piattaforma in cui le cose sono più difficili è il BlueGene, in cui il compilatore del sistema è in genere xlC, con tutti i suoi bug.


Solo per curiosità, hai provato a compilare con i nuovi compilatori xlc su / Q?
Aron Ahmadia,

No. Devo ammettere di aver rinunciato a xlC.
Wolfgang Bangerth,

5

Ho sperimentato un po 'con ET molto tempo fa quando, come hai detto, i compilatori stavano ancora lottando con loro. Ho usato la libreria blitz per l'algebra lineare in un mio codice. Il problema quindi era ottenere il buon compilatore e poiché non sono un programmatore C ++ perfetto, interpretando i messaggi di errore del compilatore. Quest'ultimo era semplicemente ingestibile. Il compilatore genererebbe in media circa 1000 righe di messaggi di errore. In nessun modo sono stato in grado di trovare rapidamente il mio errore di programmazione.

Puoi trovare maggiori informazioni sulla pagina web oonumerics (ci sono gli atti di due seminari ET).

Ma starei lontano da loro ...


I messaggi di errore del compilatore sono davvero una delle mie preoccupazioni. Con parte del codice C ++ basato su modelli che compilo per creare librerie per i miei progetti, il compilatore può generare centinaia di righe di messaggi di avviso. Tuttavia, non è il mio codice, non lo capisco e, in generale, funziona, quindi lo lascio da solo. I messaggi di errore lunghi e criptici non promettono bene per il debug.
Geoff Oxberry,

4

Il problema inizia già con il termine "modelli di espressione (ET)". Non so se esiste una definizione precisa per questo. Ma nel suo uso comune in qualche modo accoppia "come si codificano le espressioni di algebra lineare" e "come viene calcolato". Per esempio:

Si codifica l'operazione vettoriale

v = 2*x + 3*y + 4*z;                    // (1)

E viene calcolato da un loop

for (int i=0; i<n; ++i)                 // (2)
    v(i) = 2*x(i) + 3*y(i) + 4*z(i);

A mio avviso si tratta di due cose diverse che devono essere disaccoppiate: (1) è un'interfaccia e (2) una possibile implementazione. Voglio dire, questa è una pratica comune in programmazione. Certo (2) potrebbe essere una buona implementazione predefinita, ma in generale voglio essere in grado di utilizzare un'implementazione specializzata e dedicata. Ad esempio, voglio che funzioni come

myGreatVecSum(alpha, x, beta, y, gamma, z, result);    // (3)

mi viene chiamato quando sto codificando (1). Forse (3) usa solo internamente un loop come in (2). Ma a seconda della dimensione del vettore altre implementazioni potrebbero essere più efficienti. Ad ogni modo, alcuni esperti di alte prestazioni possono implementare e ottimizzare (3) il più possibile. Quindi se (1) non può essere mappato su una chiamata di (3) allora evito piuttosto lo zucchero sintattico di (1) e richiamo direttamente (3) immediatamente.

Quello che descrivo non è una novità. Al contrario, è l'idea alla base di BLAS / LPACK:

  • Tutte le operazioni critiche per le prestazioni in LAPACK vengono eseguite chiamando le funzioni BLAS.
  • BLAS definisce semplicemente un'interfaccia per quelle espressioni di algebra lineare che sono comunemente necessarie.
  • Per BLAS esistono diverse implementazioni ottimizzate.

Se l'ambito di BLAS non è sufficiente (ad es. Non fornisce una funzione come (3)), si può estendere l'ambito di BLAS. Quindi questo dinosauro degli anni '60 e '70 realizza con il suo strumento dell'età della pietra una separazione pulita e ortogonale di interfaccia e implementazione. È strano che le librerie C ++ (la maggior parte) numeriche non raggiungano questo livello di qualità del software. Sebbene il linguaggio di programmazione stesso sia molto più sofisticato. Quindi non sorprende che BLAS / LAPACK sia ancora vivo e sviluppato attivamente.

Quindi secondo me gli ET non sono di per sé cattivi. Ma il modo in cui sono comunemente usati nelle librerie numeriche C ++ ha guadagnato loro una pessima reputazione nei circoli di informatica scientifica.


Michael, penso che ti manchi uno dei modelli di punti di espressione. Il tuo esempio di codice (1) in realtà non esegue il mapping a nessuna chiamata BLAS ottimizzata. In effetti, anche quando esiste una routine BLAS, l'overhead di una chiamata di funzione BLAS lo rende abbastanza terribile per piccoli vettori e matrici. Le sofisticate librerie di modelli di espressioni come Blaze ed Eigen possono utilizzare la valutazione dell'espressione differita per evitare l'uso di provvisori, ma sono convinto che quasi nulla di un linguaggio specifico del dominio sarà in grado di battere l'algebra lineare a rotazione manuale.
Aron Ahmadia,

No, penso che ti stia perdendo il punto. Devi distinguere tra (a) BLAS come una specifica di alcune operazioni di algebra lineare frequentemente necessarie (b) un'implementazione di BLAS come ATLAS, GotoBLAS, ecc. A proposito di come funziona in FLENS: per impostazione predefinita un'espressione come (1) sarebbe essere valutato chiamando axpy da BLAS tre volte. Ma senza modificare (1) potrei anche valutarlo come in (2). Quindi ciò che accade logicamente è il seguente: se un'operazione come in (1) è importante, è possibile estendere l'insieme di operazioni BLAS specificate (a).
Michael Lehn,

Quindi il punto chiave è: Notazione come 'v = x + y + z' e come viene finalmente calcolata dovrebbe essere separata. Eigen, MTL, BLITZ, blaze-lib falliscono completamente in questo senso.
Michael Lehn,

1
Esatto, ma il numero di operazioni di algebra lineare frequentemente necessarie è combinatorio. Se hai intenzione di usare un linguaggio come C ++, puoi scegliere se implementare secondo necessità le maschere di espressione (questo è l'approccio Eigen / Blaze) combinando blocchi secondari e algoritmi in modo intelligente usando una valutazione posticipata o implementando un enorme libreria di ogni possibile routine. Non sostengo nessuno dei due approcci, dato che i recenti lavori di Numba e Cython dimostrano che possiamo ottenere prestazioni simili o migliori lavorando con linguaggi di scripting di alto livello come Python.
Aron Ahmadia,

Ma ancora una volta, ciò di cui mi lamento è il fatto che librerie così sofisticate (nel senso di complicate ma inflessibili) come Eigen abbinano strettamente il meccanismo di notazione e valutazione e persino pensano che sia una buona cosa. Se uso uno strumento come Matlab, voglio solo codificare le cose e fare affidamento sul fatto che Matlab sta facendo la cosa migliore possibile. Se uso un linguaggio come C ++, voglio avere il controllo. Quindi apprezzare se esiste un meccanismo di valutazione predefinito ma deve essere possibile modificarlo. Altrimenti torno indietro e chiamo direttamente le funzioni in C ++.
Michael Lehn,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.