我可以在 C 中将 GCC 的 __builtin_expect() 与三元运算符一起使用吗

2024-03-14

The 海湾合作委员会手册 http://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html仅显示 __builtin_expect() 放置在“if”语句的整个条件周围的示例。

我还注意到,如果我使用它,例如,与三元运算符一起使用,或者在任何任意整数表达式中使用它,GCC 不会抱怨,即使是不在分支上下文中使用的表达式。

所以,我想知道它的使用的根本限制到底是什么。

当用于这样的三元运算时,它会保留其效果吗:

int foo(int i)
{
  return __builtin_expect(i == 7, 1) ? 100 : 200;
}

那么这个案例呢:

int foo(int i)
{
  return __builtin_expect(i, 7) == 7 ? 100 : 200;
}

和这个:

int foo(int i)
{
  int j = __builtin_expect(i, 7);
  return j == 7 ? 100 : 200;
}

它显然适用于三元和常规 if 语句。

首先,我们看一下以下三个代码示例,其中两个使用__builtin_expect在常规 if 和三元 if 样式中,以及第三种根本不使用它。

内置.c:

int main()
{
    char c = getchar();
    const char *printVal;
    if (__builtin_expect(c == 'c', 1))
    {
        printVal = "Took expected branch!\n";
    }
    else
    {
        printVal = "Boo!\n";
    }

    printf(printVal);
}

三元.c:

int main()
{
    char c = getchar();
    const char *printVal = __builtin_expect(c == 'c', 1) 
        ? "Took expected branch!\n"
        : "Boo!\n";

    printf(printVal);
}

nobuiltin.c:

int main()
{
    char c = getchar();
    const char *printVal;
    if (c == 'c')
    {
        printVal = "Took expected branch!\n";
    }
    else
    {
        printVal = "Boo!\n";
    }

    printf(printVal);
}

当编译时-O3,所有三个结果都相同。然而,当-O被忽略(在 GCC 4.7.2 上),ternary.c 和builtin.c 都具有相同的程序集列表(在重要的地方):

内置:

    .file   "builtin.c"
    .section    .rodata
.LC0:
    .string "Took expected branch!\n"
.LC1:
    .string "Boo!\n"
    .text
    .globl  main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    andl    $-16, %esp
    subl    $32, %esp
    call    getchar
    movb    %al, 27(%esp)
    cmpb    $99, 27(%esp)
    sete    %al
    movzbl  %al, %eax
    testl   %eax, %eax
    je  .L2
    movl    $.LC0, 28(%esp)
    jmp .L3
.L2:
    movl    $.LC1, 28(%esp)
.L3:
    movl    28(%esp), %eax
    movl    %eax, (%esp)
    call    printf
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .ident  "GCC: (Debian 4.7.2-4) 4.7.2"
    .section    .note.GNU-stack,"",@progbits

三元:

    .file   "ternary.c"
    .section    .rodata
.LC0:
    .string "Took expected branch!\n"
.LC1:
    .string "Boo!\n"
    .text
    .globl  main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    andl    $-16, %esp
    subl    $32, %esp
    call    getchar
    movb    %al, 31(%esp)
    cmpb    $99, 31(%esp)
    sete    %al
    movzbl  %al, %eax
    testl   %eax, %eax
    je  .L2
    movl    $.LC0, %eax
    jmp .L3
.L2:
    movl    $.LC1, %eax
.L3:
    movl    %eax, 24(%esp)
    movl    24(%esp), %eax
    movl    %eax, (%esp)
    call    printf
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .ident  "GCC: (Debian 4.7.2-4) 4.7.2"
    .section    .note.GNU-stack,"",@progbits

而 nobuiltin.c 则不会:

    .file   "nobuiltin.c"
    .section    .rodata
.LC0:
    .string "Took expected branch!\n"
.LC1:
    .string "Boo!\n"
    .text
    .globl  main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    andl    $-16, %esp
    subl    $32, %esp
    call    getchar
    movb    %al, 27(%esp)
    cmpb    $99, 27(%esp)
    jne .L2
    movl    $.LC0, 28(%esp)
    jmp .L3
.L2:
    movl    $.LC1, 28(%esp)
.L3:
    movl    28(%esp), %eax
    movl    %eax, (%esp)
    call    printf
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .ident  "GCC: (Debian 4.7.2-4) 4.7.2"
    .section    .note.GNU-stack,"",@progbits

相关部分:

基本上,__builtin_expect导致额外的代码(sete %al...) 在之前执行je .L2基于结果testl %eax, %eaxCPU 更有可能预测为 1(这里是天真的假设),而不是基于输入字符与'c'。而在 nobuiltin.c 的情况下,不存在这样的代码,并且je/jne直接与“c”进行比较(cmp $99)。请记住,分支预测主要是在 CPU 中完成的,这里 GCC 只是为 CPU 分支预测器“放置陷阱”,以假设将采取哪条路径(通过额外的代码和切换je and jne,虽然我没有这方面的资料,因为英特尔的官方优化手册 http://www.intel.com/content/dam/doc/manual/64-ia-32-architectures-optimization-manual.pdf没有提到对待第一次遇到je vs jne分支预测不同!我只能假设 GCC 团队通过反复试验得出了这个结论)。

我确信有更好的测试用例,可以更直接地看到 GCC 的分支预测(而不是观察对 CPU 的提示),尽管我不知道如何简洁地模拟这种情况。 (猜测:它可能会涉及编译期间的循环展开。)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

我可以在 C 中将 GCC 的 __builtin_expect() 与三元运算符一起使用吗 的相关文章

  • 为什么libc++的shared_ptr实现使用完整内存屏障而不是宽松内存屏障?

    在boost的实现中shared ptr 它用放松内存排序以增加其引用计数 https github com boostorg smart ptr blob master include boost smart ptr detail sp
  • 我的线程图像生成应用程序如何将其数据传输到 GUI?

    Mandelbrot 生成器的缓慢多精度实现 线程化 使用 POSIX 线程 Gtk 图形用户界面 我有点失落了 这是我第一次尝试编写线程程序 我实际上并没有尝试转换它的单线程版本 只是尝试实现基本框架 到目前为止它是如何工作的简要描述 M
  • 向 ExpandoObject 添加方法时,“关键字 'this' 在静态属性、静态方法或静态字段初始值设定项中无效”

    我尝试向 ExpandoObject 添加一个动态方法 该方法将返回属性 动态添加 给它 但它总是给我错误 我在这里做错了什么吗 using System using System Collections Generic using Sys
  • C++:重写已弃用的虚拟方法时出现弃用警告

    我有一个纯虚拟类 它有一个纯虚拟方法 应该是const 但不幸的是不是 该接口位于库中 并且该类由单独项目中的其他几个类继承 我正在尝试使用这个方法const不会破坏兼容性 至少在一段时间内 但我找不到在非常量方法重载时产生警告的方法 以下
  • 构造函数中显式关键字的使用

    我试图了解 C 中显式关键字的用法 并查看了这个问题C 中的explicit关键字是什么意思 https stackoverflow com questions 121162 但是 那里列出的示例 实际上是前两个答案 对于用法并不是很清楚
  • 访问者和模板化虚拟方法

    在一个典型的实现中Visitor模式 该类必须考虑基类的所有变体 后代 在许多情况下 访问者中的相同方法内容应用于不同的方法 在这种情况下 模板化的虚拟方法是理想的选择 但目前这是不允许的 那么 模板化方法可以用来解析父类的虚方法吗 鉴于
  • 检查算术运算中的溢出情况[重复]

    这个问题在这里已经有答案了 可能的重复 检测 C C 中整数溢出的最佳方法 https stackoverflow com questions 199333 best way to detect integer overflow in c
  • 如何从网站下载 .EXE 文件?

    我正在编写一个应用程序 需要从网站下载 exe 文件 我正在使用 Visual Studio Express 2008 我正在使用以下代码 private void button1 Click object sender EventArgs
  • Intel:序列化指令和分支预测

    英特尔架构开发人员手册 http www intel com content www us en architecture and technology 64 ia 32 architectures software developer v
  • 基于xsd模式生成xml(使用.NET)

    我想根据我的 xsd 架构 cap xsd 生成 xml 文件 我找到了这篇文章并按照说明进行操作 使用 XSD 文件生成 XML 文件 https stackoverflow com questions 6530424 generatin
  • 生产代码中的 LRU 实现

    我有一些 C 代码 需要使用 LRU 技术实现缓存替换 目前我知道两种实现LRU缓存替换的方法 每次访问缓存数据时使用时间戳 最后比较替换时的时间戳 使用缓存项的堆栈 如果最近访问过它们 则将它们移动到顶部 因此最后底部将包含 LRU 候选
  • 将构建日期放入“关于”框中

    我有一个带有 关于 框的 C WinForms 应用程序 我使用以下方法将版本号放入 关于 框中 FileVersionInfo GetVersionInfo Assembly GetExecutingAssembly Location F
  • 将代码拆分为标头/源文件

    我从 Asio 的示例页面中获取了以下代码 class tcp connection public boost enable shared from this
  • 是否可以有一个 out ParameterExpression?

    我想定义一个 Lambda 表达式out范围 有可能做到吗 下面是我尝试过的 C Net 4 0 控制台应用程序的代码片段 正如您在 procedure25 中看到的 我可以使用 lambda 表达式来定义具有输出参数的委托 但是 当我想使
  • 剪贴板在 .NET 3.5 和 4 中的行为有所不同,但为什么呢?

    我们最近将一个非常大的项目从 NET Framework 3 5 升级到 4 最初一切似乎都工作正常 但现在复制粘贴操作开始出现错误 我已经成功制作了一个小型的可复制应用程序 它显示了 NET 3 5 和 4 中的不同行为 我还找到了一种解
  • 双精度类型二维多维数组的 pinvoke 编组作为 c# 和 c++ 之间的输入和输出

    我有以下我正在尝试解决的双物质类型的 2d 多维数组的 c 和 c pinvoke 编组 我已经查看了以下热门内容以获得我目前拥有的内容使用双精度数组进行 P Invoke 在 C 和 C 之间编组数据 https stackoverflo
  • 带重定向标准流的 C# + telnet 进程立即退出

    我正在尝试用 C 做一个 脚本化 telnet 项目 有点类似于Tcl期望 http expect nist gov 我需要为其启动 telnet 进程并重定向 和处理 其 stdin stdout 流 问题是 生成的 telnet 进程在
  • Googletest:如何异步运行测试?

    考虑到一个包含数千个测试的大型项目 其中一些测试需要几分钟才能完成 如果按顺序执行 整套测试需要一个多小时才能完成 通过并行执行测试可以减少测试时间 据我所知 没有办法直接从 googletest mock 做到这一点 就像 async选项
  • 使用 Crypto++ 获取 ECDSA 签名

    我必须使用 Crypto 在变量中获取 ECDSA 签名 我在启动 SignMessage 后尝试获取它 但签名为空 我怎样才能得到它 你看过 Crypto wiki 吗 上面有很多东西椭圆曲线数字签名算法 http www cryptop
  • 匿名结构体作为返回类型

    下面的代码编译得很好VC 19 00 23506 http rextester com GMUP11493 标志 Wall WX Za 与VC 19 10 25109 0 标志 Wall WX Za permissive 这可以在以下位置检

随机推荐