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 */
...