2013-09-24 2 views
6

Мне было интересно, как сделать макет памяти класса более эффективным в шаблоном. Насколько мне известно, Стандарт обязывает членов данных класса, которые будут изложены в памяти по порядку их объявления. Может быть возможным дополнение, выполняемое компилятором, чтобы выровнять элементы данных, добавляя излишне к размеру класса. Идея состоит в том, чтобы реорганизовать объявления элементов данных во время компиляции, чтобы свести к минимуму такие дополнения. Я сделал некоторые поиски, но не смог найти никакой информации (большую часть времени люди обсуждают директивы компилятора упаковки, что не совсем то же, что и я).Повторная компоновка элементов данных?

Во-первых, пожалуйста, рассмотрим следующую (тривиальное, но повторяющиеся и безобразно) код (same code on ideone.com) (вопросы находятся ниже кода, не стесняйтесь, чтобы пропустить прямо к ним): выход

#include <iostream> 
#include <cstdint> 

namespace so 
{ 
template <typename Ta, typename Tb, typename Tc, std::size_t = 
    ((sizeof(Ta) >= sizeof(Tb)) && (sizeof(Tb) >= sizeof(Tc))) ? 10 : 
    ((sizeof(Ta) >= sizeof(Tc)) && (sizeof(Tc) >= sizeof(Tb))) ? 11 : 
    ((sizeof(Tb) >= sizeof(Ta)) && (sizeof(Ta) >= sizeof(Tc))) ? 20 : 
    ((sizeof(Tb) >= sizeof(Tc)) && (sizeof(Tc) >= sizeof(Ta))) ? 21 : 
    ((sizeof(Tc) >= sizeof(Ta)) && (sizeof(Ta) >= sizeof(Tb))) ? 30 : 
    ((sizeof(Tc) >= sizeof(Tb)) && (sizeof(Tb) >= sizeof(Ta))) ? 31 : 0> 
struct foo {}; 

template <typename Ta, typename Tb, typename Tc> 
struct foo<Ta, Tb, Tc, 10> 
{ 
    Ta a; 
    Tb b; 
    Tc c; 
    foo(Ta _a, Tb _b, Tc _c) : a{_a}, b{_b}, c{_c} {} 
}; 

template <typename Ta, typename Tb, typename Tc> 
struct foo<Ta, Tb, Tc, 11> 
{ 
    Ta a; 
    Tc c; 
    Tb b; 
    foo(Ta _a, Tb _b, Tc _c) : a{_a}, c{_c}, b{_b} {} 
}; 

template <typename Ta, typename Tb, typename Tc> 
struct foo<Ta, Tb, Tc, 20> 
{ 
    Tb b; 
    Ta a; 
    Tc c; 
    foo(Ta _a, Tb _b, Tc _c) : b{_b}, a{_a}, c{_c} {} 
}; 

template <typename Ta, typename Tb, typename Tc> 
struct foo<Ta, Tb, Tc, 21> 
{ 
    Tb b; 
    Tc c; 
    Ta a; 
    foo(Ta _a, Tb _b, Tc _c) : b{_b}, c{_c}, a{_a} {} 
}; 

template <typename Ta, typename Tb, typename Tc> 
struct foo<Ta, Tb, Tc, 30> 
{ 
    Tc c; 
    Ta a; 
    Tb b; 
    foo(Ta _a, Tb _b, Tc _c) : c{_c}, a{_a}, b{_b} {} 
}; 

template <typename Ta, typename Tb, typename Tc> 
struct foo<Ta, Tb, Tc, 31> 
{ 
    Tc c; 
    Tb b; 
    Ta a; 
    foo(Ta _a, Tb _b, Tc _c) : c{_c}, b{_b}, a{_a} {} 
}; 

template <typename Ta, typename Tb, typename Tc> 
struct bar: public foo<Ta, Tb, Tc> 
{ 
private: 
    using base = foo<Ta, Tb, Tc>; 
public: 
    bar() = default; 
    using base::base; 
}; 

template <typename Ta, typename Tb, typename Tc> 
struct foobar 
{ 
    Ta a; 
    Tb b; 
    Tc c; 
    foobar() = default; 
    foobar(Ta _a, Tb _b, Tc _c) : a{_a}, b{_b}, c{_c} {} 
}; 
} //namespace so 

int main() 
{ 
so::bar<std::uint16_t, std::uint32_t, std::uint16_t> bar{1, 2, 3}; 
so::foobar<std::uint16_t, std::uint32_t, std::uint16_t> foobar{1, 2, 3}; 

std::cout << sizeof(bar) << "\t" << sizeof(foobar) << std::endl; 

std::cout << bar.a << " : " << bar.b << " : " << bar.c << std::endl; 
std::cout << foobar.a << " : " << foobar.b << " : " << foobar.c << std::endl; 

return (0); 
} 

программы:

8 12 
1 : 2 : 3 
1 : 2 : 3 

Вопросы:

  1. I Есть ли какой-то известный, независимый от компилятора способ решения такой вещи (возможно, Boost)?
  2. Если нет, существуют ли какие-либо специфические для компилятора директивы, которые будут делать такую ​​вещь автоматически (без смещения данных, как в случае с GCC __atribute__((packed)))?
  3. Можно ли это сделать более общим способом (возможно, с использованием вариативных шаблонов)?

Заранее благодарен!

+0

Подсказка: я считаю, что вы можете решить проблему повторения, используя наследование, которое открывает дверь для вариативных шаблонов, однако это будет в значительной степени полагаться на компилятор, и код может быть не таким изящным (ака, вам придется использовать функция доступа к элементу, например 'get <0> (foo)'). Я также хотел бы отметить, что вы смотрите только на размеры, а не на выравнивания, на некоторых архитектурах «double» имеет длину 8 байтов, но выравнивается на границах 4 байта, поэтому ваши вычисления могут оказаться неоптимальными. –

+0

@ MatthieuM. Вы имеете в виду какое-то рекурсивное наследование, когда мы разворачиваем пакет параметров и используем какой-то 'get <>' для навигации по нему? Выравнивание можно рассматривать как 'alignof()/std :: alignment_of <>', я думаю, спасибо за указание на это. – lapk

+0

На самом деле, моя главная проблема заключалась в том, что, к сожалению, вы не можете использовать расширение пакета в атрибутах (раздражает), и есть различные способы справиться с этим ... и затем я понял, что я уже рассмотрел эти способы при проверке того, как 'std :: tuple' была реализована и что вместо того, чтобы пытаться повторно реализовать их с помощью ухищрений наследования, я мог бы просто повторно использовать 'std :: tuple': D –

ответ

5

Я считаю, что у меня есть относительно простое решение с вариационным шаблоном.

Реализация потребует пару помощников, поэтому, я представлю его назад, чтобы вы могли получить его суть в первую очередь.

template <typename... Args> 
class OptimizedLayout { 
public: 
    template <size_t I> 
    auto at() -> decltype(std::get<Index<I>::value>(_storage)) { 
     return std::get<Index<I>::value>(_storage); 
    } 

    template <size_t I> 
    auto at() const -> decltype(std::get<Index<I>::value>(_storage)) { 
     return std::get<Index<I>::value>(_storage); 
    } 

private: 
    using Indexed = /**/; // pairs of sorted Args (by decreasing size) 
          // and their original index in the argument list 

    using Storage = /*std::tuple<Indexed.first ...>*/; 

    template <size_t I> 
    using Index = /*index of element of Indexed whose .second is I*/; 

    Storage _storage; 
}; // class OptimizedLayout 

Основное преимущество в том, что изменения, как упаковать элементы будут влиять только как Indexed определяется, так что вы можете легко улучшить алгоритм. На данный момент я просто предоставил эквивалент вашего шаблона.


Отказ от ответственности: следующий код не тестировался, он может даже не компилируется, не говоря уже производить правильный результат.

I. Генерирующие индексы.

Объяснение можно найти на the lounge, мы можем его повторно использовать, чтобы создать пакет пар (тип, индекс). Для его сортировки мы будем использовать алгоритм MPL, поэтому проще сделать пакет как MPL vector.

template <std::size_t... Is> 
struct indices {}; 

template <std::size_t N, std::size_t... Is> 
struct build_indices 
    : build_indices<N-1, N-1, Is...> {}; 

template <std::size_t... Is> 
struct build_indices<0, Is...> { using type = indices<Is...>; }; 

template <typename Tuple, typename Indices> 
struct IndexedImpl; 

template <typename... T, size_t... I> 
struct IndexedImpl< std::tuple<T...>, indices<I...> > { 
    using type = boost::mpl::vector< std::pair<T, I>... >; 
}; 

template <typename Tuple> 
using Indexed = 
    IndexedImpl<Tuple, typename build_indices<std::tuple_size<Tuple>::value>::type>; 

II. Сортировка

Для сортировки мы будем использовать MPL sort algorithm, который работает с типами.

struct GreaterSize { 
    template <typename T, typename U> 
    struct apply { 
     using type = boost::mpl::bool_<sizeof(T) > sizeof(U)>; 
    }; 
}; 

template <typename T> 
struct TupleInserter { 
    using state = T; 

    template <typename Seq, typename E> 
    struct apply; 
}; 

template <typename T> 
template <typename... Args, typename E> 
struct TupleInserter<T>::apply<std::tuple<Args...>, E> { 
    using type = std::tuple<Args..., E>; 
}; 

template <typename Tuple> 
using SortedSequence = boost::mpl::sort< 
    typename Indexed<Tuple>::type, 
    GreaterSize, 
    TupleInserter 
>; 

III. Вычисление класса хранения

Теперь нам нужно вычислить класс хранения, который выполняется путем извлечения первого элемента каждой пары. Интересно, что сопоставление шаблонов может действительно помочь здесь.

template <typename T> 
struct TupleFirstExtractor; 

template <typename... T, size_t... I> 
struct TupleFirstExtractor<std::tuple<std::pair<T, I>...>> { 
    using type = std::tuple<T...>; 
}; 

IV. Вычисление индекса решатель

template <typename Tuple, size_t Needle, size_t Acc> 
struct IndexFinderImpl; 

template <typename H, size_t h, typename... Tail, size_t Needle, size_t Acc> 
struct IndexFinderImpl<std::tuple<std::pair<H,h>, Tail...>, Needle, Acc>: 
    IndexFinderImpl<std::tuple<Tail...>, Needle, Acc+1> {}; 

template <typename H, typename... Tail, size_t Needle, size_t Acc> 
struct IndexFinderImpl<std::tuple<std::pair<H, Needle>, Tail...>, Needle, Acc>: 
    std::integral_constant<size_t, Acc> {}; 

В. Собираем все вместе

И теперь мы телеграфировать все:

template <typename... Args> 
class OptimizedLayout { 
public: 
    template <size_t I> 
    auto at() -> decltype(std::get<Index<I>::value>(_storage)) { 
     return std::get<Index<I>::value>(_storage); 
    } 

    template <size_t I> 
    auto at() const -> decltype(std::get<Index<I>::value>(_storage)) { 
     return std::get<Index<I>::value>(_storage); 
    } 

private: 
    using Indexed = typename SortedSequence<std::tuple<Args...>>::type; 

    using Storage = typename TupleFirstExtractor<Indexed>::type; 

    template <size_t I> 
    using Index = IndexFinderImpl<Indexed, I, 0>; 

    Storage _storage; 
}; // class OptimizedLayout 

Подсказка: Я советую использовать специализированное пространство имен для хранения всех помощники. Хотя они могут быть определены в шаблоне, их легче определить, так как они не зависят от Args..., однако вам нужно будет изолировать их, чтобы избежать столкновений с другими частями вашей программы.

+0

** + 1 ** Спасибо за такой подробный прорыв. Я собираюсь изучить его, как только у меня будет немного больше времени, и попытайтесь полностью его реализовать. Думаю, после этого я смогу обновить вопрос и принять ваш ответ. И еще раз спасибо за указание на недостаток 'sizeof()' использования в этом случае. Вы абсолютно правы, это должно быть 'alignof()' вместо этого. – lapk

+0

@PetrBudnik: обратите внимание, что после прочтения R.Мартинхо Фернандес, я понял, что нет никакой гарантии, что 'std :: tuple' закладывает элементы в том порядке, в котором вы их передаете. Поэтому я бы порекомендовал вам выполнить его реализацию; хотя я позволю этому ответу здесь, потому что он достаточно мал, чтобы его можно было легко получить. –

+0

Да, я прочитал часть, сравнивающую компоновку 'libC++' и 'libstdC++' 'std :: tuple' (и об ошибке GCC 4.7.2). Но вы сделали всеобъемлющий, быстрый пост, подробно описывая его логику и давая несколько полезных ссылок. И мне на самом деле нравится, что он оставляет мне возможность попробовать что-то. Я просто хочу отдать должное, фактически сделав это (и, возможно, задав несколько вопросов в этом процессе). – lapk

2

Взгляните на эту серию записей в блогах Р. Мартинхо Фернандес: http://flamingdangerzone.com/cxx11/2012/07/06/optimal-tuple-i.html.

Он описывает оптимальную упаковку кортежей. Вы можете использовать такой «упакованный» кортеж, как хранилище данных для вашего класса, и предоставить аксессурам, скрывающим доступ к элементу типа tuple get<0>().

+0

Очень интересная серия, однако быстрый обзор проблем/решений был бы хорош, чтобы отразить этот ответ. –

+0

Да, ну, я не автор этих сообщений, и я не хотел бы предлагать это решение как мое ... возможно, я должен был разместить это как комментарий вместо этого ... – mitchnull

+0

Я, конечно, не если бы это был ответ, это было самое интересное чтение и фактически указывало на недостаток в моем собственном ответе: я слепо предположил, что 'std :: tuple' будет выстроен по заказу, который, к сожалению, не переносится. –

Смежные вопросы