C ++ valarray vs. vettore


159

Mi piacciono molto i vettori. Sono eleganti e veloci. Ma so che esiste una cosa chiamata valarray. Perché dovrei usare un valarray invece di un vettore? So che i valarrays hanno dello zucchero sintattico, ma a parte questo, quando sono utili?


2
Stavo meditando anche l'altro giorno. Per quanto ne so, è davvero altrettanto un vettore matematico specializzato.
GManNickG,

Valarray non fa i modelli di espressione?
Mooing Duck il

Il fisico Ulrich Mutze fornisce un caso d'uso per valarray qui e qui
equilibrio di vita

Risposte:


70

Valarrays (array di valori) hanno lo scopo di portare parte della velocità di Fortran a C ++. Non faresti una valarray di puntatori in modo che il compilatore possa fare ipotesi sul codice e ottimizzarlo meglio. (Il motivo principale per cui Fortran è così veloce è che non esiste un tipo di puntatore, quindi non può esserci alcun alias puntatore.)

Valarrays ha anche delle classi che ti permettono di suddividerle in un modo ragionevolmente semplice, sebbene quella parte dello standard potrebbe usare un po 'più di lavoro. Il ridimensionamento è distruttivo e mancano di iteratori.

Quindi, se si tratta di numeri con cui si sta lavorando e la convenienza non è poi così importante utilizzare valarrays. Altrimenti, i vettori sono solo molto più convenienti.


11
Non sono progettati per evitare puntatori. C ++ 11 definisce begin () e end () in valarray che restituiscono loro gli iteratori
Mohamed El-Nakib

3
@ user2023370: ecco perché così tanti utenti Fortran preferiscono Fortran 77. :)
Michael,

152

valarrayè una specie di orfano che è nato nel posto sbagliato al momento sbagliato. È un tentativo di ottimizzazione, abbastanza specificamente per le macchine utilizzate per la matematica pesante quando è stata scritta, in particolare i processori vettoriali come il Crays.

Per un processore vettoriale, ciò che generalmente volevi fare era applicare una singola operazione a un intero array, quindi applicare l'operazione successiva all'intero array e così via fino a quando non avessi fatto tutto ciò che dovevi fare.

A meno che tu non abbia a che fare con array abbastanza piccoli, ciò tende a funzionare male con la memorizzazione nella cache. Sulla maggior parte delle macchine moderne, ciò che generalmente preferiresti (per quanto possibile) sarebbe caricare parte dell'array, fare tutte le operazioni su di esso che stai per fare, quindi passare alla parte successiva dell'array.

valarraysi suppone inoltre di eliminare ogni possibilità di aliasing, che (almeno teoricamente) consente al compilatore di migliorare la velocità perché è più gratuito memorizzare valori nei registri. In realtà, tuttavia, non sono affatto sicuro che qualsiasi reale implementazione ne tragga vantaggio in misura significativa. Ho il sospetto che sia piuttosto una specie di problema a base di gallina e uovo - senza il supporto del compilatore non è diventato popolare, e finché non è popolare, nessuno si preoccuperà di lavorare sul proprio compilatore per sostenerlo.

C'è anche una sconcertante (letteralmente) matrice di classi accessorie da usare con valarray. Si ottiene slice, slice_array, gslicee gslice_arraydi giocare con pezzi di una valarray, e fare agire come un array multi-dimensionale. Puoi anche mask_array"mascherare" un'operazione (ad es. Aggiungi elementi da x a y, ma solo nelle posizioni in cui z è diverso da zero). Per fare un uso più che banale valarray, devi imparare molto su queste classi accessorie, alcune delle quali sono piuttosto complesse e nessuna delle quali sembra (almeno per me) molto ben documentata.

In conclusione: mentre ha momenti di brillantezza e può fare alcune cose abbastanza bene, ci sono anche delle ottime ragioni per cui è (e quasi sicuramente rimarrà) oscuro.

Modifica (otto anni dopo, nel 2017): alcuni dei precedenti sono diventati obsoleti almeno in una certa misura. Ad esempio, Intel ha implementato una versione ottimizzata di valarray per il proprio compilatore. Utilizza Intel Integrated Performance Primitives (Intel IPP) per migliorare le prestazioni. Sebbene l'esatto miglioramento delle prestazioni vari senza dubbio, un test rapido con un semplice codice mostra un miglioramento della velocità di 2: 1, rispetto al codice identico compilato con l'implementazione "standard" di valarray.

Quindi, anche se non sono del tutto convinto che i programmatori C ++ inizieranno a usare valarrayin grandi quantità, ci sono almeno alcune circostanze in cui può fornire un miglioramento della velocità.


1
È espressamente vietato memorizzare tipi di oggetti arbitrari all'interno di valarray?
user541686

6
@Mehrdad: Sì - c'è un elenco (piuttosto lungo) di restrizioni in [Numeric.Requirements]. Solo per un paio di esempi, sono vietate tutte le classi astratte ed eccezioni. Richiede anche l'equivalenza tra (ad esempio) la costruzione della copia e una sequenza di costruzione predefinita seguita da assegnazione.
Jerry Coffin,

@JerryCoffin sheesh è spaventoso. promettiamo che non lo useremo.
Hani Goc,

4
Non lo deciderei in base alla paura. Lo deciderei in base alla necessità di memorizzare elementi che utilizzano funzionalità vietate.
Jerry Coffin,

3
@annoying_squid: se hai informazioni più specifiche e (ritieni) accurate da aggiungere, non esitare ad aggiungere una risposta che le mostri. Allo stato attuale, tuttavia, il tuo commento non sembra aggiungere alcuna informazione utile.
Jerry Coffin,

39

Durante la standardizzazione di C ++ 98, valarray è stato progettato per consentire una sorta di calcoli matematici veloci. Tuttavia, in quel periodo Todd Veldhuizen inventò modelli di espressioni e creò blitz ++ e vennero inventate tecniche simili di modelli-meta, il che rese Valarrays praticamente obsoleto prima ancora che venisse pubblicato lo standard. IIRC, il / i proponente / i originale / i di Valarray lo ha abbandonato a metà strada verso la standardizzazione, che (se vera) non ha aiutato neanche.

ISTR afferma che il motivo principale per cui non è stato rimosso dallo standard è che nessuno ha impiegato del tempo per valutare a fondo il problema e scrivere una proposta per rimuoverlo.

Tieni presente, tuttavia, che tutto ciò è vagamente ricordato per sentito dire. Prendi questo con un granello di sale e spera che qualcuno lo corregga o lo confermi.


i modelli di espressione possono essere ugualmente accreditati anche su Vandevoorde, giusto?
Nikos Athanasiou,

@Nikos: Non che io sappia. Potrei sbagliarmi però. Che cosa hai a favore di quella lettura?
sbi,

1
è menzionato nel libro "Modelli C ++ - La guida completa", penso che sia generalmente accettato che entrambi li abbiano inventati indipendentemente .
Nikos Athanasiou,

27

So che i valarrays hanno dello zucchero sintattico

Devo dire che non credo std::valarraysabbia molto in termini di zucchero sintattico. La sintassi è diversa, ma non definirei la differenza "zucchero". L'API è strana. La sezione su std::valarrays in Il linguaggio di programmazione C ++ menziona questa insolita API e il fatto che, poiché std::valarraysi prevede che i messaggi di posta elettronica siano altamente ottimizzati, qualsiasi messaggio di errore che si ottiene durante il loro utilizzo sarà probabilmente non intuitivo.

Per curiosità, circa un anno fa mi sono std::valarrayopposto std::vector. Non ho più il codice o i risultati precisi (anche se non dovrebbe essere difficile scrivere il tuo). Utilizzando GCC ho fatto ottenere un po 'di miglioramento delle prestazioni quando si utilizza std::valarrayper la matematica semplice, ma non per i miei implementazioni per calcolare la deviazione standard (e, naturalmente, la deviazione standard non è quel complesso, per quanto riguarda la matematica va). Ho il sospetto che le operazioni su ciascun elemento in un std::vectorgioco di grandi dimensioni siano migliori con le cache rispetto alle operazioni su std::valarrays. ( NOTA , seguendo i consigli del musiphil , sono riuscito a ottenere prestazioni quasi identiche da vectore valarray).

Alla fine, ho deciso di utilizzare std::vectorprestando molta attenzione a cose come l'allocazione della memoria e la creazione temporanea di oggetti.


Entrambi std::vectore std::valarrayarchiviano i dati in un blocco contiguo. Tuttavia, accedono a tali dati utilizzando modelli diversi e, cosa più importante, l'API per std::valarrayincoraggia modelli di accesso diversi rispetto all'API per std::vector.

Per l'esempio di deviazione standard, in una fase particolare avevo bisogno di trovare la media della raccolta e la differenza tra il valore di ciascun elemento e la media.

Per il std::valarray, ho fatto qualcosa del tipo:

std::valarray<double> original_values = ... // obviously I put something here
double mean = original_values.sum() / original_values.size();
std::valarray<double> temp(mean, original_values.size());
std::valarray<double> differences_from_mean = original_values - temp;

Potrei essere stato più intelligente con std::sliceo std::gslice. Sono passati più di cinque anni.

Per std::vector, ho fatto qualcosa sulla falsariga di:

std::vector<double> original_values = ... // obviously, I put something here
double mean = std::accumulate(original_values.begin(), original_values.end(), 0.0) / original_values.size();

std::vector<double> differences_from_mean;
differences_from_mean.reserve(original_values.size());
std::transform(original_values.begin(), original_values.end(), std::back_inserter(differences_from_mean), std::bind1st(std::minus<double>(), mean));

Oggi lo scriverei sicuramente diversamente. Se non altro, approfitterei di C ++ 11 lambdas.

È ovvio che questi due frammenti di codice fanno cose diverse. Per uno, l' std::vectoresempio non crea una raccolta intermedia come std::valarraynell'esempio. Tuttavia, penso che sia giusto confrontarli perché le differenze sono legate alle differenze tra std::vectore std::valarray.

Quando ho scritto questa risposta, sospettavo che sottrarre il valore degli elementi da due std::valarrays (ultima riga std::valarraynell'esempio) sarebbe meno compatibile con la cache della riga corrispondente std::vectornell'esempio (che sembra essere anche l'ultima riga).

Si scopre, tuttavia, che

std::valarray<double> original_values = ... // obviously I put something here
double mean = original_values.sum() / original_values.size();
std::valarray<double> differences_from_mean = original_values - mean;

Fa la stessa cosa std::vectordell'esempio e ha prestazioni quasi identiche. Alla fine, la domanda è quale API preferisci.


Non riesco a pensare a nessun motivo per cui a std::vectordovrebbe giocare meglio con le cache di a std::valarray; entrambi allocano un singolo blocco contiguo di memoria per i loro elementi.
musiphil,

1
@musiphil La mia risposta è durata troppo a lungo per un commento, quindi ho aggiornato la risposta.
Max Lybbert,

1
Per il tuo valarrayesempio sopra, non hai dovuto costruire un temp valarrayoggetto, ma avresti potuto farlo std::valarray<double> differences_from_mean = original_values - mean;e il comportamento della cache dovrebbe essere simile a quello vectordell'esempio. (A proposito, se lo meanè davvero int, doublepotresti averne bisogno static_cast<double>(mean).)
musiphil

Grazie per il suggerimento di ripulire il file valarray. Dovrò vedere se questo migliora le prestazioni. Per quanto riguarda l' meanessere int: è stato un errore. Inizialmente ho scritto l'esempio usando ints, e poi ho capito che meansarebbe stato molto lontano dalla media reale a causa del troncamento. Ma ho perso alcune modifiche necessarie nel mio primo giro di modifiche.
Max Lybbert,

@musiphil Hai ragione; tale modifica ha portato il codice di esempio a prestazioni quasi identiche.
Max Lybbert,

23

valarray avrebbe dovuto lasciare un po 'di bontà di elaborazione vettoriale FORTRAN cancellarsi dal C ++. In qualche modo il supporto del compilatore necessario non è mai realmente accaduto.

I libri di Josuttis contengono alcuni interessanti commenti (in qualche modo denigratori) su Valarray ( qui e qui ).

Tuttavia, Intel ora sta rivisitando valarray nelle recenti versioni del compilatore (ad esempio, vedere la diapositiva 9 ); questo è uno sviluppo interessante dato che il loro set di istruzioni SSD SIMD a 4 vie sta per essere affiancato da istruzioni AVX a 8 vie e Larrabee a 16 vie e nell'interesse della portabilità sarà probabilmente molto meglio codificare con un'astrazione come valarray di (diciamo) intrinseci.


16

Ho trovato un buon utilizzo per valarray. Usare valarray proprio come array intorpiditi.

auto x = linspace(0, 2 * 3.14, 100);
plot(x, sin(x) + sin(3.f * x) / 3.f + sin(5.f * x) / 5.f);

inserisci qui la descrizione dell'immagine

Possiamo implementare sopra con valarray.

valarray<float> linspace(float start, float stop, int size)
{
    valarray<float> v(size);
    for(int i=0; i<size; i++) v[i] = start + i * (stop-start)/size;
    return v;
}

std::valarray<float> arange(float start, float step, float stop)
{
    int size = (stop - start) / step;
    valarray<float> v(size);
    for(int i=0; i<size; i++) v[i] = start + step * i;
    return v;
}

string psstm(string command)
{//return system call output as string
    string s;
    char tmp[1000];
    FILE* f = popen(command.c_str(), "r");
    while(fgets(tmp, sizeof(tmp), f)) s += tmp;
    pclose(f);
    return s;
}

string plot(const valarray<float>& x, const valarray<float>& y)
{
    int sz = x.size();
    assert(sz == y.size());
    int bytes = sz * sizeof(float) * 2;
    const char* name = "plot1";
    int shm_fd = shm_open(name, O_CREAT | O_RDWR, 0666);
    ftruncate(shm_fd, bytes);
    float* ptr = (float*)mmap(0, bytes, PROT_WRITE, MAP_SHARED, shm_fd, 0);
    for(int i=0; i<sz; i++) {
        *ptr++ = x[i];
        *ptr++ = y[i];
    }

    string command = "python plot.py ";
    string s = psstm(command + to_string(sz));
    shm_unlink(name);
    return s;
}

Inoltre, abbiamo bisogno dello script Python.

import sys, posix_ipc, os, struct
import matplotlib.pyplot as plt

sz = int(sys.argv[1])
f = posix_ipc.SharedMemory("plot1")
x = [0] * sz
y = [0] * sz
for i in range(sz):
    x[i], y[i] = struct.unpack('ff', os.read(f.fd, 8))
os.close(f.fd)
plt.plot(x, y)
plt.show()

2
Avevo letteralmente gli stessi pensieri che hai fatto quando ho scoperto Valarray oggi al lavoro. Penso che d'ora in poi per problemi di elaborazione matematica in c ++ userò valarray poiché il codice sembra molto più semplice da comprendere da una prospettiva matematica.
Zachary Kraus,

8

Lo standard C ++ 11 dice:

Le classi di array valarray sono definite libere da alcune forme di aliasing, consentendo così di ottimizzare le operazioni su queste classi.

Vedi C ++ 11 26.6.1-2.


Dal momento che presumo che lo Standard definisca quali moduli, puoi citarli? Inoltre, questi sono implementati usando trucchi di codifica o sono eccezioni basate sul compilatore alle regole di aliasing altrove nella lingua?
underscore_d

2

Con std::valarrayte puoi usare la notazione matematica standard come pronta all'uso v1 = a*v2 + v3. Questo non è possibile con i vettori se non si definiscono i propri operatori.


0

std :: valarray è destinato a compiti numerici pesanti, come la fluidodinamica computazionale o la dinamica della struttura computazionale, in cui si hanno array con milioni, a volte decine di milioni di elementi, e si scorre su di essi in un ciclo con anche milioni di timestep. Forse oggi std :: vector ha prestazioni comparabili ma, circa 15 anni fa, valarray era quasi obbligatorio se si voleva scrivere un risolutore numerico efficiente.

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.