Question

J'aime beaucoup les vecteurs. Ils sont chouettes et rapides. Mais je sais que cette chose appelée un valarray existe. Pourquoi devrais-je utiliser un valarray au lieu d'un vecteur? Je sais que les valarrays ont du sucre syntaxique, mais à part ça, quand sont-ils utiles?

Était-ce utile?

La solution

Les Valarrays (tableaux de valeurs) sont destinés à amener une partie de la vitesse de Fortran au C ++. Vous ne feriez pas une liste de pointeurs afin que le compilateur puisse faire des hypothèses sur le code et l'optimiser au mieux. (La raison principale pour laquelle Fortran est si rapide est qu’il n’existe aucun type de pointeur et qu’il ne peut donc y avoir de pseudonyme.)

Valarrays a également des classes qui vous permettent de les découper de manière assez simple bien que cette partie de la norme puisse nécessiter un peu plus de travail. Leur redimensionnement est destructeur et il leur manque des itérateurs.

Donc, s’il s’agit de chiffres avec lesquels vous travaillez et que la commodité n’est pas si importante, utilisez des tableaux de valeur. Sinon, les vecteurs sont beaucoup plus pratiques.

Autres conseils

valarray est en quelque sorte un orphelin né au mauvais endroit au mauvais moment. C’est une tentative d’optimisation, assez spécifiquement pour les machines qui étaient utilisées pour les mathématiques lourdes lorsqu’il a été écrit - en particulier, les processeurs vectoriels comme le Crays.

Pour un processeur vectoriel, vous vouliez généralement appliquer une opération unique à un tableau entier, puis appliquer l'opération suivante à l'ensemble du tableau, et ainsi de suite jusqu'à ce que vous ayez accompli tout ce que vous deviez faire.

Sauf si vous avez affaire à des baies assez petites, cependant, cela a tendance à mal fonctionner avec la mise en cache. Sur la plupart des machines modernes, ce que vous préférez généralement (dans la mesure du possible) consiste à charger une partie du tableau, à effectuer toutes les opérations sur le projet, puis à passer à la partie suivante du tableau.

valarray est également supposé éliminer toute possibilité de repliement d’alias, ce qui (du moins théoriquement) permet au compilateur d’accroître la vitesse car il est plus libre de stocker des valeurs dans des registres. En réalité, cependant, je ne suis pas du tout certain qu'une réelle mise en œuvre en tire avantage de manière significative. Je suppose que c'est plutôt un problème de type œuf et poule - sans le support du compilateur, il n'est pas devenu populaire et tant que ce n'est pas populaire, personne ne va avoir la peine de travailler sur leur compilateur pour le prendre en charge.

Il existe également une gamme déconcertante (littéralement) de classes auxiliaires à utiliser avec valarray. Vous obtenez tranche , slice_array , gslice et gslice_array pour jouer avec les éléments d'un valarray , et le faire agir comme un tableau multi-dimensionnel. Vous obtenez également mask_array en " masque " une opération (par exemple, ajouter des éléments de x à y, mais uniquement aux positions où z est différent de zéro). Pour utiliser plus que trivialement valarray , vous devez en apprendre beaucoup sur ces classes auxiliaires, dont certaines sont assez complexes et aucune d’entre elles ne semble (du moins pour moi) très bien documentée.

En résumé: bien qu’il ait des moments de brillance et qu’il puisse faire certaines choses de façon ordonnée, il existe aussi de très bonnes raisons pour lesquelles il est (et restera presque certainement) obscur.

Edit (huit ans plus tard, en 2017): Certaines des précédentes sont devenues obsolètes à au moins un certain degré. Par exemple, Intel a implémenté une version optimisée de valarray pour son compilateur. Il utilise les Intel Integrated Performance Primitives (Intel IPP) pour améliorer les performances. Bien que l’amélioration exacte des performances varie sans aucun doute, un test rapide avec un code simple montre une amélioration de la vitesse d’environ 2: 1, par rapport à un code identique compilé avec le code "standard". implémentation de valarray .

Ainsi, même si je ne suis pas entièrement convaincu que les programmeurs C ++ vont commencer à utiliser valarray , il existe au moins certaines circonstances dans lesquelles il peut fournir une amélioration de la vitesse.

Au cours de la normalisation de C ++ 98, valarray a été conçu pour permettre une sorte de calcul mathématique rapide. Cependant, à peu près à cette époque, Todd Veldhuizen a inventé les modèles d’expression et créé des blitz ++ , ainsi que des techniques similaires de modèle-méta. , ce qui a rendu valarrays quasiment obsolète avant même que le standard ne soit publié. Le IIRC, le proposant initial de valarray l’a abandonné à mi-chemin de la normalisation, ce qui (si vrai) ne l’a pas aidé non plus.

ISTR que la raison principale pour laquelle il n'a pas été supprimé de la norme est que personne n'a pris le temps de bien évaluer le problème et de rédiger une proposition pour le supprimer.

Veuillez garder à l'esprit, cependant, que tout cela est un souvenir approximatif. Prenez ceci avec un grain de sel et espérez que quelqu'un le corrige ou le confirme.

  

Je sais que les Valarrays ont du sucre syntaxique

Je dois dire que je ne pense pas que std :: valarrays ait beaucoup de sucre syntaxique. La syntaxe est différente, mais je n’appellerais pas la différence "sucre". L'API est bizarre. La section sur std :: valarray dans Le langage de programmation C ++ mentionne cette API inhabituelle et le fait que, depuis std :: valarray , devrait être hautement optimisé, les messages d'erreur que vous obtiendrez en les utilisant ne seront probablement pas intuitifs.

Par curiosité, il y a environ un an, j'ai opposé std :: valarray à std :: vector . Je n'ai plus le code ni les résultats précis (bien qu'il ne soit pas difficile d'écrire le vôtre). En utilisant GCC, j'ai obtenu un petit avantage en termes de performances lorsque j'utilisais std :: valarray pour des calculs simples, mais pas pour que mes implémentations calculent l'écart-type (et, bien sûr, l'écart-type n’est-ce pas si complexe en maths). Je soupçonne que les opérations sur chaque élément d'un std :: vector volent mieux avec des caches que les opérations sur std :: valarray . (< strong> REMARQUE , en suivant les conseils de musiphil , j'ai obtenu des performances presque identiques de . vecteur et valarray ).

À la fin, j'ai décidé d'utiliser std :: vector tout en accordant une attention particulière à des éléments tels que l'allocation de mémoire et la création d'objets temporaires.

std :: vector et std :: valarray stockent les données dans un bloc contigu. Cependant, ils accèdent à ces données à l'aide de modèles différents et, plus important encore, l'API pour std :: valarray encourage des modèles d'accès différents de ceux de l'API pour std :: vector .

Pour l'exemple d'écart-type, à une étape particulière, je devais trouver la moyenne de la collection et la différence entre la valeur de chaque élément et la moyenne.

Pour std :: valarray , j’ai fait quelque chose comme:

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;

J'ai peut-être été plus intelligent avec std :: slice ou std :: gslice . Cela fait plus de cinq ans maintenant.

Pour std :: vector , j'ai fait quelque chose dans le sens de:

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));

Aujourd'hui, j'écrirais certainement cela différemment. Si rien d’autre, je tirerais parti des lambdas C ++ 11.

Il est évident que ces deux extraits de code font des choses différentes. D'une part, l'exemple std :: vector ne crée pas une collection intermédiaire comme le fait l'exemple std :: valarray . Cependant, je pense qu'il est juste de les comparer car les différences sont liées aux différences entre std :: vector et std :: valarray .

Quand j'ai écrit cette réponse, je pensais que soustraire la valeur des éléments de deux exemples std :: valarray (dernière ligne de l'exemple std :: valarray ) aurait être moins convivial en cache que la ligne correspondante dans l'exemple std :: vector (qui se trouve être également la dernière ligne).

Il s'avère cependant que

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;

Fait la même chose que l'exemple std :: vector et a des performances presque identiques. Au final, la question est de savoir quelle API vous préférez.

valarray était censé laisser une certaine qualité du traitement vectoriel FORTRAN disparaître en C ++. D'une manière ou d'une autre, le support nécessaire du compilateur n'est jamais vraiment arrivé.

Le livre Josuttis contient des commentaires intéressants (quelque peu décriants) sur valarray ( ici et ici ).

Cependant, Intel semble maintenant revisiter valarray dans ses dernières versions du compilateur (voir par exemple diapositive 9 ); C’est un développement intéressant, étant donné que leur jeu d’instructions SIMD SSE à 4 voies est sur le point d’être complété par des instructions AVX à 8 voies et Larrabee à 16 voies. Par souci de portabilité, il sera probablement préférable de coder avec une abstraction telle que valarray que (par exemple) les composants intrinsèques.

J'ai trouvé un bon usage pour Valarray. C’est utiliser valarray comme des tableaux numpy.

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

 entrez la description de l'image ici

Nous pouvons implémenter ce qui précède avec 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;
}

De plus, nous avons besoin d'un 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()

La norme C ++ 11 dit:

  

Les classes de tableau valarray sont définies comme étant exemptes de certaines formes de   aliasing, permettant ainsi d'optimiser les opérations sur ces classes.

Voir C ++ 11 26.6.1-2.

std :: valarray est destiné aux tâches numériques lourdes, telles que la dynamique des fluides computationnelle ou la dynamique de la structure computationnelle, dans lesquelles vous avez des tableaux contenant des millions, parfois des dizaines de millions d’éléments, et les parcourant dans une boucle contenant également des millions. des pas de temps. Peut-être qu'aujourd'hui std :: vector a une performance comparable, mais il y a 15 ans, valarray était presque obligatoire si vous vouliez écrire un solveur numérique efficace.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top