Recently I was writing a program with 3 binary values and I had to handle all 8
possible "situations" and switch is said to be faster, because it uses an
indexed branch table. However, it accepts only one value, while I had 3
different values to check.
I've come up with a little trick to store all 8 possible situations into one
single integer value:
switch ((a == 1 ? 1 : 0) + (b == 1 ? 2 : 0) + (c
== 1 ? 4 : 0))
{
case 0: break;
//none true
case 1: break;
//a true
case 2: break;
//b true
case 3: break;
//a and b true
case 4: break;
//c true
case 5: break;
//a and c true
case 6: break;
//b and c true
case 7: break;
//all true
}
The mathematical formula for each variable:
-
n: numeral system base [2, ∞]
- m: zero-based index of the checked variable [0, ∞]
- v: zero-based index of the variable's value [0, n - 1]
result for one variable = nm * v
As an example, 3 ternary variables:
int
situation = (a == 0 ? 0 : (a == 1 ? 1 : 2)) + (b == 0 ? 0 : (b == 1 ? 3 : 6)) +
(c == 0 ? 0 : (c == 1 ? 9 : 18));
The switch above has to (in every case) get-and-check 4 times (3 times in the
calculation, 1 time to match a case). However, it also has to perform the
addition.
So, is switch in this case faster? I have tested switch's performance (every
single situation 100,000,000 times) against the following if-else if trees:
1. The obvious one:
if (a == 1 && b == 1 && c == 1) ;
else if (a == 1
&& b == 1) ;
else if (a == 1
&& c == 1) ;
else if (b == 1
&& c == 1) ;
else if (a == 1)
;
else if (b == 1)
;
else if (c == 1)
;
else ;
Condition |
Num of checks |
Check sequence |
a & b & c |
3 |
a,b,c |
a & b |
5 |
a,b,c,a,b |
a & c |
6 |
a,b,a,b,a,c |
b & c |
5 |
a,a,a,b,c |
a |
8 |
a,b,a,b,a,c,a |
b |
8 |
a,a,a,b,c,a,b |
c |
8 |
a,a,a,b,a,b,c |
none |
8 |
a,a,a,b,a,b, |
Average: 6.375 checks
Switch/if-tree ratio:
-
calculation into a variable first: 9/10
- calculation straight into switch: 3/4
Switch is faster.
My testing code:
int
counter = 0;
Timer
timer = new Timer(1);
timer.Elapsed += (object
o, ElapsedEventArgs e) =>
{
++counter;
};
timer.Start();
for
(int i = 0; i < 100000000; ++i)
{
for (int a = 0; a < 2; ++a)
{
for (int b = 0;
b < 2; ++b)
{
for (int c = 0;
c < 2; ++c)
{
if (a == 1 && b == 1 && c == 1) ;
else if (a == 1
&& b == 1) ;
else if (a == 1
&& c == 1) ;
else if (b == 1
&& c == 1) ;
else if (a == 1)
;
else if (b == 1)
;
else if (c == 1)
;
else ;
}
}
}
}
timer.Stop();
Console.WriteLine(counter.ToString());
counter = 0;
timer.Start();
for
(int i = 0; i < 100000000; ++i)
{
for (int a = 0; a < 2; ++a)
{
for (int b = 0;
b < 2; ++b)
{
for (int c = 0;
c < 2; ++c)
{
switch ((a == 1 ? 1 : 0) +
(b == 1 ?
2 : 0) +
(c == 1 ?
4 : 0))
{
case 0: break;
case 1: break;
case 2: break;
case 3: break;
case 4: break;
case 5: break;
case 6: break;
case 7: break;
}
}
}
}
}
timer.Stop();
Console.WriteLine(counter.ToString());
2. Dual-check/single-check:
if (a && b)
{
if (c) ;
else ;
}
else if (a && c)
;
else if (b && c)
;
else if (a) ;
else if (b) ;
else if (c) ;
else ;
Condition |
Num of checks |
Check sequence |
a & b & c |
3 |
a,b,c |
a & b |
3 |
a,b,c, |
a & c |
4 |
a,b,a,c |
b & c |
5 |
a,a,a,b,c |
a |
6 |
a,b,a,c,b,a |
b |
6 |
a,a,b,c,a,b |
c |
6 |
a,a,b,a,b,c |
none |
6 |
a,a,b,a,b,c |
Average: 4.875 checks
Switch/if-tree ratio:
-
calculation into a variable first: 18/17
- calculation straight into switch: 1/1
Same speed unless you use a separate variable.
3. Single-check:
if (a)
{
if (b)
{
if (c) ;
else ;
}
else if (c) ;
else ;
}
else if (b)
{
if (c) ;
else ;
}
else if (c) ;
else ;
Condition |
Num of checks |
Check sequence |
a |
3 |
a,b,c |
a & b |
3 |
a,b,c, |
a & b & c |
3 |
a,b,c |
a & c |
3 |
a,b,c |
b |
3 |
a,b,c |
b & c |
3 |
a,b,c |
c |
3 |
a,b,c |
none |
3 |
a,b,c |
Average: 3 checks
Switch/if-tree ratio:
-
calculation into a variable first: 14/9
- calculation straight into switch: 3/2
If-else if tree is quite faster. This is expected, because it makes 3 checks,
while switch makes 4.
4. What if we make the switch nested too?
switch (a)
{
case 0:
switch (b)
{
case 0:
switch (c)
{
case 0: break;
// none
case 1: break;
// c
}
break;
case 1:
switch (c)
{
case 0: break;
// b
case
1: break;
// b & c
}
break;
}
break;
case 1:
switch (b)
{
case 0:
switch (c)
{
case 0: break;
// a
case 1: break;
// a & c
}
break;
case 1:
switch
(c)
{
case 0: break;
// a & b
case 1: break;
// a & b & c
}
break;
}
break;
}
Switch/if-tree ratio: 15/14
If-else if tree still slightly faster.
All these ratios were very consistent, average relative mistake was 0.6%.
Conclusion:
If you want to make your code clear, then it's perhaps better to use a switch
statement. But don't forget to add comments to each case about which situation
it represents.
If clarity is not an issue, use "Single-check" if-else if tree.
Thanks for reading, code safely!