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-member union

  • with_storage : mis en œuvre en utilisant l'approche "unique std::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à!" )

g++ results

clang++ results


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 utilise std::aligned_storage_t - si oui, pourquoi?)

  • est-ce purement une question de "qualité de la mise en œuvre"?

20
demandé sur Vittorio Romeo 2018-02-06 17:22:33

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 :

  1. changement _data à partir d'un std::array<U, N> tout simplement U[N] . La Performance devient identique

  2. rappeler à gcc que _data est en fait aligné en changeant la mise en œuvre de emplace_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 .

11
répondu Barry 2018-02-06 18:22:10