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
:
Cambie
_data
de unstd::array<U, N>
a simplemente unaU[N]
. El rendimiento se vuelve idénticoRecuerde a gcc que
_data
está realmente alineado cambiando la implementación deemplace_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 "singlestd::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!" )
- Resultados para
g++ 7.2
( vivir aquí ) :
- Resultados para
clang++ 5.0
( vivir aquí ) :
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 usastd::aligned_storage_t
; de ser así, ¿por qué?)¿Es este un problema puramente de "calidad de implementación"?