式テンプレートの1つの特定の利点を見てみましょう。ETを使用すると、次のようなオーバーロードされた演算子で発生する、メモリ内のベクトルサイズの一時的なものを回避できます。
template<typename T>
std::vector<T> operator+(const std::vector<T>& a, const std::vector<T>& b)
{
std::vector<T> tmp; // vector-sized temporary
for_each(...);
return tmp;
}
C ++ 11では、この関数のreturnステートメントは移動セマンティクスを適用します。ベクトルのコピーはありません。それは勝利です。
しかし、私が次のような単純な表現を見ると
d = a + b + c;
上記の関数は(両方に対してoperator+
)2回呼び出されますが、最終的な割り当ては移動セマンティクスで実行できます。
合計2つのループが実行されます。一時的に出して、すぐに読み返すという意味です。大きなベクトルの場合、これはキャッシュから外れます。これは、式テンプレートよりも悪いです。彼らはたった1つのループですべてを行うことができます。ETは、次と同等の上記のコードを実行できます。
for(int i=0 ; i < vec_length ; ++i)
d[i] = a[i] + b[i] + c[i];
ラムダと移動セマンティクスまたはその他の新機能を組み合わせてETと同じくらいうまくいくかどうか疑問に思いました。何かご意見は?
編集:
基本的に、コンパイラはET手法を使用して、型システムを使用した代数式に似た解析ツリーを構築します。このツリーは、内部ノードとリーフノードで構成されています。内部ノードは操作(加算、乗算など)を表し、リーフノードはデータオブジェクトへの参照を表します。
計算プロセス全体をスタックマシンのように考えてみました。操作スタックから操作を取得し、引数スタックから次の引数を取得して、操作を評価します。結果をスタックに戻し、操作を待機します。
これらの2つの異なるオブジェクト(操作スタックとデータリーフスタック)を表すstd::tuple
ために、操作用の
aとデータリーフ用のaをにバンドルしstd::tuple
ましたstd::pair<>
。最初はを使用しましたstd:vector
が、実行時のオーバーヘッドが発生しました。
プロセス全体は2つのフェーズで行われます。スタックマシンの初期化では、操作と引数のスタックが初期化されます。そして、ペアのコンテナをベクトルに割り当てることによってトリガーされる評価フェーズ。
Vec
プライベートarray<int,5>
(ペイロード)を保持し、「式」を受け取るオーバーロードされた代入演算子を特徴とするクラスを作成しました。
グローバルoperator*
は、takeingと "expression"のすべての組み合わせでオーバーロードされ
Vec
、がだけではない場合にも正しい処理を可能にしますa*b
。imull
(注意してください、私はこの教育的な例を乗算に切り替えました-基本的にはアセンブラーですばやく見つけるためです。)
Vec
評価を開始する前に最初に行うことは、関連するオブジェクトから値を「抽出」し、引数スタックを初期化することです。これは、インデックス付け可能なベクトルとインデックス付け不可能な結果など、さまざまな種類のオブジェクトが存在しないようにするために必要でした。これが
Extractor
目的です。繰り返しになりますが、可変個引数テンプレートが使用されます。この場合、実行時のオーバーヘッドは発生しません(これはすべてコンパイル時に行われます)。
すべてが機能します。式は適切に評価されます(追加も追加しましたが、コードに合わせるためにここでは省略しています)。以下に、アセンブラの出力を示します。まさにあなたが望むように、生の計算:ET技術と同等。
アップショット。C ++ 11の新しい言語機能は、(テンプレートメタプログラミングとともに)コンパイル時の計算の領域を開く可変個引数テンプレートを提供します。ここでは、可変個引数テンプレートの利点を使用して、従来のET手法と同じくらい優れたコードを生成する方法を示しました。
#include<algorithm>
#include<iostream>
#include<vector>
#include<tuple>
#include<utility>
#include<array>
template<typename Target,typename Tuple, int N, bool end>
struct Extractor {
template < typename ... Args >
static Target index(int i,const Tuple& t, Args && ... args)
{
return Extractor<Target, Tuple, N+1,
std::tuple_size<Tuple>::value == N+1>::
index(i, t , std::forward<Args>(args)..., std::get<N>(t).vec[i] );
}
};
template < typename Target, typename Tuple, int N >
struct Extractor<Target,Tuple,N,true>
{
template < typename ... Args >
static Target index(int i,Tuple const& t,
Args && ... args) {
return Target(std::forward<Args>(args)...); }
};
template < typename ... Vs >
std::tuple<typename std::remove_reference<Vs>::type::type_t...>
extract(int i , const std::tuple<Vs...>& tpl)
{
return Extractor<std::tuple<typename std::remove_reference<Vs>::type::type_t...>,
std::tuple<Vs...>, 0,
std::tuple_size<std::tuple<Vs...> >::value == 0>::index(i,tpl);
}
struct Vec {
std::array<int,5> vec;
typedef int type_t;
template<typename... OPs,typename... VALs>
Vec& operator=(const std::pair< std::tuple<VALs...> , std::tuple<OPs...> >& e) {
for( int i = 0 ; i < vec.size() ; ++i ) {
vec[i] = eval( extract(i,e.first) , e.second );
}
}
};
template<int OpPos,int ValPos, bool end>
struct StackMachine {
template<typename... OPs,typename... VALs>
static void eval_pos( std::tuple<VALs...>& vals , const std::tuple<OPs...> & ops )
{
std::get<ValPos+1>( vals ) =
std::get<OpPos>(ops).apply( std::get<ValPos>( vals ) ,
std::get<ValPos+1>( vals ) );
StackMachine<OpPos+1,ValPos+1,sizeof...(OPs) == OpPos+1>::eval_pos(vals,ops);
}
};
template<int OpPos,int ValPos>
struct StackMachine<OpPos,ValPos,true> {
template<typename... OPs,typename... VALs>
static void eval_pos( std::tuple<VALs...>& vals ,
const std::tuple<OPs...> & ops )
{}
};
template<typename... OPs,typename... VALs>
int eval( const std::tuple<VALs...>& vals , const std::tuple<OPs...> & ops )
{
StackMachine<0,0,false>::eval_pos(const_cast<std::tuple<VALs...>&>(vals),ops);
return std::get<sizeof...(OPs)>(vals);
}
struct OpMul {
static int apply(const int& lhs,const int& rhs) {
return lhs*rhs;
}
};
std::pair< std::tuple< const Vec&, const Vec& > , std::tuple<OpMul> >
operator*(const Vec& lhs,const Vec& rhs)
{
return std::make_pair( std::tuple< const Vec&, const Vec& >( lhs , rhs ) ,
std::tuple<OpMul>( OpMul() ) );
}
template<typename... OPs,typename... VALs>
std::pair< std::tuple< const Vec&, VALs... > , std::tuple<OPs...,OpMul> >
operator*(const Vec& lhs,const std::pair< std::tuple< VALs... > , std::tuple<OPs...> >& rhs)
{
return std::make_pair( std::tuple_cat( rhs.first , std::tuple< const Vec& >(lhs) ) ,
std::tuple_cat( rhs.second , std::tuple<OpMul>( OpMul() ) ) );
}
template<typename... OPs,typename... VALs>
std::pair< std::tuple< const Vec&, VALs... > , std::tuple<OPs...,OpMul> >
operator*(const std::pair< std::tuple< VALs... > , std::tuple<OPs...> >& lhs,
const Vec& rhs)
{
return std::make_pair( std::tuple_cat( lhs.first , std::tuple< const Vec& >(rhs) ) ,
std::tuple_cat( lhs.second , std::tuple<OpMul>( OpMul() ) ) );
}
int main()
{
Vec d,c,b,a;
for( int i = 0 ; i < d.vec.size() ; ++i ) {
a.vec[i] = 10+i;
b.vec[i] = 20+i;
c.vec[i] = 30+i;
d.vec[i] = 0;
}
d = a * b * c * a;
for( int i = 0 ; i < d.vec.size() ; ++i )
std::cout << d.vec[i] << std::endl;
}
で生成されたアセンブラg++-4.6 -O3
(コンパイラがコンパイル時にすべてを計算せず、実際にインスタンスが表示されるように、実行時の依存関係をベクトルの初期化にimull
含める必要がありました。)
imull %esi, %edx
imull 32(%rsp), %edx
imull %edx, %esi
movl 68(%rsp), %edx
imull %ecx, %edx
movl %esi, (%rsp)
imull 36(%rsp), %edx
imull %ecx, %edx
movl 104(%rsp), %ecx
movl %edx, 4(%rsp)
movl 72(%rsp), %edx
imull %ecx, %edx
imull 40(%rsp), %edx
imull %ecx, %edx
movl 108(%rsp), %ecx
movl %edx, 8(%rsp)
movl 76(%rsp), %edx
imull %ecx, %edx
imull 44(%rsp), %edx
imull %ecx, %edx
movl 112(%rsp), %ecx
movl %edx, 12(%rsp)
movl 80(%rsp), %edx
imull %ecx, %edx
imull %eax, %edx
imull %ecx, %edx
movl %edx, 16(%rsp)