Reassembling data during data compilation?

I was interested in learning about a possible way to make a class memory layout more efficient in a template. As far as I know, the Standard obliges the members of the class data, which will be stated in memory in the order of their declaration. It may be possible for the compiler to complement the data elements, adding unnecessarily to the size of the class. The idea is to reorganize data element declarations at compile time to minimize such additions. I did some searching, but could not find any information (most of the time people discuss packaging compiler directives, which is not exactly the same as me).

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

#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:

  • Is there any known compiler-independent way to solve such a problem (possibly Boost)?
  • If not, are there any compiler-specific directives that will do this thing automatically (without data biasing, as with GCC __atribute__((packed)) )?
  • Can this be done in a more general way (possibly using variable templates)?

Thanks in advance!

+6
source share
2 answers

I believe that I have a relatively simple solution with a variation pattern.

Implementation will require several helpers, so I will introduce it back so you can get 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 advantage here is that changing the layout of the elements only affects how Indexed is determined, so you can easily improve the algorithm. At the moment, I just provided the equivalent of your template.


Disclaimer: the following code is not verified, it may not even compile, not to mention getting the correct result.

I. Creating indexes.

An explanation can be found in the living room , we can reuse it to create a package of pairs (type, index). To sort it, we will use the MPL algorithm, so it’s easier to create a package as an 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

For sorting, we will use the MPL sorting algorithm, which works with 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. Storage Class Calculation

Now we only need to calculate the storage class, which is performed by extracting the first element of each pair. Interestingly, pattern matching can really help 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. Index Solver Calculation

 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 connect 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 

Tip. I recommend using a specialized namespace to store all helpers. Although they can be defined in a template, they are easier to define since they are not dependent on Args... however you will need to isolate them to avoid collisions with other parts of your program.

+5
source

Take a look at this series of blog entries by R. Martinho Fernandez: http://flamingdangerzone.com/cxx11/2012/07/06/optimal-tuple-i.html .

It describes the optimal packaging of tuples. You can use such a "packed" tuple as the data store for your class, and provide accessors that hide access to the element's element set get<0>() .

+2
source

Source: https://habr.com/ru/post/954479/


All Articles