Présentation des données internes du vecteur statique - 'union' vs ' std:: aligned storage t` - énorme différence de performance
suppose que vous devez mettre en œuvre une classe static_vector<T, N>
, qui est un capacité fixe conteneur qui vit entièrement sur la pile et n'allaite jamais, et expose une interface std::vector
comme. (Boost fournit boost::static_vector
.)
considérant que nous devons avoir uninitialized storage for maximum N
instances de T
, il y a Plusieurs choix qui peuvent être faits lors de la conception de la mise en page des données internes:
-
Simple membre
union
:union U { T _x; }; std::array<U, N> _data;
-
Unique
std::aligned_storage_t
:std::aligned_storage_t<sizeof(T) * N, alignof(T)> _data;
-
Tableau de
std::aligned_storage_t
:using storage = std::aligned_storage_t<sizeof(T), alignof(T)>; std::array<storage, N> _data;
quel que soit le choix, la création des membres nécessitera l'utilisation de " placement new
" et pour y accéder, il faudra quelque chose du genre reinterpret_cast
.
supposons maintenant que nous avons deux implémentations très minimales de static_vector<T, N>
:
-
with_union
: mis en œuvre en utilisant l'approche "single-memberunion
-
with_storage
: mis en œuvre en utilisant l'approche "uniquestd::aligned_storage_t
".
effectuons le benchmark suivant en utilisant à la fois g++
et clang++
avec -O3
. J'ai utilisé quick-bench.com pour cette tâche :
void escape(void* p) { asm volatile("" : : "g"(p) : "memory"); }
void clobber() { asm volatile("" : : : "memory"); }
template <typename Vector>
void test()
{
for(std::size_t j = 0; j < 10; ++j)
{
clobber();
Vector v;
for(int i = 0; i < 123456; ++i) v.emplace_back(i);
escape(&v);
}
}
( escape
et clobber
sont tirées de la conférence CppCon 2015 de Chandler Carruth: " Tuning C++: Benchmarks, et CPUs, et compilateurs! Oh Là Là!" )
- résultats pour
g++ 7.2
( vivre ici ) :
- résultats pour
clang++ 5.0
( vivre ici ) :
comme vous pouvez le voir à partir des résultats, g++
semble être en mesure d'optimiser de façon agressive (vectorisation) la mise en œuvre qui utilise l'approche" unique std::aligned_storage_t
", mais pas la mise en œuvre en utilisant le union
.
mes questions sont:
-
il n'y a rien dans le Une norme qui empêche la mise en œuvre de
union
d'être optimisée de manière agressive? (i. e. la norme accorde - t-elle plus de liberté au compilateur lorsqu'il utilisestd::aligned_storage_t
- si oui, pourquoi?) -
est-ce purement une question de "qualité de la mise en œuvre"?
1 réponses
xskxzr est à droite, c'est la même question que dans cette question . Fondamentalement, gcc manque une occasion d'optimisation en oubliant que les données de std::array
est aligné. John Zwinck a généreusement signalé bug 80561 .
vous pouvez vérifier cela dans votre benchmark en faisant l'un des deux changements à with_union
:
-
changement
_data
à partir d'unstd::array<U, N>
tout simplementU[N]
. La Performance devient identique -
rappeler à gcc que
_data
est en fait aligné en changeant la mise en œuvre deemplace_back()
à:template <typename... Ts> T& emplace_back(Ts&&... xs) { U* data = static_cast<U*>(__builtin_assume_aligned(_data.data(), alignof(U))); T* ptr = &data[_size++]._x; return *(new (ptr) T{std::forward<Ts>(xs)...}); }
L'un ou l'autre de ces changements avec le reste de votre benchmark me donne des résultats comparables entre WithUnion
et WithAlignedStorage
.