数据成员的编译时间重新排列? [英] Compile-time re-arrangement of data members?

查看:169
本文介绍了数据成员的编译时间重新排列?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想知道一个可能的方法使一个类的内存布局在模板代码中更有效。据我所知,标准要求一个类的数据成员按照声明的顺序排列在内存中。可能存在由编译器完成的填充,以将数据成员不必要地添加到类的大小。这个想法是在编译时重新排列数据成员声明,​​以最小化这种填充。我做了一些搜索,但没有找到任何信息(大多数时候人们讨论打包编译器指令,这是不一样的,我看到它)。



首先,请考虑以下(琐碎,但重复性和丑陋)代码( ideone.com上的相同代码)(问题在代码下面,可随时跳到下面):

  #include< iostream> 
#include< cstdint>

命名空间所以
{
模板< typename Ta,typename Tb,typename Tc,std :: size_t =
((sizeof(Ta)> = sizeof (Tb))&(尺寸(Tb)> =尺寸(Tc))) 10:
((sizeof(Ta)> = sizeof(Tc))&(sizeof(Tc) 11:
((sizeof(Tb)> = sizeof(Ta))&(sizeof(Ta) 20:
((sizeof(Tb)> = sizeof(Tc))&(sizeof(Tc) 21:
((sizeof(Tc)≥Sizeof(Ta))&(sizeof(Ta) 30:
((sizeof(Tc)≥Sizeof(Tb))&(sizeof(Tb) 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} {}
};
} //命名空间所以

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> foob​​ar {1,2,3};

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

std :: cout<< bar.a < :<< bar.b < :<< bar.c<< std :: endl;
std :: cout<< foob​​ar.a<< :<< foob​​ar.b < :<< foob​​ar.c<< std :: endl;

return(0);
}

程序输出:

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

问题:



  1. 如果没有,是否有一些编译器特定的指令将自动执行这样的事情(没有数据未对齐,如同GCC的 __ atribute __((packed)))?

  2. 可以使用更通用的方法(可能使用可变参数模板)吗?

提前感谢!

解决方案

一个相对简单的可变参数模板解决方案。



实现将需要一个帮助者,所以我会提出它,所以你可以先得到它的要点。 >

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

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

private:
using Indexed = / ** /; //对排序的Args(通过减小大小)
//和它们在参数列表中的原始索引

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

template< size_t I>
使用Index = / * Indexed的元素的索引,其.second是I * /;

存储_storage;
}; // class OptimizedLayout

这里的主要好处是改变如何包装元素只会影响 Indexed 是定义的,所以你可以很容易地改进算法。






免责声明:以下代码未经测试,它可能甚至不能编译,更不用说产生正确的结果。



我。正在生成索引。



解释可以在休息室,我们可以重用它来生成一对对(类型,索引)。为了对它进行排序,我们将使用MPL算法,因此生成MPL向量

 模板< 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 ...>,索引< 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排序算法,它根据类型进行操作。

  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>
使用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, {};

V。

现在我们将所有内容结合起来:

 <$> c $ c> 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>
}

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

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

使用Storage = typename TupleFirstExtractor< Indexed> :: type;

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

存储_storage;
}; // class OptimizedLayout

提示:我建议使用专门的命名空间来保存所有的帮助。虽然可以在模板中定义它们,但是更容易在外部定义它们,因为它们不依赖于 Args ... ,但是您需要隔离它们以避免冲突与您计划的其他部分。


I was wondering about a possible way to make memory layout of a class to be more effective in templated code. As far as I know, Standard mandates data members of a class to be laid out in memory on order of their declaration. There might be possible padding done by the compiler to align the data members adding unnecessarily to the size of the class. The idea is to re-arrange data members declarations at compile time to minimize such padding. I did some searching, but couldn't find any info (most of the time people discuss packing compiler directives, which is not quite the same as I see it).

First, please consider the following (trivial, but repetitive and ugly) code (same code on ideone.com) (questions are below the code, feel free to skip right down to them):

#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);
}

Program output:

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

Questions:

  1. Is there some well-known, compiler-independent way of solving such thing (Boost, maybe)?
  2. If no, is there some compiler-specific directives that will do such thing automatically (without data misalignment as with GCC's __atribute__((packed)))?
  3. Can this be done in a more generic way (possibly using variadic templates)?

Thanks in advance!

解决方案

I believe I have a relatively simple variadic template solution.

The implementation will require a couple helpers though, so I will present it backward so you can get the gist of it first.

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

The main benefit here is that changing how to pack elements will only affect how Indexed is defined, so you can easily improve on the algorithm. For now, I will just provide an equivalent of your template.


Disclaimer: the following code is untested, it may not even compile, let alone produce the correct result.

I. Generating indices.

The explanation can be found on the lounge, we can reuse it to generate a pack of pairs (type, index). To sort it we are going to use a MPL algorithm, so it is simpler to produce the pack as a 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. Sorting

In order to sort we are going to use the MPL sort algorithm, which operates on types.

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. Computing the Storage class

Now, we only need to compute the storage class which is done by extracting the first element of each pair. Interestingly, pattern matching can really assist here.

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. Computing the Index solver

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> {};

V. Putting it all together

And now we wire up everything:

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

Hint: I advise using a specialized namespace to hold all the helpers. Whilst they could be defined within the template, it is easier to define them outside since they do not depend on Args..., however you would need to isolate them to avoid clashes with other parts of your program.

这篇关于数据成员的编译时间重新排列?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆