Оптимизация для используемых ODR пустых классов
Многие из сегодняшнего кода C++, как правило, загружаются шаблонами в наибольшей степени. Это библиотеки: STL, Boost.Spirit, Boost.MPL и другие. Они поощряют пользователей объявлять функциональные объекты в форме struct S { /* presence of non-virtual member functions and operators, but absense of non-static data members or non-empty base classes */ }; S const s{};
, Большинство из них не имеют гражданства (т.е. static_assert(std::is_empty< S >{});
имеет место). Для тех из них, которые используют ODR, независимо от их пустоты data
раздел прироста файла на 1 байт (sizeof(S) == 1
для пустого типа S
потому что все адреса для последовательно выделенных объектов должны быть разными). Даже в простых грамматиках Boost.Spirit существует множество таких пустых классов, используемых в ODR. Но совершенно бессмысленно оставлять им место.
Я пытался проверить clang
на колиру, используя следующий код (-Ofast
):
#include <utility>
#include <type_traits>
#include <cstdlib>
#include <cassert>
template< std::size_t index >
struct S {};
namespace
{
template< std::size_t index >
S< index > value = {};
}
template< typename lhs, typename rhs >
std::ptrdiff_t
diff(lhs & l, rhs & r)
{
return (static_cast< char * >(static_cast< void * >(&r)) - static_cast< char * >(static_cast< void * >(&l)));
}
template< std::size_t base, std::size_t ...indices >
std::ptrdiff_t
bss_check(std::index_sequence< indices... >)
{
return (diff(value< (base + indices) >, value< (base + indices + 1) >) + ...);
}
template< std::size_t size, std::size_t base >
bool
enumerate()
{
return (bss_check< base >(std::make_index_sequence< size >{}) + 1 == size);
}
template< std::size_t size, std::size_t ...bases >
bool
expand(std::index_sequence< bases... >)
{
return (enumerate< size, (bases * size) >() && ...);
}
template< std::size_t size = 100, std::size_t count = size >
bool
check()
{
return expand< size >(std::make_index_sequence< count >{});
}
int
main()
{
static_assert(std::is_empty< S< 0 > >{});
assert((check< DIM >()));
return EXIT_SUCCESS;
}
и получить результат (вывод size
утилита для DIM == 100
т.е. 100 * 100 классов):
text data bss dec hex filename
112724 10612 4 123340 1e1cc ./a.out
Если я поменяю подпись diff(lhs & l, rhs & r)
в diff(lhs l, rhs r)
чтобы подавить использование ODR, тогда результат:
text data bss dec hex filename
69140 608 8 69756 1107c ./a.out
Почти равно (data
раздел представляет только интерес) случай простого комментирования assert((check< DIM >()));
линия (большая часть text
секция предсказуема, DCE-оптимизирована):
text data bss dec hex filename
1451 600 8 2059 80b ./a.out
Отсюда я делаю вывод, что оптимизация для пустых классов, используемых ODR, отсутствует.
Для явно заданных параметров шаблона есть возможность использовать простой фильтр типов:
template< typename type >
using ref_or_value = std::conditional_t< std::is_empty< std::decay_t< type > >{}, std::decay_t< type >, type && >;
Но у меня нет простого обходного пути для выводимых типов шаблонов.
Есть ли вышеупомянутая оптимизация в современных компиляторах? Если да, то как включить? Если нет, есть ли методика достижения желаемого поведения в данный момент?
Иногда я знаю, что адреса объектов много болтают, но в описанной выше ситуации это не так.
Я думаю, что-то вроде атрибута для переменной или типа (например, [[immaterial]]
) было бы удобно. Возможно, такой атрибут (используемый для классов) должен отрицать возможность получения адреса экземпляров приписанных классов (сложная ошибка во время компиляции) или оператора адресации &
shoud возвращает бессмысленное значение (определяется реализацией).
1 ответ
C++17 будет добавлять встроенные переменные, чтобы помочь решить некоторые из этих проблем, как описано в N4424. Это также объясняет некоторые обходные пути. Для объектов глобальной функции вы можете определить их так:
// Sum function object
struct sum_f
{
template<class T, class U>
auto operator()(T x, U y) const
{
return x+y;
}
};
template<class T>
struct static_const_storage
{
static constexpr T value = T();
};
template<class T>
constexpr T static_const_storage<T>::value;
template<class T>
constexpr const T& static_const()
{
return static_const_storage<T>::value;
}
static constexpr auto& sum = static_const<sum_f>();
Это делает sum
Функциональный объект уникален для всех единиц перевода, что позволяет избежать вздутия и нарушений ODR. Однако этот обходной путь на самом деле не работает для переменных шаблона, и лучше всего избегать их (если вы беспокоитесь о выполнимом раздувании), пока мы не получим встроенные переменные в C++17.