C ++ valarray vs vecteur

J’aime beaucoup les vecteurs. Ils sont chouettes et rapides. Mais je sais que cette chose appelée un valarray existe. Pourquoi utiliserais-je un filtre à la place d’un vecteur? Je sais que les valarrays contiennent du sucre syntaxique, mais à part ça, quand sont-ils utiles?

Les Valarrays (tableaux de valeurs) sont destinés à apporter une partie de la vitesse de Fortran à C ++. Vous ne feriez pas un tableau de pointeurs afin que le compilateur puisse faire des suppositions sur le code et l’optimiser. (La principale raison pour laquelle Fortran est si rapide est qu’il n’y a pas de type de pointeur, donc il ne peut y avoir d’alias de pointeur.)

Les Valarrays ont également des classes qui vous permettent de les découper de manière assez simple, bien que cette partie de la norme puisse demander un peu plus de travail. Les redimensionner est destructif et il manque des iterators.

Donc, si vous travaillez avec des chiffres et que la commodité n’est pas si importante que l’utilisation des valeurs. Sinon, les vecteurs sont beaucoup plus pratiques.

Valarray est une sorte d’orphelin né au mauvais endroit au mauvais moment. Il s’agit d’une tentative d’optimisation, assez spécifique pour les machines utilisées pour les mathématiques lourdes lorsqu’elles étaient écrites – en particulier les processeurs vectoriels comme les 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 fait tout ce que vous deviez faire.

A moins que vous ayez affaire à des baies assez petites, cela a tendance à mal fonctionner avec la mise en cache. Sur la plupart des machines modernes, vous préféreriez généralement (dans la mesure du possible) charger une partie du tableau, effectuer toutes les opérations que vous allez effectuer, puis passer à la partie suivante du tableau.

Valarray est également supposé éliminer toute possibilité d’alias, ce qui (au moins théoriquement) permet au compilateur d’améliorer 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 que toute mise en œuvre réelle en profite de manière significative. Je pense que c’est plutôt un problème de type poule-et-œuf – sans support du compilateur, il n’est pas devenu populaire, et tant que ce n’est pas populaire, personne ne va travailler sur son compilateur pour le supporter.

Il y a aussi un tableau déconcertant (littéralement) de classes auxiliaires à utiliser avec valarray. Vous obtenez slice, slice_array, gslice et gslice_array pour jouer avec des morceaux d’un tableau, et le faire agir comme un tableau multidimensionnel. Vous obtenez également mask_array pour “masquer” une opération (par exemple, append des éléments en x à y, mais uniquement aux positions où z est différent de zéro). Pour faire plus que l’utilisation sortingviale de valarray, vous devez apprendre beaucoup de choses sur ces classes auxiliaires, dont certaines sont assez complexes et dont aucune ne semble (du moins pour moi) très bien documentée.

En bout de ligne, bien qu’il y ait des moments de brillance et que l’on puisse faire certaines choses très bien, il y a aussi de très bonnes raisons pour lesquelles il est (et restra certainement) obscur.

Edit (huit ans plus tard, en 2017): Certains des précédents sont devenus obsolètes au moins dans une certaine mesure. 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 de 2: 1 par rapport à un code identique compilé avec l’implémentation “standard” de valarray .

Donc, même si je ne suis pas entièrement convaincu que les programmeurs C ++ commenceront à utiliser valarray en grand nombre, il y a moins de circonstances dans lesquelles il peut apporter une amélioration de la vitesse.

Lors de la standardisation de C ++ 98, valarray a été conçu pour permettre des calculs mathématiques rapides. Cependant, à cette époque, Todd Veldhuizen a inventé des modèles d’expression et créé blitz ++ , et des techniques de méta-modèles similaires ont été inventées, ce qui rendait les valarrays à peu près obsolètes avant même que le standard ne soit publié. L’IIRC, le ou les auteurs originaux de valarray l’ont abandonné à mi-chemin de la standardisation, ce qui (si vrai) ne l’a pas aidé non plus.

ISTR que la principale raison pour laquelle il n’a pas été retiré de la norme est que personne n’a pris le temps d’évaluer le problème en profondeur et d’écrire une proposition pour le supprimer.

Gardez cependant à l’esprit que tout cela est vaguement rappelé par ouï-dire. 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 a 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 std::valarray s devrait être hautement optimisé, tous les messages d’erreur que vous obtenez en les utilisant seront probablement non intuitifs.

Par curiosité, il y a environ un an, j’ai piqué std::valarray contre 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 utilisant std::valarray pour des calculs simples, mais pas pour mes implémentations pour calculer l’écart type (et, bien sûr, l’écart type n’est pas si complexe en mathématiques). Je soupçonne que les opérations sur chaque élément dans un grand std::vector jouent mieux avec les caches que les opérations sur std::valarray s. ( NOTE , à la suite des conseils de musiphil , j’ai réussi à obtenir des performances presque identiques à partir de vector et de valarray ).

Au final, j’ai décidé d’utiliser std::vector tout en prêtant une attention particulière à des éléments tels que l’allocation de mémoire et la création d’objects temporaires.


Les deux std::vector et std::valarray stockent les données dans un bloc contigu. Cependant, ils accèdent à ces données en utilisant différents modèles, et plus important encore, l’API pour std::valarray encourage différents modèles d’access que l’API pour std::vector .

Pour l’exemple d’écart type, à une étape particulière, il fallait trouver la moyenne de la collection et la différence entre la valeur de chaque élément et la moyenne.

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

 std::valarray original_values = ... // obviously I put something here double mean = original_values.sum() / original_values.size(); std::valarray temp(mean, original_values.size()); std::valarray 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 comme:

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

Aujourd’hui, j’écrirais certainement cela différemment. Si rien d’autre, je profiterais de lambda 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 std::valarray 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 soupçonnais que soustraire la valeur des éléments de deux std::valarray s (dernière ligne dans l’exemple std::valarray ) serait moins compatible avec le cache que la ligne correspondante dans l’exemple std::vector ( ce qui arrive aussi à être la dernière ligne).

Il s’avère, cependant, que

 std::valarray original_values = ... // obviously I put something here double mean = original_values.sum() / original_values.size(); std::valarray 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 supposé laisser un peu de bien-fondé sur le traitement vectoriel FORTRAN sur C ++. D’une manière ou d’une autre, le support du compilateur nécessaire n’a jamais vraiment eu lieu.

Les livres de Josuttis contiennent des commentaires intéressants (quelque peu désobligeants) sur valarray ( ici et ici ).

Cependant, Intel semble désormais revoir valarray dans ses récentes versions du compilateur (voir par exemple la 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 joint par des instructions AVX à 8 voies et Larrabee à 16 voies et dans l’intérêt de la portabilité, mieux vaut coder avec une abstraction comme valarray que (disons) insortingnsèques.

Le standard C ++ 11 dit:

Les classes de tableau valarray sont définies pour être exemptes de certaines formes d’alias, ce qui permet d’optimiser les opérations sur ces classes.

Voir C ++ 11 26.6.1-2.

J’ai trouvé un bon usage pour valarray. C’est utiliser Valarray comme les 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); 

entrer la description de l'image ici

Nous pouvons implémenter ci-dessus avec valarray.

 valarray linspace(float start, float stop, int size) { valarray v(size); for(int i=0; i arange(float start, float step, float stop) { int size = (stop - start) / step; valarray v(size); for(int i=0; i& x, const valarray& 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 

En outre, 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()