宏 运行 比 C 中的全局变量快吗?如何在 运行 之间更改宏?
Do macros run faster than global variables in C? How to change macros between runs?
我正在用 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 时都重新编译。我使用了一个简单的解决方案,将宏更改为全局变量,将原来的主函数放入一个新的 'program()' 函数中,然后 运行 主文件中的实验,即
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++ 中,我将通过定义 'class program'、将 CONSTANT 设置为 class 成员和 'multiplication_by_constant' 成员函数来实现。然后我可以使用 class 定义通过 'main' 轻松地 运行 实验,我想我不会有效率损失......我必须在这个实现中使用 C为什么我求助于全局变量和函数。
宏不 "run",确切地说。宏是 C 预处理器的指令,它在将宏的定义传递给编译器之前有效地用宏的定义替换了宏的使用。给定的宏或函数调用是否更有效取决于宏和函数调用。在您的示例中,正如 John Bode 在他的回答中所展示的那样,让宏表示文字比使用变量更有优势。一般来说,表达 相同的代码 内联与作为宏没有内在的区别。编码效率更高的内联语句将比编码效率较低的宏更有效。
看来您不太了解预处理器的作用。将宏视为搜索和替换模板,即您的小程序
#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));
}
在这种情况下,在乘以输入值之前没有要读取的字段,这会导致加速。
仅尝试 运行 预处理器并比较您的两个变体的输出,如果您真的 运行 只是这些小程序(并且以上内容不是您实际问题的提炼版本),您也可以尝试比较汇编器输出。如果不出意外,您将更好地了解您正在使用的任何工具链:-)
C 程序是分步构建的。这些步骤如下:
- 预处理:(宏、包含、定义等...)基本上所有以# 符号开头的内容都在这里处理。
- 编译:将预处理后的 C 源代码翻译(编译)为汇编程序。
- 汇编:汇编程序将汇编源文件(通过编译生成)翻译成机器目标代码文件。
- 链接:最后一步解析对库的外部引用并生成 运行可用程序。
有了这些知识,宏就在预处理阶段被处理了。在您的情况下,源代码中出现的所有 CONSTANT 都被 10 替换。因此,要更改常量,您必须更改 #define 语句并重新编译。由于 CONSTANT 被替换为实际数字 10,因此它被视为立即值。但是如果你想在 运行 时间内改变它,你必须使用一个变量,因为没有其他方法可以绕过它。
但是,您可以做的一件事是更改此行:
printf("%d\n",multiplication_by_constant(i));
为此:
printf("%d\n",i * CONSTANT);
这样您就可以完全避免函数调用并消除与进行该函数调用相关的开销。
好吧,这是性能差异的潜在 来源。使用宏,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<em><strong>
14 000c C1E002 sall , %eax
15 000f 01D0 addl %edx, %eax
16 0011 01C0 addl %eax, %eax</strong></em>
17 0013 C9 leave
18 0014 C3 ret
突出显示的行是函数的核心;该函数不是将 %eax
中的值乘以 10,而是先算术左移 2 位,然后进行加法运算(有效地乘以 5),然后将结果与自身相加。例如,给定 i
值为 3
:
3 << 2 == 12
3 + 12 == 15
15 + 15 == 30
如果您更改 CONSTANT
的值,编译器将生成不同的机器代码(7
的 CONSTANT
值导致算术左移 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<strong><em>
20 000d 0FAF45FC imull -4(%rbp), %eax</em></strong>
21 0011 C9 leave
22 0012 C3 ret
此版本使用 imull
操作码进行乘法运算。由于 CONSTANT
的值在编译时未知,因此编译器无法根据该值进行任何特殊优化。
现在,这就是 我的特定平台 上的情况;我不知道您使用的是什么编译器,也不知道您使用的 OS 是什么 运行,所以您得到的机器代码很可能与我上面的不同。我只是指出在编译时知道 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
我正在用 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 时都重新编译。我使用了一个简单的解决方案,将宏更改为全局变量,将原来的主函数放入一个新的 'program()' 函数中,然后 运行 主文件中的实验,即
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++ 中,我将通过定义 'class program'、将 CONSTANT 设置为 class 成员和 'multiplication_by_constant' 成员函数来实现。然后我可以使用 class 定义通过 'main' 轻松地 运行 实验,我想我不会有效率损失......我必须在这个实现中使用 C为什么我求助于全局变量和函数。
宏不 "run",确切地说。宏是 C 预处理器的指令,它在将宏的定义传递给编译器之前有效地用宏的定义替换了宏的使用。给定的宏或函数调用是否更有效取决于宏和函数调用。在您的示例中,正如 John Bode 在他的回答中所展示的那样,让宏表示文字比使用变量更有优势。一般来说,表达 相同的代码 内联与作为宏没有内在的区别。编码效率更高的内联语句将比编码效率较低的宏更有效。
看来您不太了解预处理器的作用。将宏视为搜索和替换模板,即您的小程序
#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));
}
在这种情况下,在乘以输入值之前没有要读取的字段,这会导致加速。
仅尝试 运行 预处理器并比较您的两个变体的输出,如果您真的 运行 只是这些小程序(并且以上内容不是您实际问题的提炼版本),您也可以尝试比较汇编器输出。如果不出意外,您将更好地了解您正在使用的任何工具链:-)
C 程序是分步构建的。这些步骤如下:
- 预处理:(宏、包含、定义等...)基本上所有以# 符号开头的内容都在这里处理。
- 编译:将预处理后的 C 源代码翻译(编译)为汇编程序。
- 汇编:汇编程序将汇编源文件(通过编译生成)翻译成机器目标代码文件。
- 链接:最后一步解析对库的外部引用并生成 运行可用程序。
有了这些知识,宏就在预处理阶段被处理了。在您的情况下,源代码中出现的所有 CONSTANT 都被 10 替换。因此,要更改常量,您必须更改 #define 语句并重新编译。由于 CONSTANT 被替换为实际数字 10,因此它被视为立即值。但是如果你想在 运行 时间内改变它,你必须使用一个变量,因为没有其他方法可以绕过它。
但是,您可以做的一件事是更改此行:
printf("%d\n",multiplication_by_constant(i));
为此:
printf("%d\n",i * CONSTANT);
这样您就可以完全避免函数调用并消除与进行该函数调用相关的开销。
好吧,这是性能差异的潜在 来源。使用宏,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<em><strong>
14 000c C1E002 sall , %eax
15 000f 01D0 addl %edx, %eax
16 0011 01C0 addl %eax, %eax</strong></em>
17 0013 C9 leave
18 0014 C3 ret
突出显示的行是函数的核心;该函数不是将 %eax
中的值乘以 10,而是先算术左移 2 位,然后进行加法运算(有效地乘以 5),然后将结果与自身相加。例如,给定 i
值为 3
:
3 << 2 == 12
3 + 12 == 15
15 + 15 == 30
如果您更改 CONSTANT
的值,编译器将生成不同的机器代码(7
的 CONSTANT
值导致算术左移 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<strong><em>
20 000d 0FAF45FC imull -4(%rbp), %eax</em></strong>
21 0011 C9 leave
22 0012 C3 ret
此版本使用 imull
操作码进行乘法运算。由于 CONSTANT
的值在编译时未知,因此编译器无法根据该值进行任何特殊优化。
现在,这就是 我的特定平台 上的情况;我不知道您使用的是什么编译器,也不知道您使用的 OS 是什么 运行,所以您得到的机器代码很可能与我上面的不同。我只是指出在编译时知道 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