【问题标题】:Are macros for large switch statements faster than functions with large switch statements?大型 switch 语句的宏是否比具有大型 switch 语句的函数更快?
【发布时间】:2017-11-01 23:21:52
【问题描述】:

因此,对于内联函数(1-2 语句)和小宏,使用宏或内联函数之间似乎没有太大的性能差异。

但是,考虑到较大函数的函数调用开销,我想知道,

  • GCC 7.0
  • C 语言(非 C++)
  • OSX(不确定这是否会在跨平台上有很大差异)

将大型宏用于 switch 语句会比将它们放入等效的函数调用中更快吗?这是我的一部分,假设不会内联如此大的函数。这是我的示例代码。

#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;
     }  
}

那么在宏和函数这两者之间,使用宏而不是函数有什么优化吗?

【问题讨论】:

  • 你应该测量......
  • 是的。测量,然后转储宏。
  • 内联函数是要走的路。让编译器来思考。
  • @wildplasser 但大函数也可以内联吗?
  • 而且:您的两个实现都受到以下事实的困扰:他们无法向调用者报告他们消耗了多少字符:void Lex_Char(...)

标签: c macros


【解决方案1】:

首先,请注意,当宏中有代码时,编译器必须将它内联插入到调用代码中。当您将其设为函数时,编译器可能将其插入内联。

您还应该了解当您声明如下函数时会发生什么:

void Lex_Char(char* chPtr, Tag* tag) { ... }

这告诉编译器该函数可以从其他 C 文件访问 - 编译器必须制作该函数的完整版本。内联函数意味着制作两份代码副本——一份用于完整功能版本,另一份在调用站点内联。除非您的优化设置非常强调大小而不是速度,否则编译器将不愿意这样做。

如果一个函数只在当前翻译单元中使用,你应该将它标记为“静态”:

static void Lex_Char(char* chPtr, Tag* tag) { ... }

这告诉编译器它无法从外部访问。如果该函数只在当前模块中使用过一次,那么编译器可以愉快地内联它——这样做是“免费的”。

您还可以将函数标记为“静态内联”,给编译器一个提示,您希望它被内联。

当然,这一切都取决于为编译器启用优化 - 如果你不启用优化,你所有的时间测试都是毫无价值的。

内联静态函数总是比宏更好的选择(当您有选择时 - 宏可以比内联函数更灵活)。代码写起来更清晰,你有更好的静态警告和错误检查。结果代码(假设优化)将是相同的。

顺便说一句,您的时序测试在这里毫无意义 - 编译器将看到所涉及的值不会更改并且不会多次运行该函数,当它被内联并启用优化时。它可能根本不运行它,而是在编译时预先计算结果。

哦,您忘记了案例“!”中的“中断”。

【讨论】:

【解决方案2】:

结果证明,经过定时测试,并在相同的 for 循环下重复,宏版本的速度大约是常规函数的两倍。

这是我为生成结果而编译的完整计时器和完整文件

#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;
}

结果:

  • 函数:0.000067
  • 宏:0.000032

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-30
    • 2022-11-22
    • 1970-01-01
    • 2013-12-02
    • 2020-10-02
    相关资源
    最近更新 更多