【问题标题】:C/C++ optimizing away checks to see if a function has already been run beforeC/C++ 优化检查以查看之前是否已经运行过函数
【发布时间】:2012-10-10 13:51:56
【问题描述】:

假设您有一个 C/C++ 函数,它在第一次运行时会以某种方式运行。然后,所有其他时间它都以另一种方式表现(例如,见下文)。第一次运行后,if 语句变得多余,如果速度很重要,可以优化掉。有没有办法进行这种优化?

bool val = true; 

void function1() {

   if (val == true) {
      // do something
      val = false; 
   }
   else {
      // do other stuff, val is never set to true again 
   }

}

【问题讨论】:

  • 这是在自修改代码领域。我怀疑你是否能够直接在 c 或 c++ 中完成
  • 当然可以。 static local boolean 将告诉编译器您希望特定位运行一次且仅一次。
  • 如果这是用于笔记本电脑/台式机 CPU,那么答案是“没关系,因为现在使用的任何 CPU 最多会错误预测少数跳跃,每次浪费约 1ns” .
  • @j_random_hacker 但是,有微控制器、IC、移动设备……即使是一个周期也可能不会好输。 µC 也往往没有分支预测
  • @hippietrail,Hotspot (JVM) 使用自修改代码作为规范,在 java7 中有 INVOKE_DYNAMIC,如果正确实施,它将执行 OP 想要的操作,它是语言的一部分(不是真正的 java,而是java字节码)。 Java 中的static final 倾向于转为常量然后常量折叠,代码需要一些额外的技巧但完全有可能导致代码没有负载val 和后续分支。

标签: c++ c performance optimization


【解决方案1】:

像 g++(我相信 msvc)这样的编译器支持在第一次运行时生成配置文件数据,然后使用该数据更好地猜测最有可能遵循的分支,并进行相应的优化。如果您使用 gcc,请查看 -fprofile-generate 选项。

预期的行为是编译器将优化 if 语句,以便首先对 else 进行排序,从而避免在所有后续调用中执行 jmp 操作,使其与不存在时一样快,尤其是如果你在 else 中的某个地方返回(从而避免跳过“if”语句)

【讨论】:

    【解决方案2】:

    一种可能的方法是编译该函数的两个不同版本(这可以使用模板从源中的单个函数完成),并在运行时使用函数指针或对象来决定。但是,除非您的函数真的很昂贵,否则指针开销可能会超过任何潜在收益。

    【讨论】:

      【解决方案3】:

      只有在确定确实是瓶颈时才应该进行更改。使用分支预测,if 语句可能是即时的,因为它是一个非常可预测的模式。

      也就是说,您可以使用回调:

      #include <iostream>
      using namespace std;
      typedef void (*FunPtr) (void);
      FunPtr method;
      void subsequentRun()
      {
          std::cout << "subsequent call" << std::endl;
      }
      void firstRun()
      {
          std::cout << "first run" << std::endl;
          method = subsequentRun;  
      }
      int main()
      {
          method = firstRun;
          method();
          method();
          method();
      }
      

      产生输出:

      第一次运行
      后续调用
      后续调用

      【讨论】:

      • 我想对这是否真的使事情变得更好或更糟进行基准测试。如果编译器现在必须将调用的目标视为未知,那么让目标包含更少的分支可能会因无法在调用中应用优化而完全胜过。
      • @Ben 绝对。我怀疑会有什么不同。
      【解决方案4】:

      您可以使用函数指针,但无论如何它都需要间接调用:

      void (*yourFunction)(void) = &firstCall;
      
      void firstCall() {
       ..
       yourFunction = &otherCalls;
      }
      
      void otherCalls() {
       ..
      }
      
      void main()
      {
        yourFunction();
      }
      

      【讨论】:

      • 我不确定,但我怀疑这比原始代码慢。
      • 它与大致虚拟通话一样慢。它确实可能更慢,我指定了它需要间接调用的事实。
      • 对,我的意思是他要求避免开销,而不是引入更多*。 (*仍待定,但我很确定情况会更糟。)
      • 间接调用在大多数架构上会更糟,它需要地址的加载已经完成才能获取下一条指令。在原始情况下,分支预测将像 99.9% 一样成功,因此它只花费 val 的负载
      【解决方案5】:

      编译器只能优化编译时已知的内容。

      在您的情况下,val 的值仅在运行时已知,因此无法优化。

      if 测试非常快,您不必担心优化它。

      【讨论】:

        【解决方案6】:

        您可以使用static 成员变量而不是全局变量..

        或者,如果您第一次运行的代码更改了某些内容以供将来使用(例如,打开文件?),您可以使用该更改作为检查来确定是否运行代码(即,检查文件是否打开)。这将为您节省额外的变量。此外,它可能有助于错误检查 - 如果由于某种原因初始更改未被其他操作更改(例如,文件位于不正确删除的可移动媒体上),您的检查可能会尝试重新进行更改。

        【讨论】:

        • 静态比全局有什么优势?
        • 在一个大型项目中,全局变量往往会很快变得混乱。我会说这通常只是一种很好的编码实践,尽管可能有一些内存访问优势..(不要引用我的话)
        • @LuchianGrigore 优势是双重的。首先,缓存局部性。见en.wikipedia.org/wiki/Locality_of_reference。其次,如果你使用局部静态而不是全局,编译器可能更容易推断出它的值不会再次改变,从而进行相应的优化。
        • @Nikos:缓存位置用什么?局部静态离什么近,全局离什么远?
        • @Steve 嗯,这对我来说是个脑残。 var 一开始是静态的,所以无论如何它都会在数据段或 BSS 中结束。没有缓存位置可以应用。如果它不是静态的(当然没有意义,但无论如何),那么将 var 定义在您使用它的位置附近有助于避免缓存未命中。
        【解决方案7】:

        gcc 有一个内置函数,可让您通知实现有关分支预测的信息:

         __builtin_expect 
        

        http://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html

        例如你的情况:

        bool val = true; 
        
        void function1()
        {
            if (__builtin_expect(val, 0)) {
               // do something
               val = false; 
            }
            else {
              // do other stuff, val is never set to true again 
            }
        }
        

        【讨论】:

        • 了解__builtin_expect 很有用,但它在这里的影响可以忽略不计:在分支预测器“意识到”正在发生的事情之前,最多会错误预测少数跳转。当对两种结果之一存在偏见但没有明显的规律模式时,它似乎会更有用。
        • 我在这里看到的最佳答案。没有过早的优化,只是给编译器额外的信息。
        • @j_random_hacker:除非函数调用相距足够远,以至于指令在运行之间被刷新出缓存。另外值得注意的是,这个标志可能会导致重新排序优化以加速预期的分支。
        • 对 __builtin_expect 的补充,如果这是非常频繁使用的代码并且确实没有其他方法可以解决它,我只会添加它,最好将“通常”的情况放在 if - 以及 else 分支中的“很少”情况。可能不会对所有目标平台都有帮助,但可以在某些平台上有所作为。
        • @nneonneo:如果它从缓存中刷新出来,它一定是相对很少运行的,这表明它不值得为我们两个额外的循环而烦恼。不过对于嵌入式工作来说可能仍然值得。
        【解决方案8】:

        进行这种优化的一种方法是将函数一分为二。而不是:

        void function1()
        {
            if (val == true) {
                // do something
                val = false; 
            } else {
               // do other stuff
            }
        }
        

        这样做:

        void function1()
        {
            // do something
        }
        
        void function2()
        {
           // do other stuff
        }
        

        【讨论】:

          【解决方案9】:

          您可以做的一件事是将逻辑放入对象的构造函数中,然后将其定义为static。如果这样的static 对象出现在块作用域中,则构造函数在该作用域执行发生的第一次运行。编译器发出一次性检查。

          您也可以将static 对象放在文件范围内,然后在调用main 之前对其进行初始化。

          我给出这个答案是因为你可能没有有效地使用 C++ 类。

          (关于C/C++,没有这样的语言。有C,也有C++。你是在使用C 语言工作,还必须编译为C++(有时非正式地称为“Clean C”),还是你真的在 C++ 中工作吗?)


          What is "Clean C" and how does it differ from standard C?

          【讨论】:

            【解决方案10】:

            为了保持编译器的独立性,您可以在一个函数中编写 if() 的部分代码,在另一个函数中编写 else{}。几乎所有编译器都优化了if() else{} - 所以,最有可能的是else{} - 因此在if() 中编写偶尔的可执行代码,其余的在else 中调用的单独函数中编写

            【讨论】:

              【解决方案11】:

              如果您想让代码更简洁一些,您可以使用 static 将变量设置为函数的本地变量:

              void function() {
                  static bool firstRun = true;
                  if (firstRun) {
                      firstRun = false;
                      ...
                  }
                  else {
                      ...
                  }
              }
              

              第一次进入函数时,firstRun 为真,并且会持续存在,因此每次调用函数时,firstRun 变量将与之前的实例相同(并且将以后每次都为假)。

              这可以很好地与@ouah 的解决方案一起使用。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2020-02-04
                • 1970-01-01
                • 2019-08-09
                • 2014-10-29
                • 1970-01-01
                • 2018-08-10
                相关资源
                最近更新 更多