讓我們看的表達模板之一特別的益處:ET的可用於避免在存儲器矢量大小的臨時對象中出現的過載操作符,如:表達模板和C++ 11
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這個函數的返回語句應用移動語義。矢量的副本。這是一場勝利。
但是,如果我看一個簡單的語句,比如
d = a + b + c;
我看到上面的函數被調用兩次(兩個operator+
),而最終的分配可以通過移動語義來完成。
共執行2個循環。意思是我暫時放了一張,然後馬上閱讀。對於大型媒體而言,這會脫離緩存。這比表達式模板更糟糕。他們可以在一個循環中完成整個事情。專家組可以執行上述的等效代碼:
for(int i=0 ; i < vec_length ; ++i)
d[i] = a[i] + b[i] + c[i];
我想用移動語義或任何其他新的功能lambda表達式一起是否能夠像外星人一樣好。有什麼想法嗎?
編輯:
基本上,使用ET技術編譯器生成解析樹 類似於代數表達式與它的 類型系統。該樹由內部節點和葉節點組成。內部節點表示操作(加法,乘法等)和葉節點表示對數據對象的引用。
我試圖用一個 堆棧機器的方式來思考整個計算過程:從操作堆棧中取一個操作並從參數堆棧中取出下一個參數並評估操作。 將結果放回堆棧等待操作。
爲了表示這兩個不同的對象(操作堆棧和數據 葉棧)我捆綁在一起的操作的std::tuple
和 std::tuple
用於數據離開成std::pair<>
。最初,我 使用std:vector
,但導致運行時間開銷。
整個過程分兩個階段:初始化操作和參數堆棧的堆棧機初始化 。並且通過將配對的容器 分配給矢量來觸發評估階段。
我創建的類Vec
保持的私人array<int,5>
(所述 有效載荷),並且設有一個重載的賦值運算符 取「表達」。
全球operator*
超載爲採取 Vec
和「表達」,使正確處理同樣的情況下 我們已經不僅僅是a*b
更多的所有組合。(請注意,我換這個 教育爲例,乘法 - 基本上可以快速識別 的imull
在彙編。)
什麼是評估前先進行啓動的「提取」的 值了參與Vec
的對象並初始化參數 堆棧。這是必要的,以避免不同類型的對象圍繞着:可轉位向量和不可索引的結果。這就是 Extractor
的用途。再好的事情:在這種情況下使用的變量模板導致沒有運行時開銷(所有這些都在編譯時間 完成)。
整件事情奏效。該表達式很好地評估(我也 添加了,但這是遺漏在這裏,以適應代碼)。在 以下,您可以看到彙編器輸出。只是原始計算,就像你想要的那樣:與ET技術相提並論。
Upshot。 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
instaructions。)
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)
您應該查看[copy elision and RVO](http://en.wikipedia.org/wiki/Copy_elision)。此外,通過值傳遞其中一個向量而不是製作自己的'tmp'副本可能會有所幫助。 – juanchopanza 2012-08-04 13:39:15
按價值傳遞無法幫助(仍然複製)。 (N)RVO無助於消除額外的循環 – ritter 2012-08-04 13:43:41
@Frank:你確定嗎?這是RVO的強有力候選人。 – akappa 2012-08-04 13:47:43