Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/c/61.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
GCC&x27的优势是什么;在if-else语句中内置了s\uuu expect?_C_Linux_Gcc_Built In - Fatal编程技术网

GCC&x27的优势是什么;在if-else语句中内置了s\uuu expect?

GCC&x27的优势是什么;在if-else语句中内置了s\uuu expect?,c,linux,gcc,built-in,C,Linux,Gcc,Built In,我遇到了一个#define,他们在其中使用uu builtin\uu expect 说: 内置函数:long\u内置函数(long exp,long c) 您可以使用\u内置\u expect为编译器提供分支 预测信息。一般来说,您应该更喜欢使用实际值 与程序员一样,对此(-fprofile arcs)的配置文件反馈 众所周知,他们不善于预测程序的实际执行情况。 但是,有些应用程序很难收集这些数据 返回值是exp的值,它应该是一个整数 表情。内置函数的语义是 exp==c。例如: i

我遇到了一个
#define
,他们在其中使用
uu builtin\uu expect

说:

内置函数:
long\u内置函数(long exp,long c)
您可以使用
\u内置\u expect
为编译器提供分支 预测信息。一般来说,您应该更喜欢使用实际值 与程序员一样,对此(
-fprofile arcs
)的配置文件反馈 众所周知,他们不善于预测程序的实际执行情况。 但是,有些应用程序很难收集这些数据

返回值是
exp
的值,它应该是一个整数 表情。内置函数的语义是
exp==c
。例如:

      if (__builtin_expect (x, 0))
        foo ();
表示我们不希望调用
foo
,因为我们希望
x
为零

那么为什么不直接使用:

if (x)
    foo ();

正如描述中所说,第一个版本在构造中添加了一个预测元素,告诉编译器,
x==0
分支更可能是一个分支,也就是说,您的程序将更频繁地使用该分支

考虑到这一点,编译器可以优化条件,以便在满足预期条件的情况下,它所需的工作量最少,而在出现意外条件的情况下,可能需要做更多的工作

看看在编译阶段以及在生成的程序集中如何实现条件语句,看看一个分支如何比另一个分支少工作


然而,如果所讨论的条件是频繁调用的紧密内部循环的一部分,我只希望这种优化会产生显著的效果,因为结果代码中的差异相对较小。如果您以错误的方式对其进行优化,很可能会降低性能。

内置式expect的思想是告诉编译器,您通常会发现表达式的计算结果为c,因此编译器可以针对这种情况进行优化

我猜有人认为他们很聪明,他们这样做是在加快速度

不幸的是,除非人们非常清楚这种情况(很可能他们没有做过这样的事情),否则情况很可能会变得更糟。文件甚至说:

一般来说,您应该更倾向于使用实际的概要文件反馈(
-fprofile arcs
),因为程序员在预测其程序实际执行情况方面是出了名的差。但是,有些应用程序很难收集这些数据

通常,您不应该使用
\u内置\u expect
,除非:

  • 您有一个非常真实的性能问题
  • 您已经适当地优化了系统中的算法
  • 您已经有了性能数据来支持您的断言,即某个特定情况最有可能发生

想象一下将由以下代码生成的汇编代码:

if (__builtin_expect(x, 0)) {
    foo();
    ...
} else {
    bar();
    ...
}
我想应该是这样的:

  cmp   $x, 0
  jne   _foo
_bar:
  call  bar
  ...
  jmp   after_if
_foo:
  call  foo
  ...
after_if:
您可以看到指令的排列顺序是,
bar
大小写在
foo
大小写之前(与C代码相反)。这可以更好地利用CPU管道,因为跳转会重击已经获取的指令


在执行跳转之前,跳转下方的指令(条形案例)被推送到管道中。由于
foo
的情况不太可能发生,跳跃也不太可能发生,因此不太可能破坏管道。

我看不到任何解决我认为您所问问题的答案,请转述如下:

是否有一种更便于移植的方法向编译器提示分支预测

你问题的标题让我想到了这样做:

if ( !x ) {} else foo();
如果编译器假定更可能为“true”,则可以优化为不调用
foo()


这里的问题只是,一般来说,您不知道编译器会假设什么——因此,任何使用这种技术的代码都需要仔细测量(如果上下文发生变化,可能会随着时间的推移进行监控)。

让我们反编译,看看GCC4.8如何处理它

Blagovest提到了分支反转以改进管道,但当前的编译器真的做到了吗?让我们看看

不带
\u内置\u expect

#include "stdio.h"
#include "time.h"

int main() {
    /* Use time to prevent it from being optimized away. */
    int i = !time(NULL);
    if (i)
        puts("a");
    return 0;
}
使用GCC 4.8.2 x86_64 Linux编译和反编译:

gcc -c -O3 -std=gnu11 main.c
objdump -dr main.o
输出:

0000000000000000 <main>:
   0:       48 83 ec 08             sub    $0x8,%rsp
   4:       31 ff                   xor    %edi,%edi
   6:       e8 00 00 00 00          callq  b <main+0xb>
                    7: R_X86_64_PC32        time-0x4
   b:       48 85 c0                test   %rax,%rax
   e:       75 0a                   jne    1a <main+0x1a>
  10:       bf 00 00 00 00          mov    $0x0,%edi
                    11: R_X86_64_32 .rodata.str1.1
  15:       e8 00 00 00 00          callq  1a <main+0x1a>
                    16: R_X86_64_PC32       puts-0x4
  1a:       31 c0                   xor    %eax,%eax
  1c:       48 83 c4 08             add    $0x8,%rsp
  20:       c3                      retq
我们得到:

0000000000000000 <main>:
   0:       48 83 ec 08             sub    $0x8,%rsp
   4:       31 ff                   xor    %edi,%edi
   6:       e8 00 00 00 00          callq  b <main+0xb>
                    7: R_X86_64_PC32        time-0x4
   b:       48 85 c0                test   %rax,%rax
   e:       74 07                   je     17 <main+0x17>
  10:       31 c0                   xor    %eax,%eax
  12:       48 83 c4 08             add    $0x8,%rsp
  16:       c3                      retq
  17:       bf 00 00 00 00          mov    $0x0,%edi
                    18: R_X86_64_32 .rodata.str1.1
  1c:       e8 00 00 00 00          callq  21 <main+0x21>
                    1d: R_X86_64_PC32       puts-0x4
  21:       eb ed                   jmp    10 <main+0x10>
此优化不是使用
-O0
完成的

但是祝您好运,编写一个使用
\u内置
比不使用
运行得更快的示例。我天真的尝试

C++20
[[可能]]
[[不可能]]

#include "stdio.h"
#include "time.h"

int main() {
    /* Use time to prevent it from being optimized away. */
    int i = !time(NULL);
    if (i)
        puts("a");
    return 0;
}

C++ 20已经对C++内置的内容进行了标准化:它们可能会(双关)做同样的事情。

< P>我根据Mac BraoReaveBuykLiEv和@西罗在MAC上测试。集合看起来很清晰,我添加了注释

命令是
gcc-c-O3-std=gnu11 testOpt.c;otool-tVI测试选项

当我使用-O3时,无论内置的expect(I,0)是否存在,它看起来都是一样的

testOpt.o:
(__TEXT,__text) section
_main:
0000000000000000    pushq   %rbp     
0000000000000001    movq    %rsp, %rbp    // open function stack
0000000000000004    xorl    %edi, %edi       // set time args 0 (NULL)
0000000000000006    callq   _time      // call time(NULL)
000000000000000b    testq   %rax, %rax   // check time(NULL)  result
000000000000000e    je  0x14           //  jump 0x14 if testq result = 0, namely jump to puts
0000000000000010    xorl    %eax, %eax   //  return 0   ,  return appear first 
0000000000000012    popq    %rbp    //  return 0
0000000000000013    retq                     //  return 0
0000000000000014    leaq    0x9(%rip), %rdi  ## literal pool for: "a"  // puts  part, afterwards
000000000000001b    callq   _puts
0000000000000020    xorl    %eax, %eax
0000000000000022    popq    %rbp
0000000000000023    retq
当使用-O2编译时,使用和不使用内置的expect(i,0)时,它看起来不同

首先没有

testOpt.o:
(__TEXT,__text) section
_main:
0000000000000000    pushq   %rbp
0000000000000001    movq    %rsp, %rbp
0000000000000004    xorl    %edi, %edi
0000000000000006    callq   _time
000000000000000b    testq   %rax, %rax
000000000000000e    jne 0x1c       //   jump to 0x1c if not zero, then return
0000000000000010    leaq    0x9(%rip), %rdi ## literal pool for: "a"   //   put part appear first ,  following   jne 0x1c
0000000000000017    callq   _puts
000000000000001c    xorl    %eax, %eax     // return part appear  afterwards
000000000000001e    popq    %rbp
000000000000001f    retq
现在使用内置的expect(i,0)


总而言之,内置的expect在最后一种情况下有效。

在大多数情况下,您应该保持分支预测不变,不必担心它

其中一个有益的例子是CPU密集型算法,它有很多分支。在某些情况下,跳转可能导致超出当前CPU程序缓存,使CPU等待软件的下一部分运行。通过将不太可能出现的分支推到最后,你将保留你的记忆
testOpt.o:
(__TEXT,__text) section
_main:
0000000000000000    pushq   %rbp
0000000000000001    movq    %rsp, %rbp
0000000000000004    xorl    %edi, %edi
0000000000000006    callq   _time
000000000000000b    testq   %rax, %rax
000000000000000e    jne 0x1c       //   jump to 0x1c if not zero, then return
0000000000000010    leaq    0x9(%rip), %rdi ## literal pool for: "a"   //   put part appear first ,  following   jne 0x1c
0000000000000017    callq   _puts
000000000000001c    xorl    %eax, %eax     // return part appear  afterwards
000000000000001e    popq    %rbp
000000000000001f    retq
testOpt.o:
(__TEXT,__text) section
_main:
0000000000000000    pushq   %rbp
0000000000000001    movq    %rsp, %rbp
0000000000000004    xorl    %edi, %edi
0000000000000006    callq   _time
000000000000000b    testq   %rax, %rax
000000000000000e    je  0x14   // jump to 0x14 if zero  then put. otherwise return 
0000000000000010    xorl    %eax, %eax   // return appear first 
0000000000000012    popq    %rbp
0000000000000013    retq
0000000000000014    leaq    0x7(%rip), %rdi ## literal pool for: "a"
000000000000001b    callq   _puts
0000000000000020    jmp 0x10