Search code examples
coptimizationvalgrindcallgrind

Optimizations of nested if and switch statements using callgrind or assembly modifications


Consider this piece of code

| 34 static bool                                                                     
| 35 _valid_character(char *str, size_t *idx)                                        
| 36 {                                                                               
| 37   char c = str[*idx];                                                           
| 38                                                                                 
| 39   if (c != '\\' && c != '"') {                                                  
| 40     (*idx) += 1;                                                                
| 41     return true;                                                                
| 42   } else if (c == '"') {                                                        
| 43     return false;                                                               
| 44   } else {                                                                      
| 45     char b = str[(*idx) + 1];                                                   
| 46     switch (b) {                                                                
| 47       case '"':                                                                 
| 48       case '\\':                                                                
| 49       case '/':                                                                 
| 50       case 'b':                                                                 
| 51       case 'f':                                                                 
| 52       case 'n':                                                                 
| 53       case 'r':                                                                 
| 54       case 't':                                                                 
| 55         (*idx) += 2;                                                            
| 56         return true;                                                            
| 57       default:                                                                  
| 58         pprint_error("%s@%s:%d invalid escape sequnce \\%c%c (aborting)",       
| 59             __FILE_NAME__, __func__, __LINE__, c, b);                           
| 60         abort();                                                                
| 61     }                                                                           
| 62   }                                                                             
| 63 }  

This one function is the root cause of a meaning full slowdown in my code. I have tried only using if statements and only using switch statements but this is the best optimization that I can come up with where callgrind results in the best performance. This function accounts for about 25% of the runtime so by the questionable (sorry) 80/20 rule its in my best interest to make this code faster.

Below is the callgrind output visualized with kcachegrind on this function.

enter image description here

It seems callgrind is saying that my first jump is the worst jump but I have tried all combinations of this if statement to try to minimize jumping and every time the first jump is the worst jump.

This code was compiled with clang

clang ... -Weverything -Werror -Wpedantic -m64 -O0 -g

So my question is what is the best way to go about optimizing this code and alternative techniques including assembly modification to optimize this simple yet deadly piece of code.

I would like to keep using -O0 since I find it the most useful for debugging and finding optimizations. -O1,2,3,fast tend to abstract to much away to get a good understanding of what is happening.

-- Edit 1 It was asked for an example input.

char cstr[BUF] = "abcdefghijklmnopqrstuvwxyz\"randomgarbageafterthis";
size_t idx = 0;
while (_valid_character(cstr, &idx));

In the end the input is just a string and a loop is called until the end " character. The ending idx value holds cstr[idx] == '"'' to be true.


Solution

  • If you really want to optimize your code, use pen and paper and create a boolean logic table and optimize its algebra with your own mind. After that recreate your code and please use break; within your switch statements.

    For example:

    This is your code.

    if (c != '\\' && c != '"')
    {
        (*idx) += 1;
        return true;
    }
    

    This one is faster than the above one.

    if (c != '\\')
    {
        if (c != '"')
        {
            (*idx) += 1;
            return true;
        }
    }
    

    Instead of comparing to some chars like if (c != '\\') use registered CONSTANTS.

    register const char BACKSLASH = '\\';
    

    And also do:

    register char c = str[*idx];
    

    Constants and variables which are loaded in CPU register are way faster in comparing.

    if (c != BACKSLASH)
    

    Generally, if you want fast code, only use if and else and use simple boolean compare arguments.

    EDIT:

    Code sample 1:

    if (c != '\\')
    {
        if (c == '"')
        {
            return false;
        }
        else
        {
            (*idx) += 1;
            return true;
        }
    }
    

    is equivalent to:

    Code sample 2:

    if (c != '\\')
    {
        if (c != '"')
        {
            (*idx) += 1;
            return true;
        }
    }
    else if (c == '"')
    {
        return false;
    }
    

    But the first code example only compares 2 times, but the second code compares 3 times.