2016-04-22 10 views
1

私はstd::tuple<T...>を持っていて、そのn番目の要素に効率的にアクセスしたいとします.nは実行時にのみ認識されます。タイプT...は異質であるので、私が得ることができるのはvoid *であり、私はそれでOKです。好きvoid *実行時にstd :: tupleのn番目の要素に

template <size_t ... Indexes, class Tuple> 
void * get_element_pointer(std::index_sequence<Indexes...>, Tuple & t, size_t idx) { 
    static size_t offsets[] = {(size_t)(void *)&std::get<Indexes>(t) - (size_t)(void *)(&t)...}; 
    return (void *)((size_t)(void *)(&t) + offsets[idx]); 
} 

あなたはそれを呼び出す:

get_element_pointer(std::index_sequence_for<T...>{}, some_tuple, some_index); 

これの要旨は静的に各タプルの要素のオフセットのリストが含まれていsize_t配列offsets作成することです。ここで私が到着したものです。次に、実行時に、オフセットを検索して渡されたタプルに追加するだけで済みます。私の解決策と

2つの問題のバグ私を:

  1. offsetsは、この関数が呼び出されたときに初めて作成され、そしてそれは、一度に渡されたタプルのインスタンスに基づいて作成されています。私はこれがちょっと奇妙だと思う。私はタイプTupleの偽の一時的なものを作成することができましたが、デフォルトで構成可能ではないかもしれません。あるいはをTuple *にキャストできますが、std::get<Indexes>(*(Tuple *)(nullptr))はUBを叫びます。
  2. (size_t)(void *)(&t)(void *)((size_t)(void *)(&t) + offsets[idx])ポインタジャグリングが、私に警告を出さないようにコンパイラを停止させる唯一の方法です。あなたが仮想関数などを持っているときにポインタの変換がトリッキーで重要ではないことを知っています。それで、私は何かが不足しているかもしれないと心配しています。

私の解決策は受け入れられると思いますか?あなたはより簡単なポインタジャグリングでソリューションを考えることができますか?

答えて

2

が解決を見たが、私は心にパフォーマンスに関するあなたの懸念を取り、私たちはより良い行うことができるかどうかを確認することにしました。

興味深いことに、constexprで最適化しようとする私の試みは、コンパイラによってさまざまな結果をもたらしました。

gcc 5の出力を比較します。ここでは3とリンゴ打ち鳴らす:

__Z19get_element_pointerINSt3__15tupleIJiiiiiiiiiiEEEEPvRT_m: 
    .align 4, 0x90 
    leaq __ZZ19get_element_pointerIJLm0ELm1ELm2ELm3ELm4ELm5ELm6ELm7ELm8ELm9EENSt3__15tupleIJiiiiiiiiiiEEEEPvRT0_mNS0_16integer_sequenceImJXspT_EEEEE4ptrs(%rip), %rax 
    jmpq *(%rax,%rsi,8)   ## TAILCALL 

#include <utility> 
#include <tuple> 
#include <iostream> 


template<class Tuple, size_t Index> 
    void* get_address(Tuple& t) 
{ 
    return std::addressof(std::get<Index>(t)); 
} 

template <size_t ... Indexes, class Tuple> 
constexpr void* get_element_pointer(Tuple & t, 
          size_t idx, 
          std::index_sequence<Indexes...>) 
{ 
    using function_type = void* (*)(Tuple&); 
    function_type constexpr ptrs[] = 
    { 
    &get_address<Tuple, Indexes>... 
    }; 
    return ptrs[idx](t); 
} 


template<class Tuple> 
__attribute__((noinline)) 
constexpr 
    void * get_element_pointer(Tuple& t, size_t index) 
{ 
    return get_element_pointer(t, 
          index, 
          std::make_index_sequence<std::tuple_size<Tuple>::value>()); 
} 

int main() 
{ 
    std::tuple<int, int, int, int, int, int, int , int, int, int> x; 
    x = std::make_tuple(4, 5, 6, 7, 8, 9, 10, 11, 12, 13); 
    std::cout << *reinterpret_cast<int*>(get_element_pointer(x, 1)) << std::endl; 
} 

打ち鳴らすのソリューションは、このでした(明確にするため-O2 -fomit-フレーム・ポインタを使用してコンパイル):ここ

は、私の解決策でした

__ZZ19get_element_pointerIJLm0ELm1ELm2ELm3ELm4ELm5ELm6ELm7ELm8ELm9EENSt3__15tupleIJiiiiiiiiiiEEEEPvRT0_mNS0_16integer_sequenceImJXspT_EEEEE4ptrs: 
    .quad __Z11get_addressINSt3__15tupleIJiiiiiiiiiiEEELm0EEPvRT_ 
    .quad __Z11get_addressINSt3__15tupleIJiiiiiiiiiiEEELm1EEPvRT_ 
    .quad __Z11get_addressINSt3__15tupleIJiiiiiiiiiiEEELm2EEPvRT_ 
    .quad __Z11get_addressINSt3__15tupleIJiiiiiiiiiiEEELm3EEPvRT_ 
    .quad __Z11get_addressINSt3__15tupleIJiiiiiiiiiiEEELm4EEPvRT_ 
    .quad __Z11get_addressINSt3__15tupleIJiiiiiiiiiiEEELm5EEPvRT_ 
    .quad __Z11get_addressINSt3__15tupleIJiiiiiiiiiiEEELm6EEPvRT_ 
    .quad __Z11get_addressINSt3__15tupleIJiiiiiiiiiiEEELm7EEPvRT_ 
    .quad __Z11get_addressINSt3__15tupleIJiiiiiiiiiiEEELm8EEPvRT_ 
    .quad __Z11get_addressINSt3__15tupleIJiiiiiiiiiiEEELm9EEPvRT_ 
のように、コンパイル時に生成されるジャンプテーブルを参照します。各アクセサ関数は(提供される一つの例)自明である

__Z11get_addressINSt3__15tupleIJiiiiiiiiiiEEELm2EEPvRT_: 
    leaq 8(%rdi), %rax 
    retq 

これは私が

「私はマシンコードを書いていた場合、私はどうなるのか」という、コンパイラが行うだろうと想定しましたしかし、gccはジャンプテーブルを最適化する機会を逃してしまい、使用前にメモリに構築されているようです。

void* get_element_pointer<std::tuple<int, int, int, int, int, int, int, int, int, int> >(std::tuple<int, int, int, int, int, int, int, int, int, int>&, unsigned long): 
     movq void* get_address<std::tuple<int, int, int, int, int, int, int, int, int, int>, 0ul>(std::tuple<int, int, int, int, int, int, int, int, int, int>&), -88(%rsp) 
     movq void* get_address<std::tuple<int, int, int, int, int, int, int, int, int, int>, 1ul>(std::tuple<int, int, int, int, int, int, int, int, int, int>&), -80(%rsp) 
     movq void* get_address<std::tuple<int, int, int, int, int, int, int, int, int, int>, 2ul>(std::tuple<int, int, int, int, int, int, int, int, int, int>&), -72(%rsp) 
     movq void* get_address<std::tuple<int, int, int, int, int, int, int, int, int, int>, 3ul>(std::tuple<int, int, int, int, int, int, int, int, int, int>&), -64(%rsp) 
     movq void* get_address<std::tuple<int, int, int, int, int, int, int, int, int, int>, 4ul>(std::tuple<int, int, int, int, int, int, int, int, int, int>&), -56(%rsp) 
     movq void* get_address<std::tuple<int, int, int, int, int, int, int, int, int, int>, 5ul>(std::tuple<int, int, int, int, int, int, int, int, int, int>&), -48(%rsp) 
     movq void* get_address<std::tuple<int, int, int, int, int, int, int, int, int, int>, 6ul>(std::tuple<int, int, int, int, int, int, int, int, int, int>&), -40(%rsp) 
     movq void* get_address<std::tuple<int, int, int, int, int, int, int, int, int, int>, 7ul>(std::tuple<int, int, int, int, int, int, int, int, int, int>&), -32(%rsp) 
     movq void* get_address<std::tuple<int, int, int, int, int, int, int, int, int, int>, 8ul>(std::tuple<int, int, int, int, int, int, int, int, int, int>&), -24(%rsp) 
     movq void* get_address<std::tuple<int, int, int, int, int, int, int, int, int, int>, 9ul>(std::tuple<int, int, int, int, int, int, int, int, int, int>&), -16(%rsp) 
     movq -88(%rsp,%rsi,8), %rax 
     jmp  *%rax 

は同様の些細なアクセサを呼び出す前に:

void* get_address<std::tuple<int, int, int, int, int, int, int, int, int, int>, 3ul>(std::tuple<int, int, int, int, int, int, int, int, int, int>&): 
     leaq 24(%rdi), %rax 
     ret 

だから広まり、私は非constexprの実装で定数の畳み込みを行う可能性があるかどうかを疑問に思ったより良い:

template <size_t ... Indexes, class Tuple> 
void* get_element_pointer(Tuple & t, 
          size_t idx, 
          std::index_sequence<Indexes...>) 
{ 
    using function_type = void* (*)(Tuple&); 
    function_type static const ptrs[] = 
    { 
    &get_address<Tuple, Indexes>... 
    }; 
    return ptrs[idx](t); 
} 

はそれがなかったが判明 - これでconstexprソリューションで生成されたclangと同じコードがgccで取得されます:

これはどうしたのですか?

__Z19get_element_pointerINSt3__15tupleIJiiiiiiiiiiEEEEPvRT_m: 
    movq __ZZ19get_element_pointerIJLm0ELm1ELm2ELm3ELm4ELm5ELm6ELm7ELm8ELm9EENSt3__15tuple[email protected]GOTPCREL(%rip), %rax 
    jmpq *(%rax,%rsi,8)   ## TAILCALL 

幸い同じ結果です。

だからここに、最終的な、証明可能に最適なソリューションです:

template<class Tuple, size_t Index> 
void* get_address(Tuple& t) 
{ 
    return std::addressof(std::get<Index>(t)); 
} 

template <size_t ... Indexes, class Tuple> 
void* get_element_pointer(Tuple & t, 
            size_t idx, 
            std::index_sequence<Indexes...>) 
{ 
    using function_type = void* (*)(Tuple&); 
    function_type static const ptrs[] = 
    { 
     &get_address<Tuple, Indexes>... 
    }; 
    return ptrs[idx](t); 
} 


template<class Tuple> 
__attribute__((noinline)) 
constexpr 
void * get_element_pointer(Tuple& t, size_t index) 
{ 
    return get_element_pointer(t, 
           index, 
           std::make_index_sequence<std::tuple_size<Tuple>::value>()); 
} 
+0

詳細な分析をいただきありがとうございます!あなたのソリューションは、特にポインタの算術演算を避けるために、私よりもはるかに良く見えます。私はあなたの答えをもっときれいに受け入れていますが、それが最適だとは思っていません。オフセットテーブルの値をルックアップしてタプルのアドレスに追加すると、分岐予測に影響があるため、ジャンプテーブルよりもパフォーマンスが向上する可能性があります。私はおそらくあなたのソリューションを使用して終了するだろう、私はパフォーマンスの最後のiotaを必要としないので、 – enobayram

+0

@enobayram std :: addressofは定数式ではないので、オフセットを使用するコンパイル時のソリューションを構築する方法は考えられません。このため、ルックアップテーブルは静的コンストラクターを保護するためのアトミックガードでコードに組み込まれます。 –

+0

私は同意しますが、問題は難しいですが、UBに頼らずに効率的なことを表現できないのは残念です。 – enobayram

0
  1. 最初に渡されたインスタンスを使用することは、正しさの点で私にとっては問題ではないようです。事前にタプルを作成しようとすると、デフォルトの構成可能性が問題になることを指摘するのは当然ですが、もう一度nullptrtuple*にキャストして使用することができます。

  2. おそらく(void *)((size_t)(void *)(&t) + offsets[idx])reinterpret_cast<char*>(&t) + offsets[idx]と書くことができます。

+0

1. 'のstd :: (*(タプル*)(nullptr))は'本当に私をオフに恐怖ます。 2.私の最初の試みは 'char * 'を使っていましたが、' char *'を通してポインタの変換を保証するものが何であるのか分かりません。 '(T *)' - > '(void *)' - > '(T *)'は、どのようにタイプが 'T 'であるかにかかわらず正しいことが保証されています。私は 'char *'が非POD型で約束しているかどうか分からない。 – enobayram

+0

まあ 't'はタプルですが、これは狂気の多相クラスではないので、' char * 'についてのあなたの心配を緩和するでしょうか? 'nullptr'をキャストすることについては、Cの人々はそれが私たちが' offsetof() 'のやり方を覚えているかもしれません:http://stackoverflow.com/questions/7897877/how-does-the-c-offsetof-macro-work –

+0

問題はタプルそのものではなく、タプルの要素が何であるかを事前に知る必要はありません。クレイジー多形型になる可能性があります。 – enobayram

2

理由だけではなく、私が過負荷operator &と邪悪なクラスを処理するためにstd::addressofを使用

template <size_t ... Indexes, class Tuple> 
void* get_element_pointer(std::index_sequence<Indexes...>, Tuple & t, size_t idx) { 
    void* ptrs[] = { static_cast<void *>(std::addressof(std::get<Indexes>(t)))... }; 
    return ptrs[idx]; 
} 

注意。あなたの警告を

、あなたがstd::size_tstd::intptr_tおよび/またはchar*によってあなた置き換える必要があります。

static std::intptr_t offsets[] = { 
    reinterpret_cast<char *>(std::addresof(std::get<Indexes>(t))) 
    - reinterpret_cast<char *>(&t)... 
}; 
static_cast<void *>(reinterpret_cast<char *>(&t) + offsets[idx]); 
+0

私は実際にこの代替案を検討しましたが、各呼び出しでポインタの配列を作成しているので、コンパイラが効率的なコードを生成することは確実ではありません。私はコンパイラがこれを最適化することができたら(それが何をしなければならないか考えて)、本当に感心します。 'addressof'について頭を上げてくれてありがとう。 'char *'の使用については、John Zwinckの答えを参照してください。 – enobayram

+0

@enobayram私は心配し、いくつかのテストをしました。私の答えを見てください。結果は面白いと思います。 –

関連する問題