5

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

D = A*B+sin(C)+3.;

就计算性能而言,它几乎等同于手写for循环。

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

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

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

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

F = E*(A*B+sin(C)+3.);

被评估并且整体性能相当于单个for循环的性能?

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

D = A*B+sin(C)+3.;

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

预先感谢您的任何帮助。

编辑:实验 Evgeny 建议的解决方案的结果

原指令:

Result D=A*B-sin(C)+3.;

计算时间:32ms

两步说明:

Result Intermediate=A*B;
Result D=Intermediate-sin(C)+3.;

计算时间:43ms

解决方案auto

auto&& Intermediate=A*B;
Result D=Intermediate-sin(C)+3.;

计算时间:32ms。

综上所述,auto&&使能恢复单指令情况的原始计算时间。

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

复制省略

汽车告诉我们什么

C++11 中的通用引用

C++ 右值引用解释

C++ 及以后 2012:Scott Meyers - C++11 中的通用引用

4

2 回答 2

5

当您将结果保存为某种特殊类型时,通常会评估表达式模板,例如:

Result D = A*B+sin(C)+3.;

表达式的结果类型:

A*B+sin(C)+3.

不是Result,而是可以转换为Result的东西。并且评估发生在这种转换过程中。


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

这种“转化”:

Result D = A*B+sin(C)+3.;
Result F = D*E;

Result F = (A*B+sin(C)+3.)*E;

当您不评估 D 时是可能的。为此,通常您应该将 D 捕获为真实的表达式类型。例如,在auto的帮助下:

auto &&D = A*B+sin(C)+3.;
Result F = D*E;

但是,您应该小心 - 有时表达式模板会捕获对其操作数的引用,并且如果您有一些在它的表达式之后会过期的右值:

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;

其中get_large_rvalue是:

LargeMatrix get_large_rvalue();

它的结果是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;

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

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

C++98/03 仅对模板函数进行类型推导,在 C++11 中有auto

你知道 CUDA 和 C++11 是如何交互的吗?

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

最后,只有 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;
}

我现在在 Windows 下使用 CUDA/Visual Studio 2010。您能否为两个操作系统推荐一个编译器/工具集/环境,以便在我感兴趣的框架中使用 C++11(GPGPU 和 CUDA,你知道吗)

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

但是如果你可以使用 MSVC2012——我建议你坚持使用它——它有更好的 C++11 支持。

此外,技巧 auto &&intermediate = get_large_rvalue(); 似乎对第三方用户不是“透明的”(不应该知道这样的问题)。我对吗?有什么选择吗?

如果表达式模板存储了对某些值的引用,并且您推迟了它的评估。你应该确保它的所有引用在评估的地方都是有效的。使用您想要的任何方法 - 无需自动即可完成,例如:

LargeMatrix temp = get_large_rvalue();

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

最后的评论/问题:使用 auto &&D = A*B+sin(C)+3.; 看来我应该重载 operator= 以在两个表达式之间进行赋值,对吧?

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

基本上它只是命名临时值,并将其生命周期延长到范围的末尾。检查这个 SO

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

auto D = A*B+sin(C)+3.;

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

此外,在使用 auto (用于中间表达式)和 Result 之间切换以强制计算似乎对第三方用户不透明。有什么选择吗?

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

于 2013-04-06T21:30:56.330 回答
2

在 c++11 中,您可以使用auto

auto D = A*B+sin(C)+3.;

假设您正在使用表达式模板,则 D 的类型将为<some template type which represents an expression>. 现在,您必须小心使用它,因为您正在节省一些内存(无需为矩阵分配空间),但取决于您如何使用它,这可能不是最好的。

想一想

F = D*E

在计算 D*E 时,需要多次“访问”元素 D[i][j](实际上是 n 次,其中 n 是矩阵的大小)。如果 D 是纯矩阵类型,这没问题。如果 D 是一个表达式,那么您正在多次评估它。

相反,做

F = D + E

很好。

想一想:你不能F = E*(A*B+sin(C)+3.);只使用两个嵌套循环来编写。

于 2013-04-06T21:26:25.400 回答