Straightforward C++ = ASM optimization question

Started by
7 comments, last by phresnel 15 years, 5 months ago
void do_something(const double variable) { ... }

void call_do_something() {
  double dummy;
  do_something(dummy);
}
Does specifying "const" cause the compiler to avoid the normal assembler push/pop of 'dummy' that happens when invoking do_something? It probably depends on the calling convention used...
Advertisement
Ask your compiler. You can usually get your compiler to tell you what kind of assembly it is generating by using the right command line options. gcc will do it with -S and MSVC with the /FA family of switches.
Quote:Original post by Karl G
Does specifying "const" cause the compiler to avoid the normal assembler push/pop

Why do you think "const" makes a difference here? The caller has to communicate information to the callee, and wether he treats that information as const or not doesn't really change anything for the caller, does it?
I'm have not encountered any case where const declaration would make any change to generated assembly. This refers to equivalent declarations, not value vs. const reference semantics and similar.

While on paper it seems like compiler could use extra knowledge, the volatile nature of C++ memory model effectively makes it redundant. Const is nothing more but a promise that can help the developer.
Quote:Why do you think "const" makes a difference here? The caller has to communicate information to the callee, and wether he treats that information as const or not doesn't really change anything for the caller, does it?


If you specify a variable as "const", nothing can be assigned to that memory location (well, that's not entirely true, but let's assume that's the case for the sake of argument). Normally, when a compiler generates assembler code for calling a function, it will generate code to push local variables onto the stack before the function, then pop them back off afterward to make sure that the underlying function calls don't overwrite whatever important stuff was going on in that method.

This creates an overhead for every single function call of 4 bytes of memory and 2 extra assembler commands.

If "const" in the function declaration were to cue the compiler that "hey, you don't need to push/pop this memory location because it'll be the same when this function returns", that's overhead that would apply every single time the function is called that instead goes *poof*

Quote:Ask your compiler. You can usually get your compiler to tell you what kind of assembly it is generating by using the right command line options. gcc will do it with -S and MSVC with the /FA family of switches.


The reason I ask is that this optimization seems to make a huge amount of sense (to me at least) but I've never heard it mentioned anywhere, and only recently stumbled upon some code (in this forum) with "const" on a primitive type parameter. It's the first time I've seen it in any code, anywhere in about 8 years of C++ programming.
Quote:Original post by Karl G

If "const" in the function declaration were to cue the compiler that "hey, you don't need to push/pop this memory location because it'll be the same when this function returns", that's overhead that would apply every single time the function is called that instead goes *poof*


Except that calling conventions need to be uniform. Remember that a function might be called from different places.

But even then:
double dummy;  do_something(dummy);


Where does do_something() get to know about value of dummy? It cannot assume that it's located at ebp+X (caller's stack frame), since that would be counter-productive - caller could have stored dummy in register.

Even worse, what if someone calls it like:
do_something(&global_dummy)
You would need to extend definition of calling convention to the caller. This would break compatibility with existing linkers.

And if you're asking about this:
double dummy;  do_something(dummy);  do_something(dummy);  do_something(dummy);
The above should result in something similar to:
push dummycall do_somethingcall do_somethingcall do_something
, regardless of const or not.

Quote:The reason I ask is that this optimization seems to make a huge amount of sense (to me at least) but I've never heard it mentioned anywhere, and only recently stumbled upon some code (in this forum) with "const" on a primitive type parameter. It's the first time I've seen it in any code, anywhere in about 8 years of C++ programming.


C++ code is static, and compiler is surprisingly good at determining when something is const or not, regardless of whether you specify it or not.

Const-correctness is a great tool to avoiding erroneous changes, and especially some C++ quirks with auto-allocations and implicit conversions. But as far as modern compilers go, it just doesn't add any information compiler wouldn't have anyway.

As it happens, compilers filter out redundant statements quite well
int x = 10;int y = 20;foo(x, y);
results in
push 10push 20call foo
Not to mention they are sometimes capable of evaluating result of a loops, even if some parameters are completely run-time specified. And inlining removes the call penalties altogether.

But as it happens, function calls aren't all that expensive in the long run, except in a few corner cases.
'Const' means 100% absolutely nothing from the compiler's perspective. Nothing at all. If a compiler wants to figure out if something is 'const', it can do it just as easily without you pointing it out. [is X ever assigned to? if not, X is const]
Quote:Original post by Karl G
Quote:Why do you think "const" makes a difference here? The caller has to communicate information to the callee, and wether he treats that information as const or not doesn't really change anything for the caller, does it?
If you specify a variable as "const", nothing can be assigned to that memory location (well, that's not entirely true, but let's assume that's the case for the sake of argument). Normally, when a compiler generates assembler code for calling a function, it will generate code to push local variables onto the stack before the function, then pop them back off afterward to make sure that the underlying function calls don't overwrite whatever important stuff was going on in that method.
It really doesn't have anything to do with the question at hand, but the following is perfectly valid:
void F(const int* a, int* b){ *b = *a + 1; }...int x = something;F(&x,&x);//x = something + 1.
Const assures that you won't write to *a, and you didn't. You wrote to *b. It just so happens that they both point to the same location. Saying that you won't write to a specific location actually WOULD be something compilers would be interested in, but that isn't what const means.
Quote:Original post by Karl G
This creates an overhead for every single function call of 4 bytes of memory and 2 extra assembler commands.

If "const" in the function declaration were to cue the compiler that "hey, you don't need to push/pop this memory location because it'll be the same when this function returns", that's overhead that would apply every single time the function is called that instead goes *poof*
For linking between already compiled code, these are required to assure that the function remains general, and is part of the contract established by agreeing on a calling convention. Function calls that occur within the same compilation unit that are not establishing a calling convention contract of this kind can freely pass variables to functions however they please [not quite freely, but compilers are a little more willing to skirt the rules with respect to calling convention in a way similar to how the rules are skirted for function inlining], and quite often do so through registers. [Matter of fact, many architectures have registers specifically allocated to the passing of function parameters and return values] For a bit more information on this subject, you might want to look into stdcall [stack-based function calls] calling convention vs something like fastcall [register-based function calls] since this conversation clearly rotates around x86.

Welcome to the world of static compilation.

*edit* Antheus, you have outrun me yet again it seems. Damn my queueing up my replies, and responding to them later when I get the time. I'd say you ninja'ed me, but frankly I was such an easy target that it certainly didn't require anything ninja-related to out run me on this one.
Quote:Original post by Karl G
Normally, when a compiler generates assembler code for calling a function, it will generate code to push local variables onto the stack before the function, then pop them back off afterward to make sure that the underlying function calls don't overwrite whatever important stuff was going on in that method.

Push local variables? No. To assign space for local variables, the compiler only needs a constant amount of instructions, regardless of how many local variables the function needs.
//start of function - reserve space for local variablespush bp    //remember old base pointermov bp, sp //remember old stack pointersub sp, 60 //make room for 60 bytes of local storage, for example 15 integers//end of functionmov sp, bp //restore old stack pointerpop bp     //restore old base pointerret        //return from function

If you mean push arguments, then yes, the compiler has to push the arguments one by one. But popping all of them off the stack is just one instruction.
push 25    // push third argumentpush 99    // push second argumentpush 7     // push first argumentcall fun   // call functionadd sp, 12 // remove the arguments


I think your confusion stems from not understanding the difference between arguments and local variables (at the assembly level).

Quote:Original post by Karl G
"hey, you don't need to push/pop this memory location because it'll be the same when this function returns"

But that's not the point. The important part is that the caller has to communicate information somehow to the callee. The value never changes, ok - but what IS the value? The information still has to be passed. Imagine I get caught DUI:
Quote:
Officer: "Sir, would you please step out of the car. What is your name?"
Me: "Why would you want to know? It's been the same name for as long as I can remember, and it sure isn't gonna change."
In gcc, you can make promises you should hold (via attributes). Have a look at the discussion we recently had at ompf, especially this post.

This topic is closed to new replies.

Advertisement