17

我已经阅读了一些关于 C++11 中移动语义的描述,我想知道它可以在什么上下文中使用。目前,许多 C++ 数学库使用模板元编程来延迟评估。

如果 M = A + B + C*D,其中 M、A、B、C 和 D 是矩阵,则模板元编程允许避免无用的副本。移动语义是一种更方便的方式来做这些事情吗?

如果不是,在什么上下文中使用移动语义。如果是,与模板元编程相比,这种用途有什么区别/限制?

4

5 回答 5

34

我相信您所说的“模板元编程”的更准确术语是表达式模板

如果您的矩阵动态分配其数据,则移动语义可以帮助将数据从对象传输到对象(包括到/从临时对象)在表达式期间生成,例如:

M = A + B + C*D

另一方面,表达式模板将完全消除临时变量。

如果您的矩阵没有动态分配它们的数据(例如,如果它们是固定大小且很小的),则移动语义根本不会帮助您的性能。

将表达式模板应用于矩阵库将获得最高性能。这也是一个非常困难的实现技术。移动语义实现起来要容易得多,并且可以在表达式模板之外完成(如果有可以转移的内存等资源)。

总之:

移动语义不会消除临时内存,而是会在临时内存之间传输动态分配的内存,而不是重新分配内存。

表达式模板消除了临时性。

于 2012-05-23T18:12:19.263 回答
13

我不是这些优化方面的专家,但据我了解,您正在谈论的延迟评估技术通过在矩阵类型上定义算术运算符来工作,例如A+B+C*D不返回矩阵,它返回一个代理对象可以转换成矩阵。当它被分配给 时会发生这种情况M,并且转换代码将通过库设计者可以想出的最有效的方式计算结果矩阵的每个单元格,避免临时矩阵对象。

所以,假设程序包含M = A + B + C * D;

如果您除了operator+使用 以通常的方式实现之外没有做任何聪明的operator+=事情,那么一旦正常,您就会得到类似的东西,C++03 风格的复制省略已经开始了:

Matrix tmp1 = C;
tmp1 *= D;
Matrix tmp2 = A;
tmp2 += B;
tmp2 += tmp1;
M = tmp2;

通过延迟评估,您可能会得到更多类似的信息:

for (int i = 0; i < M.rows; ++i) {
    for (int j = 0; j < M.cols; ++j) {
        /* not necessarily the best matrix multiplication, but serves to illustrate */
        c_times_d = 0;
        for (int k = 0; k < C.cols; ++k) {
            c_times_d += C[i][k] * D[k][j];
        }
        M[i][j] = A[i][j] + B[i][j] + c_times_d;
    }
}

而“没什么聪明”的代码会做几个单独的加法循环和更多的分配。

据我所知,在这种情况下,移动语义没有多大帮助。您所写的内容中没有任何内容允许我们从 、 或 移动A,所以B我们最终会得到相当于:CD

Matrix tmp1 = C;
tmp1 *= D;
Matrix tmp2 = A;
tmp2 += B;
tmp2 += std::move(tmp1);
M = std::move(tmp2);

所以移动语义除了最后一点之外没有任何帮助,也许右值版本的操作符比普通的更好。如果你写了,还有更多可用的std::move(A) + std::move(B) + std::move(C) * std::move(D),因为我们不必从Cor复制A,但我仍然认为结果不如延迟评估代码好。

基本上,移动语义对延迟评估提供的优化的一些重要部分没有帮助:

1)通过延迟评估,中间结果永远不需要作为完整矩阵实际存在。移动语义不会使编译器免于A+B在某些时候在内存中创建完整的矩阵。

2)通过延迟评估,我们可以M在计算完整个表达式之前开始修改。移动语义并不能帮助编译器重新排序修改:即使编译器足够聪明,可以发现潜在的机会,如果有任何抛出异常的危险,对非临时变量的修改也必须保持正确的顺序,因为如果有A + B + C * D投掷的一部分,然后M必须保持原样。

于 2012-05-23T18:19:45.217 回答
2

他们是两种不同的野兽。移动语义是关于从将要被销毁的值中分配资源。当与大整数(需要动态内存分配)的表达式模板混合时,人们会简单地使用这样的内存,而不是复制即将被销毁的东西。

移动语义对于本质上不可复制的对象(如 fstreams)也很重要,使其可移动是有意义的。

于 2012-05-23T18:02:08.533 回答
0

移动语义适用于对象内管理的资源,用于避免在创建临时对象时不必要地获取/释放资源(即动态分配的内存是一种资源)。

模板元编程对结构进行操作,这些结构在堆栈上分配(因为它需要对操作数进行编译时评估)。您可以使用它来避免可以在编译时计算的操作的运行时计算

于 2012-05-23T18:02:29.553 回答
0

移动语义是动态的,表达式模板不是。您不能对分布在多个语句中的表达式进行表达式模板化,并且其中一些仅在月亮是蓝色时才被评估,而移动语义可以。

于 2012-05-23T19:01:07.060 回答