Статический векторный макет внутренних данных - `union` vs` std:: aligned_storage_t` - огромная разница в производительности
Предположим, что вам нужно реализовать класс static_vector<T, N>
, который является контейнером с фиксированной емкостью, который полностью живет в стеке и никогда не выделяет, и предоставляет интерфейс std::vector
. (Boost обеспечивает boost::static_vector
.)
Учитывая, что для максимальных N
экземпляров T
у нас должно быть неинициализированное хранилище, существует множество вариантов, которые можно сделать при разработке внутреннего расположения данных:
-
Одночастный union
:
union U { T _x; };
std::array<U, N> _data;
-
Одиночный std::aligned_storage_t
:
std::aligned_storage_t<sizeof(T) * N, alignof(T)> _data;
-
Массив std::aligned_storage_t
:
using storage = std::aligned_storage_t<sizeof(T), alignof(T)>;
std::array<storage, N> _data;
Независимо от выбора, для создания членов потребуется использование "размещение new
" и доступ к ним потребует чего-то по строкам reinterpret_cast
.
Теперь предположим, что у нас есть две очень минимальные реализации static_vector<T, N>
:
Позвольте выполнить следующий тест, используя как g++
, так и clang++
с -O3
. Я использовал quick-bench.com для этой задачи:
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
и clobber
взяты из Chandler Carruth CppCon 2015 talk: "Настройка С++: тесты, и процессоры, и компиляторы! Oh My!" )
![g++ results]()
![clang++ results]()
Как вы можете видеть из результатов, g++
, похоже, может агрессивно оптимизировать (векторизация) реализацию, использующую подход "single std::aligned_storage_t
", но не реализацию с помощью union
.
clang++
не может агрессивно оптимизировать любой из них.
Мои вопросы:
-
Есть ли что-нибудь в стандарте, которое предотвращает агрессивную оптимизацию реализации с помощью union
? (Т.е. стандарт предоставляет больше свободы компилятору при использовании std::aligned_storage_t
- если так почему?)
-
Является ли это исключительно вопросом "качества реализации"?
Ответы
Ответ 1
xskxzr прав, это та же проблема, что и в этом вопросе. По сути, gcc не имеет возможности оптимизации, забывая, что данные std::array
выровнены. Джон Звинк успешно сообщил ошибка 80561.
Вы можете проверить это в своем тесте, сделав одно из двух изменений в with_union
:
-
Измените _data
с std::array<U, N>
на просто a U[N]
. Производительность становится идентичной.
-
Напомните gcc, что _data
фактически выравнивается, изменяя реализацию 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)...});
}
Любая из этих изменений с остальной частью вашего теста дает мне сопоставимые результаты между WithUnion
и WithAlignedStorage
.