LLVM 项目博客

LLVM 项目新闻和来自一线的信息

通过构造函数类型归属减少调试信息大小

调试信息的构造函数类型归属

背景

类类型信息是调试信息大小的主要贡献者。Clang 已经有一些优化可以减少类类型信息的大小,这些优化基于调试信息可以分散到多个编译单元的假设。因此,我们不必在每个引用类的编译单元中发出类类型信息,而只需要在一个地方发出它。(对于所有其他引用,发出更小的类前向声明就足够了。)例如,现有的优化之一是 vtable 归属,其中动态 C++ 类的类型信息仅在发出其 vtable 时发出。

构造函数类型归属

构造函数归属是一种类似的优化,它适用于几乎所有具有构造函数的类。它在任何发出构造函数定义的地方发出类的类型信息。与 vtable 归属不同,类的类型信息可以发出多次,但它对调试信息有很大影响,因为它适用于很大一部分类。如果所有类的构造函数都是在线以外定义的,则类类型信息只会发出一次。如果有构造函数在线内定义,则内联构造函数以及类类型信息将在调用构造函数的每个编译单元中发出。

构造函数归属假设,如果用户希望类具有调试信息,那么该类在程序中的某个地方被构造。这是一个合理的假设,因为在调试器中查看的类可能存在于程序内存中,并且任何存在于内存中的类都必须被构造。

尽管所有类都有构造函数,但有一些类型的类不适用构造函数归属:平凡类、聚合类和具有 constexpr 构造函数的类。可以不发出构造函数来创建这些类的实例,因此我们无法保证调试信息会被发出。但是,这些类型的类通常很小,因此我们可能在使用构造函数归属对它们进行优化时看不到太大改进。

可以使用 -Xclang -fuse-ctor-homing 启用构造函数归属。最终,计划在 Clang 中默认启用它,以便它作为 -fno-standalone-debug 的一部分进行。在 Clang 的 -debug-info-kind= 标志方面,构造函数归属实现为 -debug-info-kind=constructor,比 -debug-info-kind=limited 低一级。

大小改进

发出更少的类类型信息使我们能够显着减少目标文件的大小。在 Linux 上的 Chrome 调试构建中(使用拆分 dwarf 进行调试信息),带有构造函数类型归属的 .o 和 .dwo 文件大小大约减少了 30%(构建目录大小总体减少了 20%)。在 Linux 上的 Clang 调试构建中,.o 文件大小大约减少了 48%(构建目录总体减少了 38%)。在 Windows 上,Chrome 和 Clang 的 .obj 文件都减少了 37%。

较小的目标文件大小也会提高链接时间和 GDB 加载时间。在 Windows 上,使用构造函数归属链接 Chrome 快了 6%,而链接 Clang 快了 34%。在 Linux 上,Chrome 的链接时间没有明显变化,但链接 Clang 快了 25%。

在我的机器上测量,不使用 --gdb-index,Clang 的 GDB 加载时间大约为 2 分 30 秒(没有构造函数归属),使用构造函数归属则为 1 分钟。如果启用 --gdb-index,则无论如何 GDB 启动时间大约为 1 秒,并且使用构造函数归属的二进制文件大小大约减少了 30%。

潜在的陷阱

理想情况下,构造函数归属不应该改变调试时可用的调试信息,但在某些情况下它确实会改变。尽管这在 C++ 中是未定义的行为,但可以定义一个具有非平凡构造函数的类,并在不调用构造函数的情况下创建该类的实例(这通常在 C 代码中完成,C 代码中没有构造函数)。

Foo *p = malloc(sizeof(Foo));
p->someField = 1;

Foo 的构造函数从未被调用,因此它的调试信息也从未被发出。

在 Chrome 中启用构造函数类型归属后,我们发现 libc++ 中有一些类避免了调用构造函数,并且由于各种原因,这很难改变。为了确保它们仍然具有调试信息,Clang 中有一个新的属性称为 [[standalone_debug]]。如果一个类具有该属性,它将具有与使用 -fstandalone-debug 构建时相同的调试信息。这可用于获取否则会因构造函数归属(或任何其他调试信息优化)而省略类型信息的类的调试信息。

我还研究了是否有其他常见情况会导致构造函数归属省略调试信息。手动比较 Clang 构建中可用的调试信息显示了一些缺失的类型。有一些类在任何地方都没有使用。还有一些伪命名空间类,它们只有静态方法,因此从未被构造。查看调试信息的差异有点困难,因为存在很多目标文件和类型(我看到的多数缺失类型是因为该类型没有在我的比较的特定二进制文件或目标文件集中构造),因此可能还有一些我错过的其他情况。

总结

构造函数类型归属是一种新的优化,可以极大地减少目标文件中调试信息的大小。目前,可以使用 cc1 标志 -fuse-ctor-homing 启用它,并且计划在 Clang 中默认启用它作为 -fno-standalone-debug 的一部分。如果您想减小调试构建的大小,请尝试在构建中添加 -Xclang -fuse-ctor-homing,并告诉我们它节省了多少目标文件大小。