表达模板:提高评估表达式的性能?

Jac*_*ern 5 c++ templates expression simplify

通过表达模板技术,一个矩阵表达式就像

D = A*B+sin(C)+3.;
Run Code Online (Sandbox Code Playgroud)

就计算性能而言,它与手写for循环几乎相同.

现在,假设我有以下两个表达式

D = A*B+sin(C)+3.;
F = D*E;
cout << F << "\n";
Run Code Online (Sandbox Code Playgroud)

在表达模板的"经典"实现中,计算性能将与for顺序中的两个循环的计算性能几乎相同.这是因为在=遇到运算符后立即计算表达式.

我的问题是:是否有任何技术(例如,使用占位符?)来识别D实际上未使用的值,并且感兴趣的值是唯一的元素F,因此只有表达式

F = E*(A*B+sin(C)+3.);
Run Code Online (Sandbox Code Playgroud)

评估并且整个性能等同于单个for循环的性能?

当然,这种假设技术也应该能够返回来评估表达

D = A*B+sin(C)+3.;
Run Code Online (Sandbox Code Playgroud)

如果稍后在代码D中需要值.

预先感谢您的任何帮助.

编辑:结果试验了Evgeny提出的解决方案

原始说明:

Result D=A*B-sin(C)+3.;
Run Code Online (Sandbox Code Playgroud)

计算时间:32ms

两步说明:

Result Intermediate=A*B;
Result D=Intermediate-sin(C)+3.;
Run Code Online (Sandbox Code Playgroud)

计算时间:43ms

解决方案auto:

auto&& Intermediate=A*B;
Result D=Intermediate-sin(C)+3.;
Run Code Online (Sandbox Code Playgroud)

计算时间:32ms.

总之,auto&&能够恢复单个指令案例的原始计算时间.

编辑:根据Evgeny的建议总结相关链接

复制Elision

汽车告诉我们什么

C++中的通用引用11

C++ Rvalue引用说明

C++和2012年之后:Scott Meyers - C++中的通用引用11

Evg*_*yuk 5

当您将结果保存到某些特殊类型时,通常会发生表达式模板的评估:

Result D = A*B+sin(C)+3.;
Run Code Online (Sandbox Code Playgroud)

表达式的结果类型:

A*B+sin(C)+3.
Run Code Online (Sandbox Code Playgroud)

不是Result,但它可以转换为Result.并且在这种转换期间进行评估.


我的问题是:是否有任何技术(例如,使用占位符?)来识别D的值实际上是未使用的

这种"转型":

Result D = A*B+sin(C)+3.;
Result F = D*E;
Run Code Online (Sandbox Code Playgroud)

Result F = (A*B+sin(C)+3.)*E;
Run Code Online (Sandbox Code Playgroud)

当您不评估时,可以使用D.为此,通常您应该捕获D,因为它是真实的表达式类型.例如,在auto的帮助下:

auto &&D = A*B+sin(C)+3.;
Result F = D*E;
Run Code Online (Sandbox Code Playgroud)

但是,你应该小心 - 有时表达式模板会捕获对它的操作数的引用,如果你有一些rvalue会在它的表达式之后到期:

auto &&D = A*get_large_rvalue();
// At this point, result of **get_large_rvalue** is destructed
// And D has expiried reference
Result F = D*E;
Run Code Online (Sandbox Code Playgroud)

其中get_large_rvalue是:

LargeMatrix get_large_rvalue();
Run Code Online (Sandbox Code Playgroud)

它的结果是rvalue,它在调用get_large_rvalue时在完整表达式结束时到期.如果表达式中的某些内容将存储指针/引用(以供以后评估),并且您将"推迟"评估 - 指针/引用将比指向/引用的对象更长.

为了防止这种情况,你应该这样做:

auto &&intermediate = get_large_rvalue(); // it would live till the end of scope
auto &&D = A*intermediate ;
Result F = D*E;
Run Code Online (Sandbox Code Playgroud)

我不熟悉C++ 11,但据我所知,auto要求编译器从初始化时确定变量的类型

对,就是这样.这称为类型推断/演绎.

C++ 98/03只有模板函数的类型推导,在C++ 11中有auto.

你知道CUDA和C++ 11如何相互影响吗?

我没有使用过CUDA(虽然我使用的是OpenCL),但我想在使用C++ 11的主机代码中没有任何问题.设备代码中可能不支持某些C++ 11功能,但出于您的目的 - 您只需要在主机代码中自动执行

最后,有没有可能只有C++?

你的意思是预C++ 11吗?即C++ 98/C++ 03?是的,它是可能的,但它有更多的语法噪音,也许这是拒绝它的理由:

// somehwhere
{
    use_D(A*B+sin(C)+3.);
}
// ...
template<typename Expression>
void use_D(Expression D) // depending on your expression template library
                         //   it may be better to use (const Expression &e)
{
    Result F = D*E;
}
Run Code Online (Sandbox Code Playgroud)

我现在在Windows下使用CUDA/Visual Studio 2010.你可以推荐一个编译器/工具集/环境,让OS'在我感兴趣的框架中使用C++ 11(GPGPU和CUDA,你知道吗)

MSVC 2010确实支持C++ 11的某些部分.特别是它支持auto.所以,如果你只需要来自C++ 11的自动 - MSVC2010就行了.

但是,如果您可以使用MSVC2012 - 我建议坚持使用它 - 它有更好的C++ 11支持.

此外,技巧auto && intermediate = get_large_rvalue(); 似乎对第三方用户"不透明"(不应该知道这样的问题).我对吗?还有其他选择

如果表达式模板存储对某些值的引用,则推迟它的评估.你应该确定它的所有引用都在评估的地方存在.使用您想要的任何方法 - 它可以在没有自动的情况下完成,例如:

LargeMatrix temp = get_large_rvalue();
Run Code Online (Sandbox Code Playgroud)

或者甚至可能是全局/静态变量(不太喜欢的方法).

最后一条评论/问题:使用auto && D = A*B + sin(C)+3.似乎我应该重载operator = for两个表达式之间的赋值,对吧?

不,这种形式不需要也不需要复制/移动赋值运算符或复制/移动构造函数.

基本上它只是命名临时值,并将其寿命延长到范围的末尾.检查这个.

但是,如果你要使用另一种形式:

auto D = A*B+sin(C)+3.;
Run Code Online (Sandbox Code Playgroud)

在这种情况下,可能需要复制/移动/转换构造函数才能编译(尽管编译器可以通过使用Copy Ellision优化实际复制)

此外,在使用auto(对于中间表达式)和Result强制计算之间切换对于第三方用户来说似乎是不透明的.还有其他选择

我不确定是否有其他选择.这是表达模板的本质.当你在表达式中使用它们时 - 它们会返回一些内部中间类型,但是当你存储到某些"特殊"类型时 - 会触发评估.