2013-09-24 4 views
6

私は、クラスのメモリレイアウトをテンプレート化されたコードでより効果的にするための可能な方法について疑問を抱いていました。私が知る限り、Standardはクラスのデータメンバーが宣言の順番でメモリに配置されるように指示します。クラスのサイズに不必要に追加するデータメンバーを整列させるために、コンパイラーによってパディングが行われる可能性があります。このようなパディングを最小限に抑えるために、データメンバ宣言をコンパイル時に再配置することが考えられます。私はいくつかの検索をしましたが、情報を見つけることができませんでした(ほとんどの場合、人々はコンパイラー・ディレクティブを議論します。データメンバーのコンパイル時の再配置?

まず、以下の(些細な、しかし、反復的で醜い)コード(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 
>; 

3である。ストレージクラスの計算

ここで、各ペアの最初の要素を抽出することによって行われるストレージクラスを計算するだけで済みます。興味深いことに、ここでパターンマッチングが本当に助けになることができます

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

V.コンピューティングそして今、我々はすべてアップ配線:

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を読んだ後に注意してください。Martinho Fernandesのブログのセリフ私は、 'std :: tuple'が要素を渡す順序で要素を配置するという保証はないことに気付きました。だから私は実際に彼の実装を取ることをお勧めします。私はここで答えを聞かせますが、それは簡単に簡単にできるほど小さいからです。 –

+0

はい、私は 'libC++'と 'libstdC++' 'std :: tuple'のレイアウト(とGCC 4.7.2のバグについて)を比較しています。しかし、あなたは包括的かつ迅速な投稿を作成し、そのロジックを詳細に説明し、いくつかの参考にしました。そして私は事実を試す余地が残っているのが本当に好きです。私はちょうどそれを実際に行うことによってそれを正当化したい(そして多分プロセスの中でいくつかの質問をする)。 – lapk

2

R. Martinho Fernandesのブログ記事のこのシリーズをご覧ください:http://flamingdangerzone.com/cxx11/2012/07/06/optimal-tuple-i.html

タプルの最適なパッキングの概要を示します。そのような「パックされた」タプルをクラスのデータストレージとして使用し、get<0>()タプル要素アクセスを隠すアクセサを提供できます。

+0

非常に興味深いセリですが、この答えを素早く解説するのは良いことです。 –

+0

はい、まあ、私はそれらの投稿の著者ではない、私は解決策を私のように提示したくない...多分私はコメントとしてこれを投稿している必要があります... – mitchnull

+0

私は確かにしないそれが答えであることを念頭に置いておいて、それは最も興味深い読書であり、実際には自分自身の答えの瑕疵を指摘しています。私は、std :: tupleが不運にも移植性がないようにレイアウトされると盲目的に仮定しました。 –

関連する問題