Visual C++ 2005 中的命名返回值优化

摘要:说明 Visual C++ 编译器如何消除各种情况下多余的 Copy 构造函数调用和析构函数调用。


Microsoft 一直在为 Visual C++ 优化编译器寻找新的技术和优化方法,以便尽可能地为编程人员提供更高的性能。本文将说明编译器如何尝试在各种情况下消除多余的 Copy 构造函数调用和析构函数调用。


通常,当一个方法返回对象的一个实例时,将创建一个临时对象,并通过复制构造函数将其复制到目标对象。C++ 标准允许省略复制构造函数的一部分(即使这样做会导致不同的程序行为),这对编译器将这两个对象作为一个对象进行处理具有副作用。Visual C++ 8.0 编译器充分利用了标准提供的灵活性,并添加了一个新功能:命名返回值优化(Named Return Value Optimization,NRVO)。NRVO 消除了复制构造函数和析构函数基于堆栈的返回值。这样进行优化,可以去掉多余的复制构造函数调用和析构函数调用,从而提高整体性能。注意,这会导致优化的程序和未优化的程序之间的不同行为(请参阅优化的副作用部分)。


在某些情况下不会发生优化(请参阅优化限制部分中的示例)。比较常见的情况有:


返回不同命名对象的不同路径。


引入 EH 状态的多个返回路径(即使所有路径上返回的都是同一个命名对象)。


在内联 asm 块内引用了返回的命名对象。


*
本页内容
 优化描述 优化描述 
 代码示例 代码示例 
 优化限制 优化限制 
 优化的副作用 优化的副作用 
 参考资料 参考资料 
优化描述
图 1 中的一个简单示例说明了这种优化及其实现过程:


A MyMethod (B &var)
{
   A retVal;
   retVal.member = var.value + bar(var);
   return retVal;
}
图 1. 原始代码


使用以上函数的程序可能具有以下结构:


valA = MyMethod(valB);
MyMethod 返回的这个值是在 ValA 使用的隐藏参数所指向的内存空间中创建的。当我们公开该隐藏参数,并显式显示构造函数和析构函数时,图 1 中的函数将如下所示:


A MyMethod (A &_hiddenArg, B &var)
{
   A retVal;
   retVal.A::A(); // constructor for retVal
   retVal.member = var.value + bar(var);
   _hiddenArg.A::A(retVal);  // the copy constructor for A
   return;
retVal.A::~A();  // destructor for retVal


}
图 2. 没有 NRVO 的隐藏参数代码(伪代码)


您可能会从以上代码中发现一些可以优化的地方。基本思想是,消除基于堆栈的临时值 (retVal),并使用隐藏参数。因此,这将消除基于堆栈的值的复制构造函数和析构函数。以下是基于 NRVO 的优化代码:


A MyMethod(A &_hiddenArg, B &var)
{
   _hiddenArg.A::A();
   _hiddenArg.member = var.value + bar(var);
   Return
}
图 3. 有 NRVO 的隐藏参数代码(伪代码)


  返回页首 
代码示例
Sample 1:简单示例


#include 
class RVO
{
public:
       
            RVO(){printf("I am in constructor\n");}
            RVO (const RVO& c_RVO) {printf ("I am in copy constructor\n");}
            ~RVO(){printf ("I am in destructor\n");}
            int mem_var;       
};
RVO MyMethod (int i)
{
            RVO rvo;
            rvo.mem_var = i;
            return (rvo);
}
int main()
{
            RVO rvo;
            rvo=MyMethod(5);
}
图 4. Sample1.cpp


编译 sample1.cpp 时启用以及不启用 NRVO 将产生不同的行为。


若不启用 NRVO (cl /Od sample1.cpp),预期输出将是:


I am in constructor
I am in constructor
I am in copy constructor
I am in destructor
I am in destructor
I am in destructor
若启用 NRVO (cl /O2 sample1.cpp),预期输出将是:


I am in constructor
I am in constructor
I am in destructor
I am in destructor
Sample 2:比较复杂的示例


#include 
class A {
  public:
    A() {printf ("A: I am in constructor\n");i = 1;}
    ~A() { printf ("A: I am in destructor\n"); i = 0;}
    A(const A& a) {printf ("A: I am in copy constructor\n"); i = a.i;}
    int i, x, w;
};
 class B {
  public:
    A a;
    B()  { printf ("B: I am in constructor\n");}
    ~B() { printf ("B: I am in destructor\n");}
    B(const B& b) { printf ("B: I am in copy constructor\n");}
};
A MyMethod()
{
    B* b = new B();
    A a = b->a;
    delete b;
    return (a);
}
int main()
{
    A a;
    a = MyMethod();
}
图 5. Sample2.cpp


不启用 NRVO (cl /Od sample2.cpp) 的输出将如下所示:


A: I am in constructor
A: I am in constructor
B: I am in constructor
A: I am in copy constructor
B: I am in destructor
A: I am in destructor
A: I am in copy constructor
A: I am in destructor
A: I am in destructor
A: I am in destructor
而加入 NRVO 优化 (cl /O2 sample2.cpp) 后,输出将变成:


A: I am in constructor
A: I am in constructor
B: I am in constructor
A: I am in copy constructor
B: I am in destructor
A: I am in destructor
A: I am in destructor
A: I am in destructor
  返回页首 
优化限制
某些情况下,不会加入优化。以下就是这种限制的几个示例。


Sample 3:异常示例


遇到异常时,隐藏参数必须在它所替换的临时(对象)范围内进行析构。以下示例进行说明:


//RVO class is defined above in figure 4
#include 
RVO MyMethod (int i)
{
            RVO rvo;
            rvo.mem_var = i;
            throw "I am throwing an exception!";
            return (rvo);
}
int main()
{
            RVO rvo;
            try 
            {
                        rvo=MyMethod(5);
            }
            catch (char* str)
            {
                        printf ("I caught the exception\n");
            }
}
图 6. Sample3.cpp


若没有启用 NRVO (cl /Od /EHsc sample3.cpp),预期输出将是:


I am in constructor
I am in constructor
I am in destructor
I caught the exception
I am in destructor
若去掉“throw”的注释说明,输出将变成:


I am in constructor
I am in constructor
I am in copy constructor
I am in destructor
I am in destructor
I am in destructor
现在,若去掉“throw”的注释说明,并触发 NRVO,输出将如下所示:


I am in constructor
I am in constructor
I am in destructor
I am in destructor
也就是说,图 6 所示的 sample3.cpp 在使用和未使用 NRVO 的情况下具有相同的行为。


Sample 4:不同命名对象的示例


要充分利用优化,所有退出路径必须返回同一个命名对象。为了说明这一点,请考虑 sample4.cpp:


#include 
class RVO
{
public:
       
            RVO(){printf("I am in constructor\n");}
            RVO (const RVO& c_RVO) {printf ("I am in copy constructor\n");}
            int mem_var;       
};
RVO MyMethod (int i)
{
            RVO rvo;
            rvo.mem_var = i;
      if (rvo.mem_var == 10)
         return (RVO());
            return (rvo); 
}
int main()
{
            RVO rvo;
            rvo=MyMethod(5);
}
图 7. Sample4.cpp


启用优化后的输出 (cl /O2 sample4.cpp) 与未启用任何优化的输出 (cl /Od sample.cpp) 相同。这是因为并非所有返回路径都返回同一个命名对象,所以实际上没有发生 NRVO。


I am in constructor
I am in constructor
I am in copy constructor
如果将以上示例更改为在所有退出路径中返回 rvo(如 图 8. Sample4_modified.cpp 所示),那么优化将消除复制构造函数:


#include 
class RVO
{
public:
       
            RVO(){printf("I am in constructor\n");}
            RVO (const RVO& c_RVO) {printf ("I am in copy constructor\n");}
            int mem_var;       
};
RVO MyMethod (int i)
{
            RVO rvo;
           if (i==10)
         return (rvo);
      rvo.mem_var = i;
            return (rvo); 
}
int main()
{
            RVO rvo;
            rvo=MyMethod(5);
}
图 8. 修改 Sample4_Modified.cpp 以利用 NRVO


输出 (cl /O2 Sample4_Modified.cpp) 将如下所示:


I am in constructor
I am in constructor
Sample 5:EH 限制示例


下面的图 9 展示与图 8 相同的示例,但向 RVO 类中加入了析构函数。拥有多个返回路径并引入这样一个析构函数将在函数中创建 EH 状态。由于编译器跟踪机制的复杂性,它在跟踪需要析构的对象时,会回避返回值优化。事实上,这是 Visual C++ 2005 需要在未来改进的地方。


//RVO class is defined above in figure 4
#include 
RVO MyMethod (int i)
{
            RVO rvo;
           if (i==10)
         return (rvo);
      rvo.mem_var = i;
            return (rvo); 
}
int main()
{
            RVO rvo;
            rvo=MyMethod(5);
}
图 8. Sample5.cpp


编译 Sample5.cpp 时使用和不使用优化将产生相同的结果:


I am in constructor
I am in constructor
I am in copy constructor
I am in destructor
I am in destructor
I am in destructor
为了充分利用 NRVO,请将 MyMethod 更改为以下代码,以尝试消除这种情况下的多个返回点:


RVO MyMethod (int i)
{
            RVO rvo;
      if (i!=10)
         rvo.mem_var = i;
      return(rvo);  
}
Sample 6:内联 asm 限制


编译器回避执行 NRVO 的另一个情况是,在内联 asm 块中引用了命名返回对象。为了说明这一点,请考虑以下示例:


#include 
//RVO class is defined above in figure 4
RVO MyMethod (int i)
{
            RVO rvo;
__asm {
      mov eax,rvo   //comment this line out for RVO to kick in
      mov rvo,eax //comment this line out for RVO to kick in
          }
            return (rvo); 
}
int main()
{
            RVO rvo;
            rvo=MyMethod(5);
}
图 9. sample6.cpp


编译 sample6.cpp 时启用优化 (cl /O2 sample6.cpp) 仍然无法利用 NRVO。这是因为,实际上在内联 asm 块中引用了返回的对象。因此,无论是否使用了优化,输出都将如下所示:


I am in constructor
I am in constructor
I am in copy constructor
I am in destructor
I am in destructor
I am in destructor
从该输出中我们不难发现,并没有消除复制构造函数调用和析构函数调用。如果去掉 asm 块的注释说明,就会消除这些调用。


  返回页首 
优化的副作用
编程人员应该认识到,这样的优化可能会影响应用程序的流程。以下示例说明这一副作用:


#include 
int NumConsCalls=0;
int NumCpyConsCalls=0;
class RVO
{
public:
       
            RVO(){NumConsCalls++;}
            RVO (const RVO& c_RVO) {NumCpyConsCalls++;}
};
RVO MyMethod ()
{
            RVO rvo;
            return (rvo); 
}
void main()
{
           RVO rvo;
           rvo=MyMethod();
       int Division = NumConsCalls / NumCpyConsCalls;
       printf ("Constructor calls / Copy constructor calls = %d\n",Division);
}
图 10. sample7.cpp


编译 sample7.cpp 时不启用优化 (cl /Od sample7.cpp) 将产生大多数用户预期的结果。构造函数调用了两次,而复制构造函数调用了一次,因此除法运算 (2/1) 的结果为 2。


Constructor calls / Copy constructor calls = 2
从另一方面来看,如果编译以上代码时启用了优化 (cl /O2 sample7.cpp),则将加入 NRVO,因而会消除复制构造函数调用。因此,NumCpyConsCalls 将为 ZERO,导致除法出现 ZERO 异常,如果该异常没有正确处理(如 sample7.cpp 所示),可能会导致应用程序崩溃。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值