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:
- Regardless of
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 */
...
So in case 1 you have:
- Less code (doesn't means 'faster')
- Less jumps when logs not needed
- Clear sources which easy to understand (may be most important of these)
- One more branch when you need logs
In case 2 you have:
- Unnecessary extra call when you don't need logs
- Indirect call (possible extra memory read to obtain function pointer)
Of course, all of these usually not a deal, and became even more or completely negligible when you turn optimizations ON. (maybe, except, function pointer, which may "tie compiler's hands" in some optimizations)
And at the end: most reliable way to find which is faster is to measure it.
logModeto be usuallyfalse, the second if usuallytrue.if(logmode) while(running) { log(); run(); } else while (running) run();...if(logMode == true)is a C antipattern. Useif (logMode).