Reputation: 2968
So, for inline functions (1-2 statements) and small macros, it seems that there isn't too much of a performance difference between using a macro or an inline function.
However, given the function call overhead for a larger function, I am wondering for,
Would using large macros for a switch statement be faster than putting them in an equivalent function call? This is on my part assuming such large functions will not be inlined. Here is my example code.
#define LEX_CHAR(chPtr, tag) switch(*chPtr) { \
case 'a':\
case 'b':\
case 'c':\
case 'e':\
case '$': tag = Tag_A;\
break; \
case '0':\
case '1':\
case '2':\
case '3': tag = Tag_B;\
break;\
case 'r':\
if(chPtr[1] == 'd' || chPtr[1] == '@') tag = Tag_c;\
else tag = Tag_B;\
break;\
case '+':\
case '#':\
case '!':\
if(chPtr[1] == 'd') tag = Tag_C;\
case '-':\
case '^':\
tag = Tag_D;\
break;\
default:\
tag = Tag_B;\
}
enum Tag
{
Tag_A,
Tag_B,
Tag_C,
Tag_D
};
typedef enum Tag Tag;
void Lex_Char(char* chPtr, Tag* tag)
{
switch(*chPtr) {
case 'a':
case 'b':
case 'c':
case 'e':
case '$': *tag = Tag_A;
break;
case '0':
case '1':
case '2':
case '3': *tag = Tag_B;
break;
case 'r':
if(chPtr[1] == 'd' || chPtr[1] == '@') *tag = Tag_C;
else *tag = Tag_B;
break;
case '+':
case '#':
case '!':
if(chPtr[1] == 'd') *tag = Tag_C;
case '-':
case '^':
*tag = Tag_D;
break;
default:
*tag = Tag_B;
}
}
So between these two, the macro and the function, is there any optimization in using the macro over the function?
Upvotes: 1
Views: 171
Reputation: 162
First, note that when you have code in a macro, the compiler must insert it inline in the calling code. When you make it a function, the compiler may insert it inline.
You should also understand what happens when you declare a function like:
void Lex_Char(char* chPtr, Tag* tag) { ... }
This tells the compiler that the function is accessible from other C files - the compiler has to make a full version of this function. Inlining the function would then mean making two copies of the code - one for the full function version, and one inlined at the call site. The compiler will be reluctant to do this unless your optimisation settings put a strong emphasis on size over speed.
If a function is only to be used within the current translation unit, you should mark it "static":
static void Lex_Char(char* chPtr, Tag* tag) { ... }
This tells the compiler that it is inaccessible from outside. If the function is only ever used once within the current module, then the compiler can happily inline it - doing so is "free".
You can also mark the function as "static inline", giving the compiler a hint that you are keen on it being inlined.
Of course, this all depends on having optimisation enabled for the compiler - if you don't enable optimisation, all your time testing is worthless.
An inlined static function is always a better choice than a macro (when you have the choice - macros can be more flexible than inline functions). The code is clearer to write, and you have better static warning and error checking. The resulting code (assuming optimisation) will be the same.
Your timing tests, incidentally, are pointless here - the compiler will see that the values involved don't change and will not run the function more than once, when it is inlined and optimisation is enabled. It might not run it at all, but pre-calculate the result at compile time.
Oh, and you've forgotten a "break" in the case '!'.
Upvotes: 1
Reputation: 2968
So it turns out after a timed test, and repeated under an identical for loop, the macro version is about twice as fast as the regular function.
Here's is my complete timer and full file compiled to produce the result
#include "stdio.h"
#include "stdlib.h"
#include "time.h"
#define LEX_CHAR(chPtr, tag) switch(*chPtr) { \
case 'a':\
case 'b':\
case 'c':\
case 'e':\
case '$': tag = Tag_A;\
break; \
case '0':\
case '1':\
case '2':\
case '3': tag = Tag_B;\
break;\
case 'r':\
if(chPtr[1] == 'd' || chPtr[1] == '@') tag = Tag_C;\
else tag = Tag_B;\
break;\
case '+':\
case '#':\
case '!':\
if(chPtr[1] == 'd') tag = Tag_C;\
case '-':\
case '^':\
tag = Tag_D;\
break;\
default:\
tag = Tag_B;\
}
enum Tag
{
Tag_A,
Tag_B,
Tag_C,
Tag_D
};
typedef enum Tag Tag;
void Lex_Char(char* chPtr, Tag* tag)
{
switch(*chPtr) {
case 'a':
case 'b':
case 'c':
case 'e':
case '$': *tag = Tag_A;
break;
case '0':
case '1':
case '2':
case '3': *tag = Tag_B;
break;
case 'r':
if(chPtr[1] == 'd' || chPtr[1] == '@') *tag = Tag_C;
else *tag = Tag_B;
break;
case '+':
case '#':
case '!':
if(chPtr[1] == 'd') *tag = Tag_C;
case '-':
case '^':
*tag = Tag_D;
break;
default:
*tag = Tag_B;
}
}
int main(){
Tag tagPnt = Tag_D;
char* code = "#he";
clock_t start, end;
start = clock();
//for(size_t i = 0; i<10000;i++) Lex_Char(code, &tagPnt); Number of seconds: 0.000067
for(size_t i = 0; i<10000;i++) LEX_CHAR(code, tagPnt); // Number of seconds: 0.000032
end = clock();
printf( "Number of seconds: %f\n", (end-start)/(double)CLOCKS_PER_SEC );
printf("%d is tag\n", tagPnt);
return 0;
}
Result:
Upvotes: 0