Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How wasteful would it be to call an empty function?

Tags:

c++

function

These three points all relate to the same "empty function" question:

  • How much processing time is wasted when calling an empty function?
  • Would it make a huge impact to call 100, or even 1000 empty functions?
  • What if these empty functions required arguments?

Important edit Is calling an empty virtual function the same?


Edit SO basically you are all saying that in most cases the compiler will optimize this out.

But now I'm curious as this still applies to the question. What if there's a situation like so, where it is not known at compile time when an empty function will be called? Will it immediately go to the stack then exit?

class base{
public:
    virtual void method() = 0;
};

class derived1: public base{
public:
    void method() { }
};

class derived2: public base{
public:
    void method(){ std::cout << " done something  "; }
};


int main()
{
    derived1 D1;
    derived2 D2;
    base* array[] = { &D1, &D2 };
    for( int i = 0; i < 1000; i ++)
    {
        array[0]->method();// nothing
        array[1]->method();// Something
    }
    return 0;
}
like image 835
Andrew Avatar asked Dec 17 '13 23:12

Andrew


2 Answers

While it will most undoubtedly be optimized out completely by any decent compiler, here's a comparison.

void EmptyFunction() {}
void EmptyFunctionWithArgs(int a, int b, int c, int d, int e) {}
int EmptyFunctionRet() {return 0;}
int EmptyFunctionWithArgsRet(int a, int b, int c, int d, int e) {return 0;}

int main() {   
    EmptyFunction();
    EmptyFunctionWithArgs(0, 1, 2, 3, 4);
    EmptyFunctionRet();
    EmptyFunctionWithArgsRet(5, 7, 6, 8, 9);
}

In Debug Mode with VC++11 (which has very little if any optimization) here's what the calls look like:

EmptyFunction();

call    ?EmptyFunction@@YAXXZ            ; EmptyFunction

EmptyFunctionWithArgs(0, 1, 2, 3, 4);

push    4
push    3
push    2
push    1
push    0
call    ?EmptyFunctionWithArgs@@YAXHHHHH@Z    ; EmptyFunctionWithArgs
add     esp, 20                    ; 00000014H

EmptyFunctionRet();

call    ?EmptyFunctionRet@@YAHXZ        ; EmptyFunctionRet

EmptyFunctionWithArgsRet(5, 7, 6, 8, 9);

push    9
push    8
push    6
push    7
push    5
call    ?EmptyFunctionWithArgsRet@@YAHHHHHH@Z    ; EmptyFunctionWithArgsRet
add     esp, 20                    ; 00000014H

The int returning functions look like this:

int EmptyFunctionRet() and int EmptyFunctionWithArgsRet()

push   ebp
mov    ebp, esp
sub    esp, 192                ; 000000c0H
push   ebx
push   esi
push   edi
lea    edi, DWORD PTR [ebp-192]
mov    ecx, 48                    ; 00000030H
mov    eax, -858993460                ; ccccccccH
rep    stosd
xor    eax, eax
pop    edi
pop    esi
pop    ebx
mov    esp, ebp
pop    ebp
ret    0

The functions with no return value are the same, except they don't have the xor eax, eax.

In Release Mode VC++11 doesn't bother calling any of the functions. But it does generate definitions for them. The int returning functions look like this:

int EmptyFunctionRet() and int EmptyFunctionWithArgsRet()

xor    eax, eax
ret    0

Whereas the non-returning functions again remove the xor eax, eax.

You can see easily that the non-optimized functions are extremely bloated, and running them in a loop of however many times would absolutely cost time, whereas the loop itself would be removed with optimizations turned on. So do yourself a favor folks, release with optimizations on.

You shouldn't worry about this, as it violates "Don't Optimize Yet." If you're working with an obscure platform (like a 27-bit MCU), and your compiler is garbage, and your performance needs improvement, then you would profile your code to see where you need improvement (it's likely not empty function calls). As it stands, this is really just an exercise that shows how much we should let the compiler do the heavy lifting.

EDIT FOR YOUR EDIT:

Interestingly, virtual functions are a known problem with optimizers, as they have extreme trouble figuring out where pointers are actually going to go. Some contrived examples might eliminate the call, but if there's a call through a pointer, it's probably not going to be removed.

Compiling the following in VC++11 Release Mode:

class Object {
public:
    virtual void EmptyVirtual() {}
};

int main() {   
    Object * obj = new Object();
    obj->EmptyVirtual();
}

Yields the following snippet among the setup code:

mov     DWORD PTR [ecx], OFFSET ??_7Object@@6B@
mov     eax, DWORD PTR [ecx]
call    DWORD PTR [eax]

Which is the call to the virtual function.

like image 143
Sam Cristall Avatar answered Sep 30 '22 19:09

Sam Cristall


How much processing time is wasted when calling an empty function?

If you can make the compiler keep your empty function, there is the overhead of calling the function and the overhead of returning a value. The overhead of calling depends on the number of parameters and how they are passed.

The actual overhead depends on the processor and how the compiler generates the code.
For example, the processor may have enough registers to contain every argument or the compiler may have to push arguments on a stack, and access them on a stack inside the function.

Edit1:
Processors like to process sequential data instructions. Branches, jumps or function calls force the processors to change their instruction pointers and maybe reload their instruction cache. Since these operations are not processing data instructions, they are wasting time and slowing down the performance of your program. The degradation of performance from these branches is only noticeable in high performance programs (lots of data to process) or programs that need to meet critical deadlines (such as moving paper through a printer to avoid paper jams).

Would it make a huge impact to call 100, or even 1000 empty functions?

Depends on your definition of "impact". With the present speed of desktop processors, 100 or 1000 repetitions can occur in less than a second (more like less than 1 millisecond).

You would spend more time compiling (translating and linking) these functions.

What if these empty functions required arguments?

Like I stated at the top, depends on the compiler and processor.

Is calling an empty virtual function the same?

A virtual function call may require one or two more processor instructions; depends on the processor.

Profile don't Assume
Profiling is your friend. Profiling will tell you how much time is spent in each function in your program.

Don't Microoptimize
Your concerns are called micro-optimizations. You could be optimizing or worrying about code that only gets executed 20% or less of the time. Or it is stuck waiting for an external process such as a mouse click or file I/O.

Put performance issues aside and focus on the correctness and robustness of your program. A program that runs incorrectly is bad regardless of how fast or slow it is. A program that can be easily broken or crashes is worthless regardless of how soon until the crash.

Only worry about performance when Users complain or your program is missing critical real-time deadlines (such as missing data from a streaming input source).

like image 32
Thomas Matthews Avatar answered Sep 30 '22 18:09

Thomas Matthews