Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does the MSVC C++ compiler expand a simple Hello World into 4000 lines of assembly?

Lately I'm diving into optimizing my C++ code and as such started to play around with the compiler explorer. Since i am mainly developing on windows with Visual Studio i used the msvc compiler.

At some point msvc got out of hand. after some fiddling around i could narrow it down to the iostream header, that's supposed to be preferred for I/O (SL.io.3).

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

While gcc or clang's total output (main + a static initializer that calls some ios_base init functions) totals about 20 lines of assembly (after the Godbolt compiler explorer filters out directives and comments).
MSVC explodes it into 4000. Most of those lines are separate functions; MSVC's definition of main itself is 7 instructions vs. 8 for gcc/clang. (gcc/clang using GNU/Linux libstdc++ pass an extra length arg to the cout operator overload function, not just 2 pointers like MSVC does when using its own C++ library.)

If i use something like puts instead, MSVC's total output is reasonably compact and comparable to gcc/clang, like here.

Can someone kindly explain to me what is happening here, what im doing wrong or point me in the right direction?

Why are MSVC asm listings so bloated for simple functions using C++ libraries?

like image 521
Flowly Avatar asked May 18 '20 21:05

Flowly


1 Answers

This may not be a complete answer, but I think I can explain much of the differences.

Much of the standard library (e.g., iostreams) is template heavy code. I believe the Microsoft compiler generates more template instantiations and relies on the linker to remove unnecessary ones. I think that's an effect of the different strategies the Windows linker uses versus most Posix ones, but it might also be a result of simply using a different standard library implementation.

If you specify /MD, which tells the compiler you intend to use the DLL version of the standard library, the generated code drops from 4000+ lines to fewer than 500 lines. I don't know precisely why that's the case. Perhaps MSVC knows the DLL library has all the necessary template instantiations while the static library depends on template instantiation from the compiler.

You can elicit an incremental improvement by handling only C++ exceptions (with /EHs). By default, the compiler will generate code that handles asynchronous system exceptions as well. And while your hello-world sample doesn't explicitly use exceptions, parts of the standard library probably do. At this point, it looks like a lot of the additional lines are setting up stack unwinding tables and calling destructors.

A lot of the remaining excess in the MSVC version looks like it exists to unwind the stack while calling destructors, so the exception handling model may be different.

I thought Compiler Explorer had a "clang-cl" option in the past but I don't see it now. clang-cl, generally speaking, is a command driver that interprets cl.exe options and adjusts default options to make clang produce code that's binary ABI compatible with Microsoft code. It would be interesting to see if it generates code like regular clang or whether it ends up emitting code more like MSVC.

like image 167
Adrian McCarthy Avatar answered Sep 19 '22 12:09

Adrian McCarthy