が解決を見たが、私は心にパフォーマンスに関するあなたの懸念を取り、私たちはより良い行うことができるかどうかを確認することにしました。
興味深いことに、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>());
}
詳細な分析をいただきありがとうございます!あなたのソリューションは、特にポインタの算術演算を避けるために、私よりもはるかに良く見えます。私はあなたの答えをもっときれいに受け入れていますが、それが最適だとは思っていません。オフセットテーブルの値をルックアップしてタプルのアドレスに追加すると、分岐予測に影響があるため、ジャンプテーブルよりもパフォーマンスが向上する可能性があります。私はおそらくあなたのソリューションを使用して終了するだろう、私はパフォーマンスの最後のiotaを必要としないので、 – enobayram
@enobayram std :: addressofは定数式ではないので、オフセットを使用するコンパイル時のソリューションを構築する方法は考えられません。このため、ルックアップテーブルは静的コンストラクターを保護するためのアトミックガードでコードに組み込まれます。 –
私は同意しますが、問題は難しいですが、UBに頼らずに効率的なことを表現できないのは残念です。 – enobayram