pull down to refresh

I mainly program microcontrollers in C, but I also develop windows software in C#. Yesterday, while writing code for an SMS system in C, I encountered a situation that has happened to me before, but this time I think I’m obsessed with it! My main concern is the performance of the switch statement and if-else. Which statement performs better in C? Visually, I prefer the switch, and it’s easier to read, but in this specific case, I’m wondering whether the switch or if-else is better in terms of performance.
The purpose of the code is to check if an SMS needs to be sent. For that, I have a 16-bit variable, where each bit represents a specific action to be performed:
MESSAGE_TYPE TypeMsgToSend_ui16 = MESSAGE_VOID;

typedef enum _MESSAGE_TYPE
{
    MESSAGE_VOID = (uint16_t)0x00,
    
    MESSAGE_NO_SIGNAL_1 = (uint16_t)0x01,
    MESSAGE_WITH_SIGNAL_1 = (uint16_t)0x02,
    MESSAGE_NO_SIGNAL_2 = (uint16_t)0x04,
    MESSAGE_WITH_SIGNAL_2 = (uint16_t)0x08,
    MESSAGE_NO_SIGNAL_3 = (uint16_t)0x10,
    MESSAGE_WITH_SIGNAL_3 = (uint16_t)0x20,
    MESSAGE_NO_SIGNAL_4 = (uint16_t)0x40,
    MESSAGE_WITH_SIGNAL_4 = (uint16_t)0x80,

    MESSAGE_RUT_RESET = (uint16_t)0x100,
    MESSAGE_TEST_MESSAGE = (uint16_t)0x200,
    MESSAGE_RESET_CONFIRMATION = (uint16_t)0x400,
            
    MESSAGE_RESERVED_1 = (uint16_t)0x800,
    MESSAGE_RESERVED_2 = (uint16_t)0x1000,
    MESSAGE_RESERVED_3 = (uint16_t)0x2000,
    MESSAGE_RESERVED_4 = (uint16_t)0x4000,
    MESSAGE_RESERVED_5 = (uint16_t)0x8000,
    
} MESSAGE_TYPE;
Currently, I have implemented an algorithm using if-else:
if (TypeMsgToSend_ui16 != MESSAGE_VOID) { 
    if ((TypeMsgToSend_ui16 & MESSAGE_TEST_MESSAGE) == MESSAGE_TEST_MESSAGE) {
       TypeMsgToSend_ui16 &= ~(MESSAGE_TEST_MESSAGE);
       Encode_Command(RUT_COMMAND_TO_EXECUTE_SMS_SEND, PHONENUMBER, TEST_MESSAGE_TEXT); 
     } 
    else {
        if ((TypeMsgToSend_ui16 & MESSAGE_RESET_CONFIRMATION) == MESSAGE_RESET_CONFIRMATION) {
            TypeMsgToSend_ui16 &= ~(MESSAGE_RESET_CONFIRMATION); 
            Encode_Command(RUT_COMMAND_TO_EXECUTE_SMS_SEND, PHONENUMBER, RESET_CONFIRMATION_TEXT);
         }
         else {
            // ...
         }
     }
}
The order of checks determines which SMS will be sent. My question is whether this method is more or less efficient than using a for loop to go through the bits of TypeMsgToSend_ui16 with a switch case inside the loop.
Adding a loop and switch statement might be more readable/maintainable if you ever need to add more bits to check in the future.
However, if your only concern is raw performance, you're better off with just if-else statements.
Modern compilers do all sorts of witchcraft to automatically optimize your code. If-else chains can be optimized into efficient branch predictions. Some compilers will automatically flatten predictable if-else chains to minimize incorrect predictions.
You could further optimize your if-else example by avoiding deeply-nested if statements. This helps readability, and it helps the compiler make better branch predictions.
for example:
if (TypeMsgToSend_ui16 & MESSAGE_TEST_MESSAGE) {
    TypeMsgToSend_ui16 &= MESSAGE_TEST_MESSAGE;
    Encode_Command(RUT_COMMAND_TO_EXECUTE_SMS_SEND, PHONENUMBER, TEST_MESSAGE_TEXT);
}

if (TypeMsgToSend_ui16 & MESSAGE_RESET_CONFIRMATION) {
    TypeMsgToSend_ui16 &= MESSAGE_RESET_CONFIRMATION;
    Encode_Command(RUT_COMMAND_TO_EXECUTE_SMS_SEND, PHONENUMBER, RESET_CONFIRMATION_TEXT);
}

// Continue for other bits...

You can also specify hints for the complier that inform it's branch prediction optimizations: https://www.geeksforgeeks.org/branch-prediction-macros-in-gcc/
Sometimes, doing extra work (like pre-sorting an array) can lead to faster execution because the complier can make better predictions in some scenarios if it knows the array is sorted.
The only way to know for sure? Try many approaches and actually test how they perform at scale.
reply
I agree that the if-else performs better than the switch inside the for loop.
The recommendation you suggested:
if (TypeMsgToSend_ui16 & MESSAGE_TEST_MESSAGE) {
    TypeMsgToSend_ui16 &= ~(MESSAGE_TEST_MESSAGE);
    Encode_Command(RUT_COMMAND_TO_EXECUTE_SMS_SEND, PHONENUMBER, TEST_MESSAGE_TEXT);
}

if (TypeMsgToSend_ui16 & MESSAGE_RESET_CONFIRMATION) {
    TypeMsgToSend_ui16 &= ~(MESSAGE_RESET_CONFIRMATION);
    Encode_Command(RUT_COMMAND_TO_EXECUTE_SMS_SEND, PHONENUMBER, RESET_CONFIRMATION_TEXT);
}

// Continue for other bits...
can't be used because the Encode_Command function could be executed multiple times depending on TypeMsgToSend_ui16. This shouldn't happen, as Encode_Command should only be called once each time this section of code is executed, which is why the if-else approach is needed.
reply
switch statement
Are these all mutually exclusive cases? or can multiple flags be set simultaneously?
reply
multiple flags can be set simultaneously.
typedef enum _MESSAGE_TYPE
{
    MESSAGE_VOID = (uint16_t)0x00,
    
    MESSAGE_NO_SIGNAL_1 = (uint16_t)0x01,
    MESSAGE_WITH_SIGNAL_1 = (uint16_t)0x02,
    MESSAGE_NO_SIGNAL_2 = (uint16_t)0x04,
    MESSAGE_WITH_SIGNAL_2 = (uint16_t)0x08,
    MESSAGE_NO_SIGNAL_3 = (uint16_t)0x10,
    MESSAGE_WITH_SIGNAL_3 = (uint16_t)0x20,
    MESSAGE_NO_SIGNAL_4 = (uint16_t)0x40,
    MESSAGE_WITH_SIGNAL_4 = (uint16_t)0x80,

    MESSAGE_RUT_RESET = (uint16_t)0x100,
    MESSAGE_TEST_MESSAGE = (uint16_t)0x200,
    MESSAGE_RESET_CONFIRMATION = (uint16_t)0x400,
            
    MESSAGE_RESERVED_1 = (uint16_t)0x800,
    MESSAGE_RESERVED_2 = (uint16_t)0x1000,
    MESSAGE_RESERVED_3 = (uint16_t)0x2000,
    MESSAGE_RESERVED_4 = (uint16_t)0x4000,
    MESSAGE_RESERVED_5 = (uint16_t)0x8000,
    
} MESSAGE_TYPE;
reply
I assume you already checked with ChatGPT etc?
reply
No, I prefer people's opinions! Hahaha
reply