Supposons que j'ai un vecteur std :: de double, à savoir
std::vector<double> MyVec(N);
Où N
est si important que les performances comptent. Supposons maintenant que MyVec
est un vecteur non trivial (c'est-à-dire qu'il n'est pas un vecteur de zéros, mais a été modifié par une routine). Maintenant, j'ai besoin de la version niée du vecteur: j'ai besoin de -MyVec
.
Jusqu'à présent, je l'ai mis en œuvre via
std::transform(MyVec.cbegin(),MyVec.cend(),MyVec.begin(),std::negate<double>());
Mais, vraiment, je ne sais pas si c'est quelque chose de sensé ou si c'est juste super naïf de mon côté.
Suis-je en train de le faire correctement? Ou std :: transform est juste une routine super lente dans ce cas?
PS: J'utilise tout le temps les bibliothèques BLAS et LAPACK, mais je n'ai rien trouvé qui corresponde à ce besoin particulier. Cependant, s'il existe une telle fonction dans BLAS/LAPACK qui est plus rapide que std :: transform, je serais heureux de le savoir.
#include <vector>
#include <algorithm>
#include <functional>
void check()
{
std::vector<double> MyVec(255);
std::transform(MyVec.cbegin(),MyVec.cend(),MyVec.begin(),std::negate<double>());
}
Ce code sur https://godbolt.org/ avec l'option de copie -O3 génère Nice Assembly
.L3:
[...]
cmp r8, 254
je .L4
movsd xmm0, QWORD PTR [rdi+2032]
xorpd xmm0, XMMWORD PTR .LC0[rip]
movsd QWORD PTR [rdi+2032], xmm0
.L4:
C'est difficile d'imaginer plus vite. Votre code est déjà parfait, n'essayez pas de déjouer le compilateur et utilisez du code C++ propre, il fonctionne presque à chaque fois.
Heureusement, les données de std::vector
est contigu, vous pouvez donc multiplier par -1 en utilisant des intrinsèques vectorielles (en utilisant des charges/magasins non alignés et une gestion spéciale du débordement possible). Ou utiliser ippsMulC_64f
/ippsMulC_64f_I
à partir de la bibliothèque IPP d'Intel (vous aurez du mal à écrire quelque chose plus rapidement) qui utilisera les plus grands registres vectoriels disponibles pour votre plateforme: https://software.intel.com/en-us/ipp-dev- référence-mulc
Mise à jour: pour dissiper une certaine confusion dans les commentaires, la version complète d'Intel IPP est gratuite (bien que vous puissiez payer pour le support) et est disponible sur Linux, Windows et macOS.
Comme d'autres l'ont mentionné, cela dépend complètement de votre cas d'utilisation. Le moyen le plus simple serait probablement quelque chose comme ceci:
struct MyNegatingVect {
MyVect data;
bool negated = false;
void negate() { negated = !negated; }
// ... setter and getter need indirection ...
// ..for example
MyVect::data_type at(size_t index) { return negated ? - data.at(index) : data.at(index);
};
Si cette indirection supplémentaire pour chaque accès vaut la peine de transformer la négation en définissant un seul bool
dépend, comme déjà mentionné, de votre cas d'utilisation (en fait, je doute qu'il existe un cas d'utilisation où cela apporterait un avantage mesurable ).
Tout d'abord, une fonction générique negate
pour les vecteurs de type arithmétique comme exemple:
#include <type_traits>
#include <vector>
...
template <typename arithmetic_type> std::vector<arithmetic_type> &
negate (std::vector<arithmetic_type> & v)
{
static_assert(std::is_arithmetic<arithmetic_type>::value,
"negate: not an arithmetic type vector");
for (auto & vi : v) vi = - vi;
// note: anticipate that a range-based for may be more amenable
// to loop-unrolling, vectorization, etc., due to fewer compiler
// template transforms, and contiguous memory / stride.
// in theory, std::transform may generate the same code, despite
// being less concise. very large vectors *may* possibly benefit
// from C++17's 'std::execution::par_unseq' policy?
return v;
}
Votre souhait d'un canonique unaire operator -
la fonction va nécessiter la création d'un temporaire, sous la forme:
std::vector<double> operator - (const std::vector<double> & v)
{
auto ret (v); return negate(ret);
}
Ou génériquement:
template <typename arithmetic_type> std::vector<arithmetic_type>
operator - (const std::vector<arithmetic_type> & v)
{
auto ret (v); return negate(ret);
}
pas tentez d'implémenter l'opérateur comme:
template <typename arithmetic_type> std::vector<arithmetic_type> &
operator - (std::vector<arithmetic_type> & v)
{
return negate(v);
}
Tandis que (- v)
annulera les éléments et retournera le vecteur modifié sans avoir besoin d'un temporaire, il casse les conventions mathématiques en définissant efficacement: v = - v;
Si tel est votre objectif, utilisez la fonction negate
. Ne cassez pas l'évaluation attendue de l'opérateur!
clang, avec avx512 activé, génère cette boucle, annulant un impressionnant 64 doubles par itération - entre la gestion de la longueur avant/après:
vpbroadcastq LCPI0_0(%rip), %zmm0
.p2align 4, 0x90
LBB0_21:
vpxorq -448(%rsi), %zmm0, %zmm1
vpxorq -384(%rsi), %zmm0, %zmm2
vpxorq -320(%rsi), %zmm0, %zmm3
vpxorq -256(%rsi), %zmm0, %zmm4
vmovdqu64 %zmm1, -448(%rsi)
vmovdqu64 %zmm2, -384(%rsi)
vmovdqu64 %zmm3, -320(%rsi)
vmovdqu64 %zmm4, -256(%rsi)
vpxorq -192(%rsi), %zmm0, %zmm1
vpxorq -128(%rsi), %zmm0, %zmm2
vpxorq -64(%rsi), %zmm0, %zmm3
vpxorq (%rsi), %zmm0, %zmm4
vmovdqu64 %zmm1, -192(%rsi)
vmovdqu64 %zmm2, -128(%rsi)
vmovdqu64 %zmm3, -64(%rsi)
vmovdqu64 %zmm4, (%rsi)
addq $512, %rsi ## imm = 0x200
addq $-64, %rdx
jne LBB0_21
gcc-7.2.0 génère une boucle similaire, mais semble insister sur l'adressage indexé.
Utilisez for_each
std::for_each(MyVec.begin(), MyVec.end(), [](double& val) { val = -val });
ou parallèle C++ 17
std::for_each(std::execution::par_unseq, MyVec.begin(), MyVec.end(), [](double& val) { val = -val });