最近,我开始致力于优化C++代码,因此开始与编译器浏览器一起玩。由于我主要使用Visual Studio在Windows上进行开发,因此我使用了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条指令。 (使用GNU/Linux libstdc++
的gcc/clang将额外的长度arg传递给cout运算符重载函数,而不仅仅是2个指针,如MSVC在使用其自己的C++库时那样。)如果我改用
puts
之类的东西,则MSVC的总输出相当紧凑,可以与gcc/clang相媲美,例如here。有人可以向我解释一下这里发生了什么,我做错了什么,或者指出正确的方向吗?
为什么对于使用C++库的简单功能而言,MSVC asm列表如此so肿?
最佳答案
这可能不是一个完整的答案,但是我认为我可以解释很多差异。
许多标准库(例如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那样发出代码。