Speed, Inner Workings of switch()

I have always wondered, but never thought to ask, "Which is faster, a switch() or a series of if()-else if()'s?" So now I'm asking just that. And I am also interested in how the switch() works, if anyone knows.
My guess is that it makes no difference. Note that it's only possible to use switch when you are testing an integral value (including char) for exact equality. If you want to test for a range, or use other types, you must use if/else.

I tried to compile these two programs in GCC:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// switch.cpp
#include <cstdio>
int main(int argc, char** argv)
{
    char ch = '\0';
    switch (argc) {
        case 1:
            ch = '1';
            break;
        case 2:
            ch = '2';
            break;
        case 3:
            ch = '3';
            break;
        default:
            ch = '*';
            break;
    }
    std::putchar(ch);
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ifelse.cpp
#include <cstdio>
int main(int argc, char** argv)
{
    char ch = '\0';
    if (argc == 1)
        ch = '1';
    else if (argc == 2)
        ch = '2';
    else if (argc == 3)
        ch = '3';
    else
        ch = '*';
    std::putchar(ch);
}


I tried compiling this with GCC, using different optimization settings, e.g:
1
2
g++ -S -O[0..3] -o switch switch.cpp
g++ -S -O[0..3] -o ifelse ifelse.cpp


The resulting assemply code came out almost (but not quite) identical. I think the number of instructions where about the same. My conclusion is that it doesn't matter. It's much more important to focus on the readability and correctness of the code.

Here's the assembly code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
	.file	"switch.cpp"
	.text
	.align 2
	.p2align 4,,15
.globl main
	.type	main, @function
main:
.LFB2:
	leal	4(%esp), %ecx
.LCFI0:
	andl	$-16, %esp
	pushl	-4(%ecx)
.LCFI1:
	movl	$50, %edx
	pushl	%ebp
.LCFI2:
	movl	%esp, %ebp
.LCFI3:
	pushl	%ecx
.LCFI4:
	subl	$4, %esp
.LCFI5:
	movl	(%ecx), %eax
	cmpl	$2, %eax
	je	.L6
	cmpl	$3, %eax
	movb	$51, %dl
	je	.L6
	subl	$1, %eax
	movb	$42, %dl
	je	.L10
	.p2align 4,,7
.L6:
	movl	%edx, (%esp)
	call	putchar
	addl	$4, %esp
	xorl	%eax, %eax
	popl	%ecx
	popl	%ebp
	leal	-4(%ecx), %esp
	ret
	.p2align 4,,7
.L10:
	movb	$49, %dl
	jmp	.L6
.LFE2:
	.size	main, .-main
.globl __gxx_personality_v0
	.section	.eh_frame,"a",@progbits
.Lframe1:
	.long	.LECIE1-.LSCIE1
.LSCIE1:
	.long	0x0
	.byte	0x1
	.string	"zP"
	.uleb128 0x1
	.sleb128 -4
	.byte	0x8
	.uleb128 0x5
	.byte	0x0
	.long	__gxx_personality_v0
	.byte	0xc
	.uleb128 0x4
	.uleb128 0x4
	.byte	0x88
	.uleb128 0x1
	.align 4
.LECIE1:
.LSFDE1:
	.long	.LEFDE1-.LASFDE1
.LASFDE1:
	.long	.LASFDE1-.Lframe1
	.long	.LFB2
	.long	.LFE2-.LFB2
	.uleb128 0x0
	.byte	0x4
	.long	.LCFI0-.LFB2
	.byte	0xc
	.uleb128 0x1
	.uleb128 0x0
	.byte	0x9
	.uleb128 0x4
	.uleb128 0x1
	.byte	0x4
	.long	.LCFI1-.LCFI0
	.byte	0xc
	.uleb128 0x4
	.uleb128 0x4
	.byte	0x4
	.long	.LCFI2-.LCFI1
	.byte	0xe
	.uleb128 0x8
	.byte	0x85
	.uleb128 0x2
	.byte	0x4
	.long	.LCFI3-.LCFI2
	.byte	0xd
	.uleb128 0x5
	.byte	0x4
	.long	.LCFI4-.LCFI3
	.byte	0x84
	.uleb128 0x3
	.align 4
.LEFDE1:
	.ident	"GCC: (GNU) 4.2.3 (Ubuntu 4.2.3-2ubuntu7)"
	.section	.note.GNU-stack,"",@progbits


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
	.file	"ifelse.cpp"
	.text
	.align 2
	.p2align 4,,15
.globl main
	.type	main, @function
main:
.LFB2:
	leal	4(%esp), %ecx
.LCFI0:
	andl	$-16, %esp
	pushl	-4(%ecx)
.LCFI1:
	movl	$49, %eax
	pushl	%ebp
.LCFI2:
	movl	%esp, %ebp
.LCFI3:
	pushl	%ecx
.LCFI4:
	subl	$4, %esp
.LCFI5:
	movl	(%ecx), %edx
	cmpl	$1, %edx
	je	.L4
	cmpl	$2, %edx
	movb	$50, %al
	je	.L4
	xorl	%eax, %eax
	cmpl	$3, %edx
	sete	%al
	leal	42(%eax,%eax,8), %eax
.L4:
	movl	%eax, (%esp)
	call	putchar
	addl	$4, %esp
	xorl	%eax, %eax
	popl	%ecx
	popl	%ebp
	leal	-4(%ecx), %esp
	ret
.LFE2:
	.size	main, .-main
.globl __gxx_personality_v0
	.section	.eh_frame,"a",@progbits
.Lframe1:
	.long	.LECIE1-.LSCIE1
.LSCIE1:
	.long	0x0
	.byte	0x1
	.string	"zP"
	.uleb128 0x1
	.sleb128 -4
	.byte	0x8
	.uleb128 0x5
	.byte	0x0
	.long	__gxx_personality_v0
	.byte	0xc
	.uleb128 0x4
	.uleb128 0x4
	.byte	0x88
	.uleb128 0x1
	.align 4
.LECIE1:
.LSFDE1:
	.long	.LEFDE1-.LASFDE1
.LASFDE1:
	.long	.LASFDE1-.Lframe1
	.long	.LFB2
	.long	.LFE2-.LFB2
	.uleb128 0x0
	.byte	0x4
	.long	.LCFI0-.LFB2
	.byte	0xc
	.uleb128 0x1
	.uleb128 0x0
	.byte	0x9
	.uleb128 0x4
	.uleb128 0x1
	.byte	0x4
	.long	.LCFI1-.LCFI0
	.byte	0xc
	.uleb128 0x4
	.uleb128 0x4
	.byte	0x4
	.long	.LCFI2-.LCFI1
	.byte	0xe
	.uleb128 0x8
	.byte	0x85
	.uleb128 0x2
	.byte	0x4
	.long	.LCFI3-.LCFI2
	.byte	0xd
	.uleb128 0x5
	.byte	0x4
	.long	.LCFI4-.LCFI3
	.byte	0x84
	.uleb128 0x3
	.align 4
.LEFDE1:
	.ident	"GCC: (GNU) 4.2.3 (Ubuntu 4.2.3-2ubuntu7)"
	.section	.note.GNU-stack,"",@progbits
Hmm, ... thanks! That's the conclusion I was leaning to as well, but now it's apparent I could learn more about the compiler commands (I use gcc/g++ as well) and assembly diction. =P
Technically speaking, a switch could be faster than a series of if-elses, though the point is moot if the assembler output shows otherwise :) The reason it could be faster is because the compiler could generate a jump table if your case labels are densely packed (and perhaps in order too). For example, your best bet to help the compiler optimize would be something like:

1
2
3
4
5
6
switch( aValue ) {
  case 1:  /* do stuff */ break;
  case 2: /* do stuff */ break;
  case 3: /* do stuff */ break;
  // ...
};


The compiler might optimize that better than if you put the case labels in some other order.
Exactly. Just because compiler x does this does not mean that this holds true for all compilers.
Yes. All a programmer can do is help the compiler to optimize, and for that reason I would, where possible, choose a switch() statement over a series of if-elses.
Topic archived. No new replies allowed.