Оптимизация для используемых 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.

Другие вопросы по тегам