为什么 MSVC C++ 编译器会将一个简单的 Hello World 扩展成 4000 行汇编代码?
Why does the MSVC C++ compiler expand a simple Hello World into 4000 lines of assembly?
最近我正在深入优化我的 C++ 代码,因此开始使用编译器资源管理器。因为我主要在 windows 和 Visual Studio 上开发,所以我使用了 msvc 编译器。
在某些时候 msvc 失控了。经过一些摆弄之后,我可以将它缩小到 iostream header,这应该是 I/O (SL.io.3) 的首选。
#include <iostream>
int main() {
std::cout << "Hello World!\n";
return 0;
}
虽然 gcc 或 clang 的总输出(main + 调用一些 ios_base
init 函数的静态初始化器)总计大约 20 行汇编(在 Godbolt 编译器资源管理器过滤掉指令和注释之后)。
MSVC explodes it 到 4000。这些行中的大部分都是单独的函数; MSVC 对 main
本身的定义是 7 条指令,而 gcc/clang 是 8 条指令。 (gcc/clang 使用 GNU/Linux libstdc++
将额外的长度 arg 传递给 cout 运算符重载函数,而不仅仅是像 MSVC 在使用其自己的 C++ 库时那样的 2 个指针。)
如果我改用 puts
之类的东西,MSVC 的总输出相当紧凑并且与 gcc/clang 相当,例如 here.
有人可以向我解释这里发生了什么,我做错了什么或指出正确的方向吗?
为什么 MSVC asm 列表对于使用 C++ 库的简单函数如此臃肿?
这可能不是一个完整的答案,但我想我可以解释其中的大部分差异。
许多标准库(例如 iostreams)都是模板繁重的代码。我相信 Microsoft 编译器会生成更多模板实例化并依赖链接器删除不必要的实例化。我认为这是 Windows 链接器与大多数 Posix 链接器使用不同策略的结果,但也可能是简单地使用不同的标准库实现的结果。
如果您指定 /MD
,它告诉编译器您打算使用标准库的 DLL 版本,生成的代码将从 4000 多行减少到不到 500 行。我不知道为什么会这样。也许 MSVC 知道 DLL 库具有所有必要的模板实例化,而静态库依赖于编译器的模板实例化。
您可以通过仅处理 C++ 异常(使用 /EHs
)来获得增量改进。默认情况下,编译器也会生成处理异步系统异常的代码。虽然您的 hello-world 示例没有明确使用异常,但部分标准库可能会使用。在这一点上,看起来很多额外的行都在设置堆栈展开表和调用析构函数。
MSVC 版本中的很多剩余多余部分看起来是为了在调用析构函数时展开堆栈而存在,因此异常处理模型可能有所不同。
我认为 Compiler Explorer 过去有一个 "clang-cl" 选项,但我现在看不到了。一般来说,clang-cl 是一个命令驱动程序,它解释 cl.exe 选项并调整默认选项以使 clang 生成与 Microsoft 代码兼容的二进制 ABI 代码。看看它是否会像常规 clang 一样生成代码,或者它最终是否会发出更像 MSVC 的代码,这将很有趣。
最近我正在深入优化我的 C++ 代码,因此开始使用编译器资源管理器。因为我主要在 windows 和 Visual Studio 上开发,所以我使用了 msvc 编译器。
在某些时候 msvc 失控了。经过一些摆弄之后,我可以将它缩小到 iostream header,这应该是 I/O (SL.io.3) 的首选。
#include <iostream>
int main() {
std::cout << "Hello World!\n";
return 0;
}
虽然 gcc 或 clang 的总输出(main + 调用一些 ios_base
init 函数的静态初始化器)总计大约 20 行汇编(在 Godbolt 编译器资源管理器过滤掉指令和注释之后)。
MSVC explodes it 到 4000。这些行中的大部分都是单独的函数; MSVC 对 main
本身的定义是 7 条指令,而 gcc/clang 是 8 条指令。 (gcc/clang 使用 GNU/Linux libstdc++
将额外的长度 arg 传递给 cout 运算符重载函数,而不仅仅是像 MSVC 在使用其自己的 C++ 库时那样的 2 个指针。)
如果我改用 puts
之类的东西,MSVC 的总输出相当紧凑并且与 gcc/clang 相当,例如 here.
有人可以向我解释这里发生了什么,我做错了什么或指出正确的方向吗?
为什么 MSVC asm 列表对于使用 C++ 库的简单函数如此臃肿?
这可能不是一个完整的答案,但我想我可以解释其中的大部分差异。
许多标准库(例如 iostreams)都是模板繁重的代码。我相信 Microsoft 编译器会生成更多模板实例化并依赖链接器删除不必要的实例化。我认为这是 Windows 链接器与大多数 Posix 链接器使用不同策略的结果,但也可能是简单地使用不同的标准库实现的结果。
如果您指定 /MD
,它告诉编译器您打算使用标准库的 DLL 版本,生成的代码将从 4000 多行减少到不到 500 行。我不知道为什么会这样。也许 MSVC 知道 DLL 库具有所有必要的模板实例化,而静态库依赖于编译器的模板实例化。
您可以通过仅处理 C++ 异常(使用 /EHs
)来获得增量改进。默认情况下,编译器也会生成处理异步系统异常的代码。虽然您的 hello-world 示例没有明确使用异常,但部分标准库可能会使用。在这一点上,看起来很多额外的行都在设置堆栈展开表和调用析构函数。
MSVC 版本中的很多剩余多余部分看起来是为了在调用析构函数时展开堆栈而存在,因此异常处理模型可能有所不同。
我认为 Compiler Explorer 过去有一个 "clang-cl" 选项,但我现在看不到了。一般来说,clang-cl 是一个命令驱动程序,它解释 cl.exe 选项并调整默认选项以使 clang 生成与 Microsoft 代码兼容的二进制 ABI 代码。看看它是否会像常规 clang 一样生成代码,或者它最终是否会发出更像 MSVC 的代码,这将很有趣。