假设我有一个类型A的对象。对于A类型的任何函数考虑这种情况 - > A(即获取类型A的对象并返回另一个类型为A的对象):
foo = func(foo)
此处,最简单的情况是将func(foo)
的结果复制到foo
。
是否可以对此进行优化:
foo
在func
使用的语言没有限制。我想知道的是语言必须具有哪些约束和属性来实现这样的优化。是否存在执行此类优化的现有语言?
示例(伪代码):
type Matrix = List<List<int>>
Matrix rotate90Deg(Matrix x):
Matrix result(x.columns, x.rows) #Assume it has a constructor which takes as args the num of rows, and num of cols.
for (int i = 0; i < x.rows; i++):
for (int j = 0; j < x.columns; j++):
result[i][j] = x[j][i]
return result
Matrix a = [[1,2,3],[4,5,6],[7,8,9]]
a = rotate90Deg(a)
在这里,是否可以优化代码,使其不为新矩阵(结果)分配内存,而只是修改传递的原始矩阵。
答案 0 :(得分:2)
首先,您必须意识到某些操作本身不可能就地计算。矩阵 - 矩阵乘法就是一个例子,rotate90Deg
属于这个类别,因为这样的操作实际上是适当乘法矩阵的矩阵乘法。
现在,就您的示例而言,您实际编写了一个矩阵转置函数。由于您要交换数字对,因此Matrix转置可以就地完成,但我怀疑任何编译器都可以自动检测并优化它。实际上,为了获得巨大的性能提升,可以使用许多很多技巧来优化矩阵转置,以便对缓存友好。然而,通过一个天真的实现,你几乎肯定会得到与Aditya Kumar在他的回答中描述的非常相似的东西。
正如我之前使用“天真”这个词预示的那样,程序员可以通过高级模板和其他元编程技术,哄骗编译器以极其优化的方式内联大量内容。 (至少在C ++中,也许还有其他允许你重载operator =
的语言。)对于那些对如何完成以及涉及的内容进行案例研究感兴趣的人,请查看Eigen matrix library,以及它如何处理像u = v + w;
这样的简单操作,其中三个变量都是浮点矩阵。以下是关键点的简要概述。
一个天真的实现会重载operator+
以返回临时和operator=
来临时复制到结果。当然,在C ++ 11中,通过移动构造函数很容易在赋值期间避免最终复制,但是如果你在右侧有多个运算符,那么你仍然会有不必要的临时副本,如{ {1}}因为每个操作符/方法都会返回一个临时的,并且必须循环该临时操作才能处理下一个操作符。
长话短说,Eigen所做的不是在相应的函数调用发生时执行每个操作,调用返回一个模板化的算子,只是描述操作需要发生,并且所有实际工作最终都发生在u = 3.15f * u.transposed() + 5.0f;
中,从而使编译器能够发出一个内联循环,仅用于遍历数据一次并真正就地进行操作。
答案 1 :(得分:1)
是的,这是可能的,这个优化至少由C ++ 11(内联)提供。
稍微解释一下优化。
e.g。
foo_t foo;
foo = func(foo); // #1
foo_t func(foo_t foo1) {
foo_t new_foo;
// operate on new_foo by using foo1
return new_foo;
}
正在制作foo_t
的三个实例:
foo
被复制并作为foo1
传递给func
new_foo
已创建。new_foo
的内容复制到foo
; ,将new_foo
分配给foo
醇>
如果存在一些不变量,则可以删除所有三个副本。
foo
(传递给函数的参数以后永远不会使用相同的原始值。这相当于说#{1}}在#1行'死'。这是在这里建立的正在重新分配foo
。foo
中对象new_foo
的范围的生命周期不会延长函数func
的生命周期。这也在这里建立为func
的创建方式,它将在堆栈上,堆栈中对象的生命周期与创建对象的函数的生命周期相同。在C ++中,可以使用内联函数new_foo
来实现。内联后,代码基本上会是这样的。
func
尽管如此,C ++提供内联作为语言功能,但几乎所有优化编译器都会在最近进行内联。
现在,这取决于您在`foo_t foo;`
`foo_t new_foo;`
`// operate on new_foo by using foo`
`foo = new_foo;`
和new_foo
上执行的操作类型是否会优化此额外foo
。对于某些数据类型,它是微不足道的(编译器可以执行'复制传播',然后执行'死代码消除'以完全删除new_foo
。