为什么 clang 使用 -O0 生成低效的 asm(对于这个简单的浮点和)?

2024-05-14

我正在 llvm clang Apple LLVM 版本 8.0.0 (clang-800.0.42.1) 上反汇编此代码:

int main() {
    float a=0.151234;
    float b=0.2;
    float c=a+b;
    printf("%f", c);
}

我没有使用 -O 规范进行编译,但我也尝试使用 -O0 (给出相同的值)和 -O2 (实际上计算值并将其存储为预先计算的值)

拆解结果如下(我删除了不相关的部分)

->  0x100000f30 <+0>:  pushq  %rbp
    0x100000f31 <+1>:  movq   %rsp, %rbp
    0x100000f34 <+4>:  subq   $0x10, %rsp
    0x100000f38 <+8>:  leaq   0x6d(%rip), %rdi       
    0x100000f3f <+15>: movss  0x5d(%rip), %xmm0           
    0x100000f47 <+23>: movss  0x59(%rip), %xmm1        
    0x100000f4f <+31>: movss  %xmm1, -0x4(%rbp)  
    0x100000f54 <+36>: movss  %xmm0, -0x8(%rbp)
    0x100000f59 <+41>: movss  -0x4(%rbp), %xmm0         
    0x100000f5e <+46>: addss  -0x8(%rbp), %xmm0
    0x100000f63 <+51>: movss  %xmm0, -0xc(%rbp)
    ...

显然它正在执行以下操作:

  1. 将两个浮点数加载到寄存器 xmm0 和 xmm1 中
  2. 将它们放入堆栈中
  3. 将一个值(不是 xmm0 之前的值)从堆栈加载到 xmm0
  4. 执行加法。
  5. 将结果存回堆栈。

我发现它效率低下,因为:

  1. 一切都可以在注册表中完成。我稍后不会使用 a 和 b,因此它可以跳过任何涉及堆栈的操作。
  2. 即使它想使用堆栈,如果它以不同的顺序执行操作,也可以节省从堆栈中重新加载 xmm0 的时间。

既然编译器总是对的,为什么它选择这个策略呢?


-O0(未优化)是默认值。它告诉编译器您希望它快速编译(短编译时间),not花费额外的时间编译以生成高效的代码。

(-O0并不是字面上没有优化;例如gcc 仍然会消除里面的代码if(1 == 2){ }块。特别是 gcc 比大多数其他编译器仍然更多地执行诸如使用乘法逆元进行除法之类的事情-O0,因为在最终发出 asm 之前,它仍然会通过逻辑的多个内部表示来转换您的 C 源代码。)

另外,“编译器总是对的”即使在-O3。编译器在大规模方面非常出色,但在单个循环中,轻微的优化失误仍然很常见。通常影响很小,但循环中浪费的指令(或微指令)可能会占用无序执行重新排序窗口中的空间,并且在与另一个线程共享核心时对超线程不太友好。看用于测试 Collat​​z 猜想的 C++ 代码比手写汇编更快 - 为什么? https://stackoverflow.com/questions/40354978/c-code-for-testing-the-collatz-conjecture-faster-than-hand-written-assembly有关在简单的特定情况下击败编译器的更多信息。


更重要的是,-O0也意味着对待所有变量类似于volatile为了一致的调试。即,您可以设置断点或单步,然后modifyC 变量的值,然后继续执行并使程序按照您期望在 C 抽象机上运行的 C 源代码的方式工作。因此编译器无法进行任何常量传播或值范围简化。 (例如,已知非负整数可以使用它来简化事情,或者使某些 if 条件始终为真或始终为假。)

(它不是quite和......一样烂volatile:在一个语句中对同一变量的多次引用并不总是导致多次加载;在-O0编译器仍然会在单个表达式中进行一定程度的优化。)

编译器必须专门针对以下情况进行反优化-O0通过在语句之间将所有变量存储/重新加载到它们的内存地址。 (在 C 和 C++ 中,每个变量都有一个地址,除非它是用(现已废弃)声明的register关键字并且其地址从未被占用。根据其他变量的假设规则,可以优化地址,但不能在-O0)

不幸的是,调试信息格式无法通过寄存器跟踪变量的位置,因此如果没有这种缓慢而愚蠢的代码生成,完全一致的调试是不可能的。

如果你不需要这个,你可以编译-Og用于轻度优化,并且没有一致调试所需的反优化。 GCC 手册建议将其用于通常的编辑/编译/运行周期,但在调试时,您将通过自动存储来“优化”许多局部变量。全局变量和函数参数通常仍然具有其实际值,至少在函数边界处如此。


更糟糕,-O0即使您使用 GDB,编写的代码仍然可以工作jump命令在不同的源代码行继续执行。因此,每个 C 语句都必须编译成完全独立的指令块。 (是否可以在 GDB 调试器中“跳转”/“跳过”? https://stackoverflow.com/questions/4116632/is-it-possible-to-jump-skip-in-gdb-debugger)

for()循环不能转化为惯用语(用于 asm)do{}while() loops https://stackoverflow.com/questions/47783926/why-are-loops-always-compiled-into-do-while-style-tail-jump和其他限制。

由于上述所有原因,(微观)基准测试未优化的代码会浪费大量时间;结果取决于您如何编写源代码的愚蠢细节,当您使用正常优化进行编译时,这些细节并不重要。-O0 vs. -O3性能不是线性相关的;有些代码会比其他代码加速得多.

瓶颈在-O0代码通常会不同于-O3- 通常在保存在内存中的循环计数器上,创建约 6 周期循环携带的依赖链。这可以在编译器生成的 asm 中创建有趣的效果,例如添加冗余赋值可以在未经优化的情况下编译时加快代码速度 https://stackoverflow.com/questions/49189685/adding-a-redundant-assignment-speeds-up-code-when-compiled-without-optimization(从汇编的角度来看这很有趣,但是not for C.)

“我的基准测试以其他方式进行了优化”并不是查看性能的有效理由-O0代码。 看最终分配的 C 循环优化帮助 https://stackoverflow.com/questions/32000917/c-loop-optimization-help-for-final-assignment/32001196#32001196有关调整兔子洞的示例和更多详细信息-O0 is.


获得有趣的编译器输出

如果你想看看编译器如何添加 2 个变量,编写一个接受参数并返回值的函数。请记住,您只想查看汇编,而不是运行它,因此您不需要main或任何应该是运行时变量的数字文字值。

也可以看看如何从 GCC/clang 汇编输出中消除“噪音”? https://stackoverflow.com/questions/38552116/how-to-remove-noise-from-gcc-clang-assembly-output了解更多相关信息。

float foo(float a, float b) {
    float c=a+b;
    return c;
}

编译为clang -O3 (在 Godbolt 编译器资源管理器上 https://godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(j:1,lang:c%2B%2B,source:'float+foo(float+a,+float+b)+%7B%0A++++float+c%3Da%2Bb%3B%0A++++return+c%3B%0A%7D%0A%0Afloat+bar(float+a,+float+b)+%7B%0A++++register+float+c%3Da%2Bb%3B%0A++++//+can+stay+in+xmm0+between+statements+at+-O0%0A++++//+because+of+the+register+keyword.%0A++++return+c%3B%0A%7D%0A'),l:'5',n:'0',o:'C%2B%2B+source+%231',t:'0')),k:33.14447592067989,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((h:compiler,i:(compiler:clang700,filters:(b:'0',binary:'1',commentOnly:'0',demangle:'0',directives:'0',execute:'1',intel:'0',trim:'1'),lang:c%2B%2B,libs:!(),options:'-xc+-O3+-Wall',source:1),l:'5',n:'0',o:'x86-64+clang+7.0.0+(Editor+%231,+Compiler+%232)+C%2B%2B',t:'0')),header:(),k:30.23910429342857,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((h:compiler,i:(compiler:g82,filters:(b:'0',binary:'1',commentOnly:'0',demangle:'0',directives:'0',execute:'1',intel:'0',trim:'1'),lang:c%2B%2B,libs:!(),options:'-xc+-O0+-Wall',source:1),l:'5',n:'0',o:'x86-64+gcc+8.2+(Editor+%231,+Compiler+%231)+C%2B%2B',t:'0')),k:36.61641978589154,l:'4',n:'0',o:'',s:0,t:'0')),l:'2',n:'0',o:'',t:'0')),version:4)到预期的

    addss   xmm0, xmm1
    ret

但与-O0它将参数溢出到堆栈内存。 (Godbolt 使用编译器发出的调试信息根据来自哪个 C 语句对 asm 指令进行颜色编码。我添加了换行符来显示每个语句的块,但是您可以在上面的 Godbolt 链接上通过颜色突出显示来看到这一点.通常非常方便地在优化的编译器输出中查找内部循环的有趣部分。)

gcc -fverbose-asm将在将操作数名称显示为 C 变量的每一行添加注释。在优化的代码中,它通常是内部 tmp 名称,但在未优化的代码中,它通常是来自 C 源代码的实际变量。我已经手动注释了 clang 输出,因为它没有这样做。

# clang7.0 -O0  also on Godbolt
foo:
    push    rbp
    mov     rbp, rsp                  # make a traditional stack frame
    movss   DWORD PTR [rbp-20], xmm0  # spill the register args
    movss   DWORD PTR [rbp-24], xmm1  # into the red zone (below RSP)

    movss   xmm0, DWORD PTR [rbp-20]  # a
    addss   xmm0, DWORD PTR [rbp-24]  # +b
    movss   DWORD PTR [rbp-4], xmm0   # store c

    movss   xmm0, DWORD PTR [rbp-4]   # return 0
    pop     rbp                       # epilogue
    ret

有趣的事实:使用register float c = a+b;,返回值可以保留在语句之间的 XMM0 中,而不是被溢出/重新加载。该变量没有地址。 (我将该版本的函数包含在 Godbolt 链接中。)

The register关键字在优化代码中没有任何作用(除了使获取变量地址时出错,例如如何const在本地可以防止您意外修改某些内容)。我不建议使用它,但有趣的是它确实会影响未优化的代码。


Related:

  • 简单构造函数的复杂编译器输出 https://stackoverflow.com/questions/55329728/complex-compiler-output-for-simple-constructor- 传递参数时变量的每个副本通常会在 asm 中产生额外的副本。
  • 为什么这个 C++ 包装类没有被内联掉? https://stackoverflow.com/questions/54073295/why-is-this-c-wrapper-class-not-being-inlined-away/54074497#54074497 __attribute__((always_inline))可以强制内联,但不会优化创建函数参数的复制,更不用说将函数优化到调用者中。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么 clang 使用 -O0 生成低效的 asm(对于这个简单的浮点和)? 的相关文章

  • 无法在 QGLWidget 中设置所需的 OpenGL 版本

    我正在尝试在 Qt 4 8 2 中使用 QGLWidget 我注意到 QGLWidget 创建的默认上下文不显示 OpenGL 3 1 以上的任何输出 Qt wiki 有一个教程 http qt project org wiki How t
  • 如何使用不同的基本路径托管 Blazor WebAssembly 应用程序

    我有一个 Blazor Webassemble NET 托管应用程序 在我们托管它的服务器上 应用程序的基本路径将是mydomain com coolapp 因此 为了尝试让应用程序在服务器上正确呈现 我一直遵循本页 应用程序基本路径 部分
  • 并行运行多个任务

    我有一个代理列表 每个代理都会访问不同的站点并从站点中提取所需的数据 目前它一次只做一个 但我希望同时运行 10 20 个任务 这样它就可以一次性从 20 个站点下载 而不是只下载一个 这是我目前正在做的事情 private async T
  • 来自 double 的 static_cast 可以优化分配给 double 吗?

    我偶然发现了一个我认为不必要的功能 并且通常让我感到害怕 float coerceToFloat double x volatile float y static cast
  • 在 C# 中解析 JS Date.toIsoString

    我需要将 JS 日期存储为 ISO 8601 日期 我目前正在从格式为 2019 06 22T00 00 00 000Z 的表单中获取日期 正如 JS 的 toIsoString 方法所期望的那样 当这个日期传递到我的 API 控制器时 我
  • 从图像创建半透明光标

    是否可以从图像创建光标并使其半透明 我目前正在拍摄自定义图像并覆盖鼠标光标图像 如果我可以将其设为半透明 那就太好了 但不是必需的 销售人员喜欢闪亮的 目前正在做这样的事情 Image cursorImage customImage Get
  • X 轴和 Z 轴上的 Quaternion.Slerp,无 Y 轴

    I am trying to rotate the Player about X Y and Z axis The Y axis should not move from last angle Example if I rotate 45
  • 使用 STL 流时如何格式化我自己的对象?

    我想将我自己的对象输出到 STL 流 但具有自定义格式 我想出了这样的东西 但由于我之前从未使用过 locale 和 imbue 所以我不知道这是否有意义以及如何实现 MyFacet 和operator 所以我的问题是 这是否有意义以及如何
  • main.cpp 是必需的吗?

    我试图编译一个程序cmake 我最终删除了我的main cpp文件 我刚刚将其复合到另一个包含我的项目名称的文件中 即 我刚刚将主函数剪切并粘贴到该文件中 问题是我有一个main cpp未发现错误 不确定是否在C 一个名为main cpp是
  • MINIX内部碎片2

    我正在用 C 语言编写一些软件 它递归地列出给定目录中的所有文件 现在我需要计算出内部碎片 我花了很长时间研究这个问题 发现 ext2 上的内部碎片只发生在最后一个块中 我知道理论上你应该能够从索引节点号获得第一个和最后一个块地址 但我不知
  • 运行实体框架自定义工具,它有什么作用?

    在 Visual Studio 中 当使用实体框架并为 tt 和 Context tt 文件应用运行自定义工具时 它是什么以及它有什么作用 为什么它解决数据库同步问题 有时 为什么我应该在运行 tt 之前运行它 Context tt 它被称
  • MPI - 发送和接收列

    我需要从一个进程发送矩阵列并从另一个进程接收它 我尝试运行以下程序 但得到了一个奇怪的结果 至少我这么认为 仅复制矩阵的第一个元素 某些矩阵元素会发生意外变化 include
  • 从单应性估计 R/T

    我一直在尝试计算 2 个图像中的特征 然后将这些特征传递回CameraParams R没有运气 特征已成功计算并匹配 但是问题是将它们传递回R t 我明白你必须分解Homography为了使这一点成为可能 我已经使用如下方法完成了 http
  • 在 Visual Studio 2012 Express 中设置 C++ 调试环境

    我需要调试的应用程序需要设置环境变量 这在 Visual Studio 2012 中似乎非常复杂 我想做类似的事情 set path c foo c bar c windows c program files application set
  • g++ / gcc 是否支持 C++20 新的atomic_flag 功能?

    根据参考参数 https en cppreference com w cpp atomic atomic flag c 20 有丰富的 对我来说有用的 支持atomic flag运营 然而 目前尚不清楚 gcc 是否支持这些功能 它们在任何
  • 使用未命名命名空间而不是静态命名空间

    我可以假设在未命名命名空间中声明的对象相当于static namespace int x 1 static int x 2 FWIK 在这两种情况下 x将具有静态存储期限和内部链接 声明为的对象的所有规则也是如此static适用于未命名名称
  • 跟踪白色背景中的白球(Python/OpenCV)

    我在 Python 3 中使用 OpenCV 来检测白场上的白 黑球 并给出它的精确 x y 半径 和颜色 我使用函数 cv2 Canny 和 cv2 findContours 来找到它 但问题是 cv2 Canny 并不总是检测到圆的完整
  • java有类似C#的属性吗? [复制]

    这个问题在这里已经有答案了 C 属性 我的意思是 get 和 set 方法 是一个非常有用的功能 java 也有类似 C 的属性吗 我的意思是我们如何在 java 中实现类似以下 C 代码的内容 public string Name get
  • 稀疏矩阵超定线性方程组c/c++库

    我需要一个库来解决 Ax b 系统 其中 A 是一个非对称稀疏矩阵 每行有 8 个条目 而且可能很大 我认为实现双共轭梯度的库应该没问题 但我找不到一个有效的库 我尝试过 iml 但 iml sparselib 包中缺少一些标头 有小费吗
  • 如何将模型绑定到动态创建的类 nancyfx

    首先感谢任何愿意查看我的问题的人 我对 Nancyfx 还很陌生 在尝试将 JSON 有效负载绑定到动态创建的类时遇到问题 我按照这篇文章中的代码动态创建了该类 在C 中动态创建一个类 https stackoverflow com que

随机推荐

  • 从成员函数指针类型生成函子

    我正在尝试简化 通过make fn 预处理参数的函子的生成 通过wrap 对于 arity 的成员函数n 生成函子基本上可以工作 但到目前为止只能通过显式指定成员函数的参数类型来实现 现在我想从它处理的成员函数类型生成正确的函子 struc
  • Android 中 Activity 之间的 3D 动画

    How to create animation between two Activity look like As Screen shot in android 搜索jazzyviewpager 这是link https github co
  • Onblur 事件在另一个 div 的 onclick 之前触发

    如上所述 我有一个按钮 单击该按钮将打开子菜单 对于子菜单中的每个选项 都有三个元素 我认为实际上还有更多元素 但为了简单起见 将其保留为 3 我将焦点放在子菜单的主 div 白色 框架 上 Onblur 这个 div 然后我隐藏子菜单 这
  • C# 委托责任链

    为了我的理解目的 我实现了责任链模式 Abstract Base Type public abstract class CustomerServiceDesk protected CustomerServiceDesk nextHandle
  • 了解应用程序在后台时何时收到 Firebase 消息

    我知道这个标题有同样的问题 但不幸的是它没有得到正确的回答 它被接受了 here https stackoverflow com questions 37711082 how to handle notification when app
  • 移动浏览器中的 React 性能

    我有一个组件 表 其中包含许多行 其中包含数据编辑 其掩码形式为contenteditable 可以选择所有字段并同时更改所有行 在桌面上它运行得相当快 但在 iPhone 6 上我有不真实的滞后 Safari 每次操作都会挂起 20 秒
  • 在 for 循环中访问 itertools 产品的元素

    我有一个列表列表 是附加 itertools 产品的一些其他结果的结果 我想要的是能够使用 for 循环访问列表列表中列表的每个元素 但我无法访问所有元素 我只能访问最后一个列表的元素 结果是一个非常巨大的列表列表 例如 1 2 4 3 6
  • iPhone 点击时使 div 变暗

    当您的 div 附加了点击处理程序时 当点击该 div 时 iPhone 会使该 div 变暗 作为点击指示器 示例 在移动 Safari 上查看http jsbin com awejo3 4 http jsbin com awejo3 4
  • titledBorder 标题中的图标

    您好 是否可以在 titledBorder 的标题中放置一个图标 例如以下代码 import java awt GridLayout import javax swing JFrame import javax swing JLabel i
  • 有没有办法拉伸整个显示图像以适应给定的分辨率?

    我最近一直在使用pygame制作游戏 遇到了一个小问题 基本上 我希望能够将屏幕上的整个图像 我已经传输到它的所有内容 拉伸到用户将窗口大小调整到的分辨率 我在 pygame 和堆栈溢出的文档中搜索了很多 但我似乎找不到答案 这可能吗 我的
  • 在 Chapel 中计算矩阵的 rowSums

    继续我的教堂冒险 我有一个矩阵A var idx 1 n var adom idx idx var A adom int populate A var rowsums idx int 填充行和的最有效方法是什么 The 最有效率的解决方案很
  • Android计算两个日期之间的天数

    我编写了以下代码来查找两个日期之间的天数 startDateValue new Date startDate endDateValue new Date endDate long diff endDateValue getTime star
  • 如何在 Rails 3.2.1 版本中注释 Rails 模型

    我正在尝试遵循一些在线教程来在 Rails 中注释我的模型 然而 似乎所有教程都在谈论过时的注释版本或不正确的安装 这真是一团糟 到目前为止我已经尝试过以下方法 1 在 Gemfile 中添加此内容 gem annotate 2 4 0 2
  • 元素不适应 Firefox 上的

    使用 ES6 ish D3js 模块运行 Angular 6 应用程序会导致 Firefox 出现问题 Chromium Chrome Safari 和 IE Edge 工作正常 伪代码看起来类似于 生产代码可以在下面找到
  • 更改 WizardStep 按钮文本

    如何重命名每个按钮的文本 默认 步骤 1 到 步骤 n WizardStep 对于整个Wizard完成按钮的文本可以通过设置属性来设置finishButtonText 但没有类似的属性WizardStep 没有标准的方法 这是我实现它的方法
  • Angular - 为每个请求设置标头

    我需要在用户登录后为每个后续请求设置一些授权标头 要为特定请求设置标头 import Headers from angular2 http var headers new Headers headers append headerName
  • BigQuery 中使用 GROUPBY 的百分位函数

    在我的人口普查表中 我想按州分组 并为每个州获取县人口中位数和县数量 在 psql redshift 和 Snowflake 中 我可以这样做 psql gt SELECT state count county PERCENTILE CON
  • jquery ajax加载后丢失CSS

    大家知道如何解决 load Ajax 请求后的 css 问题吗 例如 如果我想从网页加载 DIV 在我的 Ajax 请求之后 container load path to div div id 我丢失了与该 div 关联的所有 css 和脚
  • 2D morton 码编码/解码 64 位

    如何将给定 x y 的莫顿代码 z 顺序 编码 解码为 32 位无符号整数 生成 64 位莫顿代码 反之亦然 我确实有 xy2d 和 d2xy 但仅适用于 16 位宽的坐标 产生 32 位莫顿数 在网上查了很多 但没有找到 请帮忙 如果您可
  • 为什么 clang 使用 -O0 生成低效的 asm(对于这个简单的浮点和)?

    我正在 llvm clang Apple LLVM 版本 8 0 0 clang 800 0 42 1 上反汇编此代码 int main float a 0 151234 float b 0 2 float c a b printf f c