SIGN UP MEMBER LOGIN:    
ARTICLE

Performance of If-else if tree vs. Switch (multiple variables) in C#

Posted by Jure Articles | Current Affairs September 28, 2010
This article demonstrates multiple ways to implement if-else if tree, when there are multiple values to check, and compares their performance to a switch.
Reader Level:

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!
 

Login to add your contents and source code to this article
share this article :
post comment
 

Thanks for the reply! The only problem with that is that first you have convert n-choice variable to [0, n-1] interval, then shift the value - which is what conditional operator (?) does in one step without performing any arithmetic or bitwise operations with values, but simply by checking the values against certain criteria.

For example, two variables below can't use shift operators:

int apples = 12 // number of apples - ternary (few, moderate, a lot)
bool mature = false // is eatable? - binary (true, false)

int situation = (apples < 5 ? 0 : (apples <= 10 ? 1 : 2)) + (mature ? 3 : 0);

switch(situation)
{
case 0: // few uneatable apples
case 1: // moderate number of uneatable apples
case 2: // a lot of uneatable apples ---> this get's executed
case 3: // few tasty apples
case 4: // moderate number of tasty apples
case 5: // a lot of tasty apples
}

Posted by Jure Oct 07, 2010

try this :

        static void newswitchtest()
        {
            Stopwatch timer;
            timer = Stopwatch.StartNew();
            timer.Stop();

            int bin;

            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)
                        {
                            bin = a + (b << 1) + (c << 2);
                            switch (bin)
                            {
                                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(timer.ElapsedMilliseconds.ToString());
        }

My tests :

9704 ms if/else
8251 ms switch
5817 ms the code above

Good article.

Posted by lgsalamon Oct 07, 2010

Good thinking. The performance isn't that good, though, if-tree was quite faster when I tested it. Of course I called the delegate methods in the if-tree too. The reason must be in addition. If-tree checks 3 values, then calls appropriate method, while the array of delegate-methods has to check values and calculate the index too.

BIG advantage is far less code, and clarity, also easy to maintain. I will definitely use array of delegates from now on, unless I have to make the program repeat the checking a whole bunch of times, like I did when testing.

Thanks for the comment!

Posted by Jure Sep 29, 2010

Another possibility would be to create an array of methods using a delegate type and then instead of using a switch, just index into the array. That could be quite efficient if there are more than 3 conditions, right?

Posted by Sam Hobbs Sep 28, 2010
Team Foundation Server Hosting
Become a Sponsor
PREMIUM SPONSORS
  • ceTE software specializes in components for dynamic PDF generation and manipulation. The DynamicPDF™ product line allows you to dynamically generate PDF documents, merge PDF documents and new content to existing PDF documents from within your applications.
    ceTE software specializes in components for dynamic PDF generation and manipulation. The DynamicPDF™ product line allows you to dynamically generate PDF documents, merge PDF documents and new content to existing PDF documents from within your applications. Visit DynamicPDF here
Nevron Gauge for SharePoint
Become a Sponsor