Monday, October 17, 2016

C+++ Compile, Part 2, Vectors vs Native Arrays in x86-64 bit Release Builds

Release Builds

We all understand that release builds make for smaller, faster code. But how? What does the compiler do differently. In C++, the difference be dramatic. Let's take a look at the exact same program in release.

What happened? Well, a couple of things. First, we notice right away that all the function calls to our little array methods are gone. Instead, all the code for our calls are baked right into the main method. This is what is called inlining. Instead of generating code for a new method and then a call for it, the compiler simply generates the code for the method in the context of the caller. There's two advantages of this. First, the advantage is that there's no function call, saving at least some stack manipulations. In particular, if the same value, say, the value of an array, is referenced across several inlined functions, the compiler will simply allocate it to a register, if it can, and then re-use that. This will put a little zoom zoom in the code.

Inlining is the secret sauce that makes C++ templates and STL workable performance-wise. A lot of what STL does is actually inlined, and from there, reduced. All those begin and end methods, ++overload methods and other tidbits in C++ that make up the implementation of iterators were simply optimized away.
We would be tempted to say that we could compare the STL implementation to our simple vector implementation, except that, it looks a lot more like, the compiler was just about smart enough to realize that the vector assignment wasn't used in our simple array methods and eliminated the calls and inlines completely.

In fact, if we look closely in this image, it almost looks like the compiler rather just blew away one of our test methods because it knew it wasn't even really being used. The only thing that remains appears to be a loop that counts to ten, but it never assigns the value to the array. Let's see if we can find out what happened. To do this, we try to back off on some of the optimizations we make in release build and see if we can see what the compiler is doing.

Optimization Options

There are several optimization switches in there, and its not immediately clear what these things also do. We experiment. As we are interested in what happened to our methods, first off, we turn off the inlining by setting "Inline Function Expansion" to Disabled (/Ob0) and then recompile.

 When we examine the compiler output, we can see that the nested layers of function calls have been restored.And, notice that all the STL methods are back in there as well. Suddenly, what was supposed to be a fairly zippy way to make performant but re-usable containers has been utterly ruined. Thus, we've learned an important lesson - that inlining is a pre-requisite to using STL.

Other things happen as well, and we'll work through the switches and what they do. But first, we're going to need to pause and actually dive into a little bit of assembly language.

No comments:

Post a Comment