【问题标题】:Which compiles to faster code: "n * 3" or "n+(n*2)"?哪个编译为更快的代码:“n * 3”或“n+(n*2)”?
【发布时间】:2010-09-08 09:36:09
【问题描述】:

哪个编译成更快的代码:“ans = n * 3”或“ans = n+(n*2)”?

假设 n 是 int 或 long,并且它在现代 Win32 Intel 机器上运行。

如果涉及一些取消引用,这会有所不同,也就是说,其中哪一个会更快?

长一个; 长 *pn; 长答案; ... *pn = some_number; 答案 = *pn * 3;

或者

答案 = *pn+(*pn*2);

或者,由于优化编译器在任何情况下都可能考虑到这一点,因此无需担心吗?

【问题讨论】:

    标签: c++ c optimization compiler-construction performance


    【解决方案1】:

    不难发现编译器对您的代码做了什么(我在这里使用的是 DevStudio 2005)。用以下代码编写一个简单的程序:

    int i = 45, j, k;
    j = i * 3;
    k = i + (i * 2);
    

    在中间行放置一个断点并使用调试器运行代码。触发断点后,右键单击源文件并选择“Go To Disassembly”。您现在将看到一个显示 CPU 正在执行的代码的窗口。在这种情况下,您会注意到最后两行产生完全相同的指令,即“lea eax,[ebx+ebx*2]”(在这种特殊情况下不是位移和加法)。在现代 IA32 CPU 上,由于 CPU 的流水线特性,在过早使用修改值时会产生惩罚,因此执行直接 MUL 可能比位移位更有效。

    这说明了 aku 的意思,即编译器足够聪明,可以为您的代码选择最佳指令。

    【讨论】:

    • 我不认为管道是个问题。运算器可能一步就可以在内部处理 ebx+ebx*2。
    【解决方案2】:

    只要您使用的是体面的优化编译器,只需编写易于编译器理解的代码。这使编译器更容易执行巧妙的优化。

    您提出这个问题表明优化编译器比您更了解优化。所以相信编译器。使用n * 3

    也请查看this answer

    【讨论】:

      【解决方案3】:

      它不在乎。我认为还有更重要的事情需要优化。你花了多少时间思考和写这个问题,而不是自己编码和测试?

      :-)

      【讨论】:

        【解决方案4】:

        相信你的编译器会优化这样的小段代码。可读性在代码级别更为重要。真正的优化应该来自更高的层次。

        【讨论】:

          【解决方案5】:

          没关系。现代处理器可以在一个时钟周期或更短的时间内执行整数 MUL 指令,这与需要执行一系列移位和内部加法以执行 MUL 从而使用多个周期的旧处理器不同。我敢打赌

          MUL EAX,3
          

          执行速度比

          MOV EBX,EAX
          SHL EAX,1
          ADD EAX,EBX
          

          这种优化可能有用的最后一个处理器可能是 486。(是的,这偏向于英特尔处理器,但也可能代表其他架构)。

          无论如何,任何合理的编译器都应该能够生成最小/最快的代码。所以总是首先考虑可读性。

          【讨论】:

          • 我真的怀疑 MUL 的执行速度更快,一旦考虑到您可以使用哪些寄存器的延迟和不灵活性。此外,在 x86 上,LEA,而不是您提供的 3 指令序列,将被任何体面的编译器用于 3*n 和 n+2*n。
          • 是的,但 LEA 仅在乘以一小组常数(如果我没记错的话,是 2、3、4、5、8 和 9)时才有用。无论如何,我的意思是让编译器找出最快的代码。
          【解决方案6】:

          既然自己测量很容易,为什么不这样做呢? (使用来自cygwin的gcctime

          /* test1.c */
          int main()
          {
              int result = 0;
              int times = 1000000000;
              while (--times)
                  result = result * 3;
              return result;
          }
          
          machine:~$ gcc -O2 test1.c -o test1
          machine:~$ time ./test1.exe
          
          real    0m0.673s
          user    0m0.608s
          sys     0m0.000s
          

          进行几次测试,然后重复其他情况。

          如果你想偷看汇编代码,gcc -S -O2 test1.c

          【讨论】:

          • 不幸的是,这是一个不好的例子——对于 i686-apple-darwin8-gcc-4.0.1,它从循环中完全删除了“result = result * 3”,因为它总是为零。将初始条件更改为“result = 1”会得到更好的结果。
          • 或者更好,创建一个随机数数组并对其进行处理,这样编译器就无法做出任何假设。
          【解决方案7】:

          这取决于编译器、它的配置和周围的代码。

          您不应该在不进行测量的情况下尝试猜测事物是否“更快”。

          一般来说你现在不应该担心这种纳米级优化的东西 - 它几乎总是完全无关紧要的,如果你真的在一个重要的领域工作,你已经在使用分析器并查看编译器的汇编语言输出。

          【讨论】:

            【解决方案8】:

            编译器擅长优化您的代码。任何现代编译器都会为这两种情况生成相同的代码,并另外用左移替换 * 2

            【讨论】:

            • 不要那么肯定 :) 我看到一些用于嵌入式软件开发的非常奇怪的编译器。
            • 在嵌入式系统中,几乎所有的传统智慧都结束了。 ;-)
            【解决方案9】:

            大多数编译器都足够聪明,可以将整数乘法分解为一系列位移和加法。我不知道 Windows 编译器,但至少使用 gcc 你可以让它吐出汇编器,如果你看一下,你可能会看到两种编写方式的相同汇编器。

            【讨论】:

              【解决方案10】:

              它确实取决于您实际使用的编译器,但很可能它们会转换为相同的代码。

              你可以通过创建一个小测试程序并检查它的反汇编来自己检查它。

              【讨论】:

                【解决方案11】:

                除非您使用一些奇特的编译器,否则无需进行 IMO 此类微优化。我会把可读性放在首位。

                【讨论】:

                  猜你喜欢
                  • 2021-12-22
                  • 2014-06-13
                  • 1970-01-01
                  • 1970-01-01
                  • 2019-01-06
                  • 1970-01-01
                  • 1970-01-01
                  • 2014-11-04
                  • 2011-01-29
                  相关资源
                  最近更新 更多