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
_datade unstd::array<U, N>a simplemente unaU[N]. El rendimiento se vuelve idénticoRecuerde a gcc que
_dataestá 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:
unionsolo 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 "unionsolo 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
unionse 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"?