2

If I write a program in C++CLI / managed C++, does the compiler perform any optimizations?

I know that for C#, there are some optimizations done at compile time, with most optimizations being done by the JIT. Is the same true for C++CLI?

A similar question: can I do the equivalent of an -O2 flag, for C++CLI? I already know about the "-c Release" flag, but I'm unclear on what kind of optimizations it does.

Thanks!

Verdagon
  • 2,456
  • 3
  • 22
  • 36
  • Are you using the C++/CLI compiler to generate pure MSIL, or mixed managed/native? – Ben Voigt Mar 17 '19 at 19:33
  • BTW, the Microsoft Visual C++ compiler has no `-c Release` option. I think you're referring to the project build system, which in turn invokes the real compiler. The real compiler has traditional optimization flags: [/O options](https://learn.microsoft.com/en-us/cpp/build/reference/o-options-optimize-code?view=vs-2017) – Ben Voigt Mar 17 '19 at 19:36
  • I'm using the c++/cli compiler to generate pure MSIL, I believe, no native involved. – Verdagon Mar 18 '19 at 18:22

2 Answers2

5

C++/CLI code is always optimized in the Release build, yes. By whom is the key, you can freely mix as you dare. This tends to go wrong with too much native C++ code getting compiled to MSIL. Hard to notice, the code generator can handle any compliant C++03 code and rarely squeals about any C++1x incantations.

A good reminder that the jitter isn't that much different from a C++ compiler's back-end. MSIL compares pretty well to, say, the IR that LLVM needs. The IR that the MSVC++ compiler uses for native code isn't documented and not visible.

Which makes it a good practice to isolate the native C++ you wrap in its own static library or DLL. But mixing at the function-level is possible, you can switch back-and-forth with #pragma un/managed.

So it is much like you'd guess, #pragma unmanaged code gets the full optimizer love and #pragma managed gets optimized at runtime by the jitter. You'll find jitter optimizations documented in this post.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Quite a few optimizations do take place en route to MSIL generation, but they are limited by (a) the expressiveness of MSIL -- e.g. lack of SIMD, and (b) the need to preserve call stacks and code structure which is visible through reflection (and CAS uses reflection) – Ben Voigt Mar 18 '19 at 00:14
  • And of course "always" is much too strong a word to describe optimization in the Release configuration -- there are project-level settings that affect MSIL generation, and the presence of an attached debugger often affects JIT optimization levels. – Ben Voigt Mar 18 '19 at 00:16
3

When generating native code, the C++/CLI compiler supports the same optimizations as Microsoft's native C++ compiler.

When generating MSIL, the C++/CLI compiler supports a smaller number of optimizations (but still more than C#), and then another optimization pass takes place during JIT (same JIT and same JIT-time optimizations as apply to C#).

For example, loop unrolling is possible when generating MSIL, but auto-vectorization is not, because MSIL doesn't have SIMD instructions. Vectorization may theoretically still be done by the JIT, but in practice the resource constraints of a JIT mean that optimization is less effective.

In addition, there are some optimizations possible for C++ but not C# due to language design. For example, C++ templates (including in C++/CLI) are compiled for each combination of template arguments, while .NET generics (including in C# and in C++/CLI) are fully resolved only based on the generic constraints.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • Thanks for the great answer! Do you happen to know which optimizations are done by the C++/CLI compiler before it produces the MSIL? – Verdagon Mar 18 '19 at 18:24
  • @Verdagon: I don't think that Microsoft publishes a comprehensive list of optimizations performed by their C++ compiler, nevermind which are available for which target architecture (throwing MSIL into the list of instruction sets which the compiler can enable/disable along with SSE, SSE2, AVX may not be perfectly accurate but it's a useful way to think about it). However, you can expect that things like constant propagation and common subexpression elimination occur before outputting MSIL. MSIL is easy enough to read that you can look at the results with `/Od` vs `/O2` and see what optimizes – Ben Voigt Mar 18 '19 at 19:51