13

最近我正在潜心优化我的 C++ 代码,因此开始使用编译器资源管理器。由于我主要使用 Visual Studio 在 Windows 上进行开发,因此我使用了 msvc 编译器。

在某些时候,msvc 失控了。经过一番摆弄后,我可以将其缩小到 iostream 标头,这应该是 I/O 的首选(SL.io.3)。

#include <iostream>
int main() {
    std::cout << "Hello World!\n";
    return 0;
}

虽然 gcc 或 clang 的总输出(main + 一个调用一些ios_baseinit 函数的静态初始化程序)总共大约 20 行汇编(在 Godbolt 编译器资源管理器过滤掉指令和注释之后)。
MSVC 将其分解为 4000 行。这些行中的大多数是单独的函数;MSVC 对main自身的定义是 7 条指令,而 gcc/clang 则为 8 条。(使用 GNU/Linux 的 gcc/clanglibstdc++将额外长度的 arg 传递给 cout 运算符重载函数,而不仅仅是 MSVC 在使用其自己的 C++ 库时所做的 2 个指针。)

如果我改用类似的东西puts,MSVC 的总输出相当紧凑,与 gcc/clang 相当,就像这里一样。

有人可以向我解释这里发生了什么,我做错了什么或指出我正确的方向吗?

为什么使用 C++ 库的简单函数的 MSVC asm 列表如此臃肿?

4

1 回答 1

6

这可能不是一个完整的答案,但我想我可以解释很多差异。

许多标准库(例如,iostreams)都是模板繁重的代码。我相信 Microsoft 编译器会生成更多模板实例化,并依赖链接器来删除不必要的实例化。我认为这是 Windows 链接器使用的策略与大多数 Posix 策略不同的结果,但这也可能是简单地使用不同的标准库实现的结果。

如果您指定/MD,它告诉编译器您打算使用标准库的 DLL 版本,生成的代码将从 4000+ 行减少到少于 500 行。我不知道为什么会这样。也许 MSVC 知道 DLL 库具有所有必要的模板实例化,而静态库依赖于来自编译器的模板实例化。

您可以通过仅处理 C++ 异常(使用/EHs)来获得增量改进。默认情况下,编译器也会生成处理异步系统异常的代码。虽然您的 hello-world 示例没有明确使用异常,但标准库的某些部分可能会这样做。在这一点上,看起来很多额外的行都在设置堆栈展开表并调用析构函数。

MSVC 版本中的许多剩余多余部分看起来像是在调用析构函数时展开堆栈,因此异常处理模型可能不同。

我认为编译器资源管理器过去有一个“clang-cl”选项,但我现在看不到它。一般来说,clang-cl 是一个命令驱动程序,它解释 cl.exe 选项并调整默认选项以使 clang 生成与 Microsoft 代码兼容的二进制 ABI 代码。看看它是否生成像常规 clang 一样的代码,或者它最终是否发出更像 MSVC 的代码,将会很有趣。

于 2020-05-18T23:57:58.200 回答