【问题标题】:Why are my C++ binary built with -LTO so very large?为什么我用 -LTO 构建的 C++ 二进制文件非常大?
【发布时间】:2021-05-01 14:55:18
【问题描述】:

我正在 Mac 上编译一些二进制文件,但使用更新的编译器(从之前的 ~5MB 增加到 ~20MB),编译后的大小变得很大。我认为这与之前未激活的 LTO(链接时间优化)有关。我没有在 linux 上观察到这个文件膨胀。

在玩过strip 之后(实际上没有减小大小,尽管尝试了基于标志-S -x 并且也没有标志的Xcode,并且由带有标志-s 的自制binutils 配方提供的GNU libtools 条带,所有这些似乎达到同样的效果)我找到了这个工具:https://github.com/google/bloatyBloaty McBloated,当在我的二进制文件上运行时,它会产生这个输出:

    FILE SIZE        VM SIZE    
 --------------  -------------- 
  53.9%  9.72Mi  53.8%  9.72Mi    __GNU_LTO,__wrapper_sects
  32.5%  5.86Mi  32.4%  5.86Mi    __GNU_DWARF_LTO,__debug_info
   6.2%  1.11Mi   6.2%  1.11Mi    __TEXT,__text
   2.2%   403Ki   2.2%   403Ki    __TEXT,__eh_frame
   1.6%   298Ki   1.6%   298Ki    __GNU_LTO,__wrapper_names
   1.0%   177Ki   1.0%   177Ki    Export Info
   0.7%   131Ki   0.7%   131Ki    Weak Binding Info
   0.4%  77.0Ki   0.4%  77.0Ki    __GNU_DWARF_LTO,__debug_str
   0.4%  75.8Ki   0.4%  75.8Ki    __DATA,__gcc_except_tab
   0.2%  44.6Ki   0.2%  44.6Ki    __GNU_LTO,__wrapper_index
   0.2%  39.4Ki   0.2%  39.4Ki    __DATA_CONST,__const
   0.2%  33.1Ki   0.2%  33.1Ki    __GNU_DWARF_LTO,__debug_abbrev
   0.1%  26.4Ki   0.1%  26.4Ki    __GNU_DWARF_LTO,__debug_line
   0.1%  21.7Ki   0.1%  23.6Ki    [20 Others]
   0.1%  19.0Ki   0.1%  19.0Ki    __TEXT,__text_cold
   0.1%  18.1Ki   0.1%  18.1Ki    __TEXT,__const
   0.0%  8.82Ki   0.0%  8.82Ki    __TEXT,__text_startup
   0.0%  8.60Ki   0.0%  8.60Ki    __TEXT,__cstring
   0.0%       0   0.0%  7.18Ki    __DATA,__pu_bss5
   0.0%       0   0.0%  6.88Ki    __DATA,__bss5
   0.0%  5.87Ki   0.0%  5.87Ki    __DATA,__la_symbol_ptr
 100.0%  18.1Mi 100.0%  18.1Mi    TOTAL

那么谁能告诉我这些巨大的*_LTO 部分的用途,以及我如何通过后处理或向我的构建链添加编译标志来摆脱它们。

OS 是 MacOS,我使用的是 g++ 10,这里有完整的跟踪: https://github.com/yanntm/testGithbuActions/runs/1778387086?check_suite_focus=true

我正在尝试尽可能多地编译静态以获得更好的可移植性。然而,二进制文件仍然动态链接到 /usr/lib/libSystem.B.dylib (我不能静态链接这个显然与 libtool)。

我不想要任何调试符号,因为这是面向最终用户的生产二进制文件。

【问题讨论】:

  • 您尝试过哪些剥离选项?
  • @AlanBirtles 我编辑添加了我尝试过的内容,包括 xcode 条和 gnu 版本(因为它们有不同的标志)。第一个条形调用确实从 19MB 变为 18MB,所以它确实做了something。但是二进制大小不是符号,因为膨胀的转储显示。
  • Xcode 的哪个版本?您的源代码的编译标志是什么?您的链接器的链接标志是什么? g++ --version 输出什么?很好奇,为什么不禁用 LTO?
  • @Eljay 所以这是 Github 操作提供的标准容器。 Xcode_12.2,来自 brew 10.2.0_2 的 g++。编译时的标志是-DNDEBUG -O3,链接时的标志是-O3 -all-static -static-libgcc -static-libstdc++。我尝试在编译和链接中添加“-flto”和“-fno-lto”。在两种情况中,带有这些 LTO 部分的二进制文件仍然是 19MB,我觉得由于某些环境配置,它忽略了这些标志。

标签: c++ macos compilation lto


【解决方案1】:

你会找到答案in gcc's documentation

链接时间优化是作为 GCC 前端实现的 在特殊部分发出的 GIMPLE 的字节码表示 .o 个文件。

[...]

由于 GIMPLE 字节码与最终的对象代码一起保存,因此对象 使用 LTO 支持生成的文件比常规目标文件大。

[...]

当前的实现只产生“胖”对象,有效地 编译时间加倍,文件大小最多增加 5 倍 原尺寸。

但是等等,还有更多。您仅使用 -flto 构建。如果您还使用了-ffat-lto-objects,那么,如 gcc 的信息页面中所述:

'-ffat-lto-objects'

胖 LTO 对象是包含中间体的对象文件 语言和目标代码。这使得它们可用于 LTO 链接和正常链接。此选项仅在以下情况下有效 使用 '-flto' 编译并在链接时被忽略。

尝试使用strip 将是徒劳的。 strip 只去除调试数据。这不是调试数据,而是基本上半编译的 C++ 代码,最终编译作为链接周期的一部分发生。如果您想“摆脱它们”,请不要使用 LTO。

编辑:某些 gcc/binutils 配置可能会将 LTO 部分留在目标二进制文件中。我研究了 Fedora 的默认 rpmbuild 配置,它默认使用 LTO 构建,但不会遭受相同的可执行文件膨胀。

事实证明,Fedora 的 rpmbuild 执行了一个 brp-strip-lto 脚本,归结为:

sh -c "$STRIP -p -R .gnu.lto_* -R .gnu.debuglto_* -N __gnu_lto_v1 \"\$@\"" ARG0

关键选项是两个-R 选项,不清楚__gnu_lto_v1 符号是什么,它被-N 删除。

【讨论】:

  • 根据引用的引号,膨胀应该只在 intermediary 二进制文件中,而不是在最终构建的 target 二进制文件中。对吗?
  • 可能有选项或设置来控制它。在 Fedora 33 的构建工具链上默认启用 LTO,但最终的二进制文件不会太大。但如果 LTO 元数据确实在二进制文件中,strip 将不会触及它。
  • 感谢您对它的解释,但正如@Eljay 所说,最终目标似乎不再包含这些部分。也许它们仍然存在,因为由于 OSX libSystem 作为静态库不可用,因此无法实现完整的静态链接?
  • 可能是一些特定于操作系统的东西,我添加了一些我在仔细阅读 Fedora 在其默认启用 LTO 的构建配置中所做的事情后挖掘的更多信息。
  • 感谢您抽出宝贵时间,我会在我的二进制文件上尝试一些类似的 strip -R 标志并回发。是的,问题是我认为特定于 Mac,我使用与 linux 上相同的 Gnu 工具链(而不是例如 clang),但 LTO 问题仅出现在 Mac 上。看起来你的声明“只剥离调试数据”可能不准确地看到 Fedora 构建的功能,所以我们都在学习一些东西:D
猜你喜欢
  • 2019-02-16
  • 1970-01-01
  • 1970-01-01
  • 2021-06-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多