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?
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?
Risposte:
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.
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.
valarray
si 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
, gslice
e gslice_array
di 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 valarray
in grandi quantità, ci sono almeno alcune circostanze in cui può fornire un miglioramento della velocità.
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.
So che i valarrays hanno dello zucchero sintattico
Devo dire che non credo std::valarrays
abbia molto in termini di zucchero sintattico. La sintassi è diversa, ma non definirei la differenza "zucchero". L'API è strana. La sezione su std::valarray
s in Il linguaggio di programmazione C ++ menziona questa insolita API e il fatto che, poiché std::valarray
si 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::valarray
opposto 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::valarray
per 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 ( NOTA , seguendo i consigli del musiphil , sono riuscito a ottenere prestazioni quasi identiche da std::vector
gioco di grandi dimensioni siano migliori con le cache rispetto alle operazioni su std::valarray
s. vector
e valarray
).
Alla fine, ho deciso di utilizzare std::vector
prestando molta attenzione a cose come l'allocazione della memoria e la creazione temporanea di oggetti.
Entrambi std::vector
e std::valarray
archiviano i dati in un blocco contiguo. Tuttavia, accedono a tali dati utilizzando modelli diversi e, cosa più importante, l'API per std::valarray
incoraggia 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::slice
o 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::vector
esempio non crea una raccolta intermedia come std::valarray
nell'esempio. Tuttavia, penso che sia giusto confrontarli perché le differenze sono legate alle differenze tra std::vector
e std::valarray
.
Quando ho scritto questa risposta, sospettavo che sottrarre il valore degli elementi da due std::valarray
s (ultima riga std::valarray
nell'esempio) sarebbe meno compatibile con la cache della riga corrispondente std::vector
nell'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::vector
dell'esempio e ha prestazioni quasi identiche. Alla fine, la domanda è quale API preferisci.
std::vector
dovrebbe giocare meglio con le cache di a std::valarray
; entrambi allocano un singolo blocco contiguo di memoria per i loro elementi.
valarray
esempio sopra, non hai dovuto costruire un temp
valarray
oggetto, ma avresti potuto farlo std::valarray<double> differences_from_mean = original_values - mean;
e il comportamento della cache dovrebbe essere simile a quello vector
dell'esempio. (A proposito, se lo mean
è davvero int
, double
potresti averne bisogno static_cast<double>(mean)
.)
valarray
. Dovrò vedere se questo migliora le prestazioni. Per quanto riguarda l' mean
essere int
: è stato un errore. Inizialmente ho scritto l'esempio usando int
s, e poi ho capito che mean
sarebbe stato molto lontano dalla media reale a causa del troncamento. Ma ho perso alcune modifiche necessarie nel mio primo giro di modifiche.
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.
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);
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()
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.
Con std::valarray
te 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.
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.