功能速度提前或结束时终止

时间:2013-02-01 15:51:39

标签: c++ performance visual-studio

在编写函数时似乎总有两个阵营。

  1. 那些希望在函数中找到1返回的人(通常在最后)
  2. 那些喜欢线性数据流的人(通常有多个回报)
  3. 通常这更像是个人风格,但我想谈谈性能。 在我提出问题之前,让我先提出一个假设的情景:

    让我们说要求说明:

    Function should first try to calc ReturnVal based off of A. (Typical Case)
    If A is unable to be determined then try to find based off of B. 
    If B is unable to be determined then try to find based off of C. 
    If all else fails then return C since that is always known.
    Function should return enum which states which way the value was found.
    

    所以我们说有以下内容:

    enum HowFound
    {
      eWithA,
      eWithB,
      eWithC
    };
    
    HowFound CalcReturn(int& nValue) const;
    

    我的问题 在速度(jmps数量等)方面,您认为编译器可以在大多数情况下更好地优化哪种风格?

    样式1

    HowFound CalcReturn(int& nValue) const
    {
      HowFound howFound = eWithA;
    
      const int valWithB = CalcWithB();
      const int valWithC = CalcWithC();
    
      nValue = CalcWithA( valWithB, valWithC );
      if (nValue == -1)
      {
        nValue = valWithB;
        howFound = eWithB;
        if (nValue == -1)
        {
           nValue = valWithC;
           howFound = eWithC;
        }
      }
    
      return howFound; 
    }
    

    使用样式1,您可以存储返回值,并且不会提前终止该函数。你有一个回报,它位于最后。

    样式2

    HowFound CalcReturn(int& nValue) const
    {
      const int valWithB = CalcWithB();
      const int valWithC = CalcWithC();
    
      nValue = CalcWithA( valWithB, valWithC );
      if (nValue != -1)
      {
        return eWithA;
      }
    
      if (valWithB != -1)
      {
        nValue = valWithB;
        return eWithB;
      }
    
      nValue = valWithC;
      return eWithC;
    }
    

    对于样式2,数据流更“线性”。一旦找到该值就退出该功能。这意味着函数中有更多的终止点。

    免责声明:显然每种风格都可以调整一些,但我的问题仍然保持不变。此外,是的,我可以写两个函数并检查反汇编(我有),但随着更多的“细节”被添加,结果会发生变化。我的问题是哪种风格的表现更好(如果有的话)。

    谢谢!

4 个答案:

答案 0 :(得分:1)

好吧,虽然编译器所做的优化变化很大,但两种代码都会降低到或多或少同等效率的代码。因此,执行时间或多或少相同。但是如果你正在寻找节省时钟周期,你应该测试它。在这种情况下,故事并没有在这里结束。它还取决于硬件优化,尤其是分支预测器。因此,如果您了解值的频率和所采用的路径,并且您正在寻找在时钟周期级别重建的保存,那么代码来适应路径将有所帮助。如果你不是在寻找这个,那么请考虑可读性。

答案 1 :(得分:0)

在大多数情况下,编译器会为两种样式生成相同的代码,但我注意到您的特定实现可能效率较低(最公平的比较将在else中分配;您的分配然后重新分配)。

但是,当然,您的编译器可能会有所不同。

答案 2 :(得分:0)

编译器会将您的两个示例翻译成相同的代码。它更多的是“哪个更易于阅读”[这实际上取决于您的代码如何与算法的实际描述相匹配]。

编辑:我应该说“任何体面的,优化的编译器” - 一个非常基本的,优化不佳的编译器实际上可以完全按照你的要求做而不重新安排任何东西,从而最终在一个案例中比另一个更好/更差。但是只要你不故意编写代码使其不必要地执行冗长的操作(例如在长字符串上调用strlen(),或者is_prime(101218819)或类似的东西,然后不使用该值,你应该得到相同的结果)。与往常一样,如果您知道这是代码的一个重要部分[基于分析],那么请务必尝试使用您为项目使用的编译器和设置,并查看它是否有任何区别 - 一如既往,询问互联网不能代替对代码的关键部分进行基准测试。

答案 3 :(得分:-1)

https://stackoverflow.com/faq#dontask

由于以下原因,似乎这个问题可能不合适:

  • 没有实际问题需要解决:“我很好奇其他人是否觉得我这么做。”
  • 这是一个伪装成一个问题的咆哮:“______很糟糕,我是对的吗?”

除此之外,OP还没有真正考虑过这个问题。

这是第三种风格:

 HowFound CalcReturn(int& nValue) const
 {
   const int valWithB = CalcWithB();
   const int valWithC = CalcWithC();
   HowFound howFound = eWithC;
   nValue = CalcWithA( valWithB, valWithC );
   if (nValue != -1)
   {
     howFound= eWithA;
   }
   else if (valWithB != -1)
   {
     nValue = valWithB;
     howFound = eWithB;
   } 
   else {
     nValue = valWithC;
   }
   return howFound;
 }

这种风格是A还是B?无论线性的直觉如何,它似乎都是线性。它有1个返回,并且很可能所有3个函数都编译为相同的代码。虽然对于一些现代编译器,NRVO使用单个出口点(http://msdn.microsoft.com/en-us/library/ms364057%28v=vs.80%29.aspx#nrvo_cpp05_topic3,例如4)可以更好

我增加或减少了复杂性吗?

这更快还是更慢?

我可以通过多少其他方式转换此代码?

如何确定哪个更易于维护?

我可以说,只需1次返回就可以将函数转换为另一个函数,这必然会使代码同等或更复杂吗?

无论如何,更复杂的是什么意思?

其中一些是开放式问题......