【问题标题】:Do macros run faster than global variables in C? How to change macros between runs?宏在 C 中是否比全局变量运行得更快?如何在运行之间更改宏?
【发布时间】:2015-05-22 18:11:24
【问题描述】:

我正在用 C 语言编写一个程序,其中有几个常量我希望我的所有函数都使用。到目前为止,我已经使用了宏。该程序的简化版本如下所示。

#define CONSTANT 10 //int
int multiplication_by_constant(int a){ return a*CONSTANT;}
int main(){
   for(int i = 1; i< 10; i++)
       printf("%d\n",multiplication_by_constant(i));
}

现在我想通过使用不同的常量值多次运行程序来对程序进行实验。我想自动执行此操作,而不是每次更改 CONSTANT 时都重新编译。我使用了一个简单的解决方案,将宏更改为全局变量,将原始主函数放入新的“程序()”函数中,然后在主文件中运行实验,即

int CONSTANT =  10; //int
int multiplication_by_constant(int a){ return a*CONSTANT;}
void program(){
   for(int i = 1; i< 10; i++)
       printf("%d\n",multiplication_by_constant(i));
}
int main(){
   while(CONSTANT < 100){
       program();
   }
   return 0;
}

我发现这种实现会导致循环性能的相当大的损失

for(int i = 1; i< 10; i++)
       printf("%d\n",multiplication_by_constant(i));

现在需要比我使用宏时多 50% 的运行时间。这是正常的吗?

谁能为我推荐一种更有效的方法来运行我的实验?

PS 在 C++ 中,我会通过定义一个“类程序”、将 CONSTANT 设置为类成员并将“multiplication_by_constant”设置为成员函数来做到这一点。然后我可以使用类定义轻松地通过'main'运行实验,我认为我不会有效率损失......我必须在这个实现中使用C,这就是我求助于全局变量和功能。

【问题讨论】:

  • 没有多大意义。宏在编译时进行评估。一旦你有一个二进制可执行文件并正在执行它,就没有“宏”。
  • 您是如何收集统计数据的?您使用什么分析技术?
  • @JohnBode 我在“for”循环之前和之后使用“gettimeofday”来计算经过的时间
  • @Plamen:你平均跑了多少次?

标签: c macros


【解决方案1】:

嗯,这是性能差异的潜在来源。对于宏,CONSTANT 的值在编译时是已知的,因此编译器可以利用该知识并稍微不同地构造机器代码。我采用了您的代码的变体,并使用gcc -Wa,-aldh 获得了宏和全局变量版本的程序集列表1,其中有一个有趣的区别。

首先是宏版本:


 3                    .globl mul_by_const
 5                    mul_by_const:
 6                    .LFB2:
 7 0000 55                    pushq   %rbp
 8                    .LCFI0:
 9 0001 4889E5                movq    %rsp, %rbp
10                    .LCFI1:
11 0004 897DFC                movl    %edi, -4(%rbp)
12 0007 8B55FC                movl    -4(%rbp), %edx
13 000a 89D0                  movl    %edx, %eax
14 000c C1E002                sall    $2, %eax
15 000f 01D0                  addl    %edx, %eax
16 0011 01C0                  addl    %eax, %eax
17 0013 C9                    leave
18 0014 C3                    ret

突出显示的行是函数的核心;该函数不是将 %eax 中的值乘以 10,而是将算术左移 2 位,然后进行加法(实际上是乘以 5),然后将结果添加到自身。例如,给定i 值为3

 3 << 2 == 12
 3 + 12 == 15
15 + 15 == 30

如果您更改 CONSTANT 的值,编译器将生成不同的机器代码(CONSTANT 的值 7 会导致算术左移 3,然后是减法,而 19 的值给出 3 的左移,然后是 3 次加法等)。

与全局变量版本比较:


 2                    .globl CONSTANT
 3                            .data
 4                            .align 4
 7                    CONSTANT:
 8 0000 0A000000              .long   10
 9                            .text
10                    .globl mul_by_const
12                    mul_by_const:
13                    .LFB2:
14 0000 55                    pushq   %rbp
15                    .LCFI0:
16 0001 4889E5                movq    %rsp, %rbp
17                    .LCFI1:
18 0004 897DFC                movl    %edi, -4(%rbp)
19 0007 8B050000              movl    CONSTANT(%rip), %eax
19      0000
20 000d 0FAF45FC              imull   -4(%rbp), %eax
21 0011 C9                    leave
22 0012 C3                    ret

此版本使用imull 操作码进行乘法运算。由于CONSTANT 的值在编译时未知,因此编译器无法根据该值进行任何特殊优化。

现在,这就是我的特定平台上发生的事情;我不知道您正在使用什么编译器或您正在运行什么操作系统,因此您获得的机器代码可能与我上面的不同。我只是指出,在编译时知道CONSTANT 允许编译器进行一些额外的优化。

编辑

如果您更改宏的值,您必须重新编译。您可以将宏放在头文件中,然后编写一个脚本来重新生成头文件并重新编译,例如

#!/bin/bash

let CONSTANT=1
while [ $CONSTANT -lt 20 ]
do
  cat > const.h << EOF
#ifndef CONST_H
#define CONST_H
#define CONSTANT $CONSTANT
#endif
EOF
  # build and run your test code, assuming profiling is captured
  # automatically
  gcc -o test test.c
  ./test
  let CONSTANT=CONSTANT+1
done

您的 C 代码将 #include const.h 文件:

#include "const.h"
int multiplication_by_constant(int a){ return a*CONSTANT;}
...


1。平台是 SUSE Linux Enterprise Server 10 (x86_64),编译器是 gcc 4.1.2

【讨论】:

    【解决方案2】:

    宏不会“运行”,确切地说。宏是 C 预处理器的指令,它在将宏传递给编译器之前有效地将宏的使用替换为宏的定义。给定的宏或函数调用是否更有效取决于宏和函数调用。在您的示例中,正如 John Bode 在他的回答中所显示的那样,让宏表示文字比使用变量更有优势。一般来说,将相同的代码内联与宏表达之间没有本质的区别。与编码效率较低的宏相比,编码效率更高的内联语句效率更高。

    【讨论】:

    • 您的回答并不完全正确。最后那句话是错误的。宏在预处理期间被替换。编译器只看到函数调用,而不是宏,因此在运行时由被替换的宏定义的函数调用和裸函数调用之间没有区别。
    • “如果宏是效率较低的函数调用,”例如调用效率较低的函数或以较低效率的方式调用相同的函数,那么它的效率将低于裸函数调用。我坚持我的逻辑,即使不是我的清晰。
    【解决方案3】:

    您似乎不太了解预处理器的作用。将宏视为搜索和替换模板,即您的小程序

    #define CONSTANT 10 //int
    int multiplication_by_constant(int a){ return a*CONSTANT; }
    int main(){
       for(int i = 1; i< 10; i++)
           printf("%d\n",multiplication_by_constant(i));
    }
    

    相当于写

    int multiplication_by_constant(int a){ return a*10; }
    int main(){
       for(int i = 1; i< 10; i++)
           printf("%d\n",multiplication_by_constant(i));
    }
    

    在这种情况下,在乘以输入值之前没有要读取的字段,这将解释加速。

    尝试仅运行预处理器并比较您的两个变体的输出,如果您真的只运行这些小程序(以上不是您实际问题的提炼版本),您可以尝试将汇编器输出比较为好。如果不出意外,您将更好地了解您正在使用的任何工具链:-)

    【讨论】:

    • cpp &lt;your_file.c&gt; 应该在预处理后输出文件。然后你可以看到编译后两个版本之间没有区别。
    • 我明白了,这已经很明显了。我想这意味着没有希望在一个 C 程序中自动化实验而不损失效率。有没有办法自动化宏定义?例如。我可以编写一个脚本来编译带有 CONSTANT 的新定义的 C 吗?
    • 没错!我所知道的所有编译器都接受宏定义作为命令行参数,请注意它们不能覆盖源代码中指定的任何内容。
    【解决方案4】:

    C 程序是逐步构建的。这些步骤如下:

    1. 预处理:(宏、包含、定义等)基本上所有以# 符号开头的内容都会在这里处理。
    2. 编译:将预处理的 C 源代码翻译(编译)为汇编程序。
    3. 汇编:汇编器将汇编源文件(通过编译生成)转换为机器目标代码文件。
    4. 链接:最后一步解析对库的外部引用并生成可运行的程序。

    有了这些知识,宏就会在预处理阶段进行处理。在您的情况下,源代码中出现的所有 CONSTANT 都被 10 替换。因此,要更改常量,您必须更改 #define 语句并重新编译。由于 CONSTANT 被实际数字 10 替换,因此它被视为立即值。但是如果你想在运行时改变它,你必须使用一个变量,因为没有其他方法可以绕过它。

    但是,您可以做的一件事是更改此行:

     printf("%d\n",multiplication_by_constant(i));
    

    到这里:

     printf("%d\n",i * CONSTANT);
    

    这样您就可以完全避免函数调用,并消除与进行该函数调用相关的开销。

    【讨论】:

      猜你喜欢
      • 2011-11-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多