c++ optimization language-lawyer unions c++17

c++ - Diseño de datos internos estáticos vectoriales-`union` vs` std:: align_storage_t`-enorme diferencia de rendimiento



optimization language-lawyer (1)

xskxzr tiene razón, este es el mismo problema que en esta pregunta . Fundamentalmente, a gcc le falta una oportunidad de optimización al olvidar que los datos de std::array están alineados. John Zwinck informó útilmente error 80561 .

Puede verificar esto en su punto de referencia haciendo uno de los dos cambios en with_union :

  1. Cambie _data de un std::array<U, N> a simplemente una U[N] . El rendimiento se vuelve idéntico

  2. Recuerde a gcc que _data está realmente alineado cambiando la implementación de emplace_back() a:

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

Cualquiera de esos cambios con el resto de su índice de referencia me ofrece resultados comparables entre WithUnion y WithAlignedStorage .

Supongamos que tiene que implementar una static_vector<T, N> , que es un contenedor de capacidad fija que vive completamente en la pila y nunca asigna, y expone una interfaz similar a std::vector . (Boost proporciona boost::static_vector .)

Teniendo en cuenta que debemos tener un almacenamiento sin inicializar para un máximo de N instancias de T , hay varias opciones que se pueden hacer al diseñar el diseño de datos interno:

  • union solo miembro:

    union U { T _x; }; std::array<U, N> _data;

  • Single std::aligned_storage_t :

    std::aligned_storage_t<sizeof(T) * N, alignof(T)> _data;

  • Arreglo de std::aligned_storage_t :

    using storage = std::aligned_storage_t<sizeof(T), alignof(T)>; std::array<storage, N> _data;

Independientemente de la elección, la creación de los miembros requerirá el uso de "ubicación new " y el acceso a ellos requerirá algo similar a reinterpret_cast .

Ahora suponga que tenemos dos implementaciones mínimas de static_vector<T, N> :

  • with_union : implementado usando el enfoque de " union solo miembro";

  • with_storage : implementado utilizando el enfoque "single std::aligned_storage_t ".

Realicemos el siguiente punto de referencia utilizando g++ y clang++ con -O3 . Utilicé quick-bench.com para esta tarea :

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

( clobber y clobber se toman de la charla CppCon 2015 de Chandler Carruth: "Afinación de C ++: Puntos de referencia, CPU y compiladores. ¡Oh My!" )

Como puede ver en los resultados, g++ parece ser capaz de optimizar agresivamente (vectorización) la implementación que utiliza el enfoque " std::aligned_storage_t " std::aligned_storage_t , pero no la implementación que usa la union .

Mis preguntas son:

  • ¿Hay algo en el estándar que impida que la implementación que usa union se optimice agresivamente? (Es decir, el Estándar otorga más libertad al compilador cuando se usa std::aligned_storage_t ; de ser así, ¿por qué?)

  • ¿Es este un problema puramente de "calidad de implementación"?