I agree with answers above: you generally don't need micro-optimizations, especially with high-level languages with optimizing compilers.
However, I want to add one more, slightly lower point of view.
Let's pretend (almost)all optimizations are OFF and find out what machine code we end up with:
In case 1:
When logMode
is false
, we end up just with one jump instruction (branch on if
) and proceed right to useful work
When logMode
is true
, we end up with at least three jumps (branch + call + return) and executing whatever inside log()
function
In case 2:
logMode
state, we have at least two jumps (call + return) and whatever inside function we calling (that our noop
function is empty doesn't means it produces no code). (and also pointer adds indirection)Real examples (built with `gcc -c -O0 testX.c -o testX`):
test1.c:
#include <stdio.h>
void log(void) { printf("Hello\n"); }
int main(int argc, char **argv)
{
int logMode = 0;
int result;
while (1) {
if (logMode == 1) {
log();
}
result = 1; /* simulate useful work */
}
return result;
}
test1 disassembly fragment:
...
0000000000000016 <main>:
16: 55 push %rbp
17: 48 89 e5 mov %rsp,%rbp
1a: 48 83 ec 20 sub $0x20,%rsp
1e: 89 7d ec mov %edi,-0x14(%rbp)
21: 48 89 75 e0 mov %rsi,-0x20(%rbp)
25: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
/* start of loop */
2c: 83 7d fc 01 cmpl $0x1,-0x4(%rbp) /* compare `logMode` to `1` */
30: 75 05 jne 37 <main+0x21> /* if `false`, jump directly to "useful work" (37) */
32: e8 00 00 00 00 call 37 <main+0x21> /* call log */
37: c7 45 f8 01 00 00 00 movl $0x1,-0x8(%rbp) /* "useful work" */
3e: eb ec jmp 2c <main+0x16> /* back to start of the loop */
...
test2.c:
#include <stdio.h>
void log(void) { printf("Hello\n"); }
void noop(void) { /* nothing here */ }
void (*func_ptr)(void);
int main(int argc, char **argv)
{
int logMode = 0;
int result;
if(logMode == 1){
func_ptr = log;
} else {
func_ptr = noop;
}
while (1) {
func_ptr();
result = 1; /* simulate useful work */
}
return result;
}
test2 disassembly fragment:
...
0000000000000016 <noop>: /* here's five lines of our "empty" function */
16: 55 push %rbp
17: 48 89 e5 mov %rsp,%rbp
1a: 90 nop
1b: 5d pop %rbp
1c: c3 ret
000000000000001d <main>:
1d: 55 push %rbp
1e: 48 89 e5 mov %rsp,%rbp
21: 48 83 ec 20 sub $0x20,%rsp
25: 89 7d ec mov %edi,-0x14(%rbp)
28: 48 89 75 e0 mov %rsi,-0x20(%rbp)
2c: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
33: 83 7d fc 01 cmpl $0x1,-0x4(%rbp)
37: 75 10 jne 49 <main+0x2c>
39: 48 8d 05 00 00 00 00 lea 0x0(%rip),%rax
40: 48 89 05 00 00 00 00 mov %rax,0x0(%rip)
47: eb 0e jmp 57 <main+0x3a>
49: 48 8d 05 00 00 00 00 lea 0x0(%rip),%rax
50: 48 89 05 00 00 00 00 mov %rax,0x0(%rip)
/* start of loop */
57: 48 8b 05 00 00 00 00 mov 0x0(%rip),%rax /* loading function pointer from memory into register */
5e: ff d0 call *%rax /* calling function regardless we want logs */
60: c7 45 f8 01 00 00 00 movl $0x1,-0x8(%rbp) /* useful work */
67: eb ee jmp 57 <main+0x3a> /* back to start of the loop */
...