让我们看一下表达式模板的一个特殊好处: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 个循环。意味着我放了一个临时的,然后马上读回来。对于大向量,这超出了缓存。这比表达式模板更糟糕。他们可以在 1 个循环中完成整个事情。ET 可以执行上述代码,相当于:
for(int i=0 ; i < vec_length ; ++i)
d[i] = a[i] + b[i] + c[i];
我想知道 lambda 与移动语义或任何其他新功能是否可以与 ET 一样好。有什么想法吗?
编辑:
基本上,使用 ET 技术,编译器构建一个类似于代数表达式及其类型系统的解析树。这棵树由内部节点和叶节点组成。内部节点表示操作(加法、乘法等),叶节点表示对数据对象的引用。
我试图以堆栈机器的方式思考整个计算过程:从操作堆栈中获取一个操作,然后从参数堆栈中提取下一个参数并评估该操作。将结果放回堆栈等待操作。
为了表示这两个不同的对象(操作堆栈和数据叶堆栈),我将std::tuple
用于操作的 a 和
std::tuple
用于数据叶的 a 捆绑到一个std::pair<>
. 最初我使用了 a std:vector
,但这导致了运行时开销。
整个过程分为两个阶段: 堆栈机器初始化,其中操作和参数堆栈被初始化。以及通过将配对容器分配给向量来触发的评估阶段。
我创建了一个类Vec
,它包含一个私有array<int,5>
(有效负载)并且具有一个采用“表达式”的重载赋值运算符。
全局operator*
对于 take 和 "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
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)