假设我有一个在程序的多个部分中调用的函数。我们还要说,我对该函数有一个特殊的调用,该函数位于一个性能极其敏感的代码段中(例如,一个迭代数千万次并且每微秒计数的循环)。有没有办法可以强制编译器(在我的情况下为gcc
)内联该单个特定函数调用,而无需内联其他函数?
编辑:让我完全清楚:这个问题不是关于强制 gcc(或任何其他编译器)内联所有对函数的调用;相反,它是关于请求编译器将特定调用内联到函数。
答案 0 :(得分:13)
在C中(与C ++相反),没有标准的方法来建议应该内联函数。它只是特定于供应商的扩展。
但是你指定它,据我所知,编译器将始终尝试内联每个实例,因此只使用该函数一次:
原:
int MyFunc() { /* do stuff */ }
更改为:
inline int MyFunc_inlined() { /* do stuff */ }
int MyFunc() { return MyFunc_inlined(); }
现在,在您想要内联的位置,使用MyFunc_inlined()
注意:上面的“inline”关键字只是gcc用于强制内联的语法的占位符。如果要信任H2CO3的删除答案,那就是:
static inline __attribute__((always_inline)) int MyFunc_inlined() { /* do stuff */ }
答案 1 :(得分:9)
可以启用内联每个翻译单元(但不是每次调用)。虽然这不是问题的答案,而且是一个丑陋的伎俩,它conforms to C standard并且作为相关的东西可能很有趣。
诀窍是使用extern
定义,而不想内联,extern inline
需要内联。
示例:
$ cat func.h
int func();
$ cat func.c
int func() { return 10; }
$ cat func_inline.h
extern inline int func() { return 5; }
$ cat main.c
#include <stdio.h>
#ifdef USE_INLINE
# include "func_inline.h"
#else
# include "func.h"
#endif
int main() { printf("%d\n", func()); return 0; }
$ gcc main.c func.c && ./a.out
10 // non-inlined version
$ gcc main.c func.c -DUSE_INLINE && ./a.out
10 // non-inlined version
$ gcc main.c func.c -DUSE_INLINE -O2 && ./a.out
5 // inlined!
您还可以使用非标准属性(例如GCC中的__attribute__(always_inline))
)进行extern inline
定义,而不是依赖-O2
。
答案 2 :(得分:6)
强制在C中内联函数的传统方法是根本不使用函数,而是使用像宏这样的函数。此方法将始终内联函数,但是像宏这样的函数存在一些问题。例如:
#define ADD(x, y) ((x) + (y))
printf("%d\n", ADD(2, 2));
还有内联关键字,它已添加到C99标准中的C.值得注意的是,Microsoft的Visual C编译器不支持C99,因此您不能将 inline 与该(悲惨的)编译器一起使用。 内联只向编译器提示您希望内联函数 - 它不能保证它。
GCC有一个扩展,要求编译器内联函数。
inline __attribute__((always_inline)) int add(int x, int y) {
return x + y;
}
为了使这个更干净,您可能想要使用宏:
#define ALWAYS_INLINE inline __attribute__((always_inline))
ALWAYS_INLINE int add(int x, int y) {
return x + y;
}
我不知道在某些调用中强制内联函数的直接方法。但是你可以结合这样的技术:
#define ALWAYS_INLINE inline __attribute__((always_inline))
#define ADD(x, y) ((x) + (y))
ALWAYS_INLINE int always_inline_add(int x, int y) {
return ADD(x, y);
}
int normal_add(int x, int y) {
return ADD(x, y);
}
或者,你可以这样:
#define ADD(x, y) ((x) + (y))
int add(int x, int y) {
return ADD(x, y);
}
int main() {
printf("%d\n", ADD(2,2)); // always inline
printf("%d\n", add(2,2)); // normal function call
return 0;
}
另请注意,强制函数内联可能无法使代码更快。内联函数会导致生成更大的代码,这可能会导致更多的缓存未命中。 我希望有所帮助。
答案 3 :(得分:4)
答案取决于您的功能,您的要求以及功能的性质。你最好的选择是:
编译器提示
这里的答案仅涵盖内联的一面,语言提示编译器。当标准说:
使函数成为内联函数表明对函数的调用为as 尽可能快。这些建议有效的程度如下 实现定义
其他更强的提示可能就是这种情况,例如:
__attribute__((always_inline))
:通常,除非指定了优化,否则不会内联函数。对于内联声明的函数,即使未指定优化级别,此属性也会内联函数。__forceinline
:__ forceinline关键字会覆盖成本/收益分析,而是依赖于程序员的判断。使用__forceinline时要小心。不加选择地使用__forceinline可能会导致更大的代码,只有轻微的性能提升,或者在某些情况下甚至会导致性能损失(例如,由于增加了对更大的可执行文件的分页)。即使这两个都依赖于内联是可能的,而且关键在于编译器标志。要使用内联函数,还需要了解编译器的优化设置。
值得一提的是,内联也可用于为您所在的编译单元提供现有功能的替换。当大概的答案对您的算法足够好时,可以使用此内联,或者可以在使用本地数据结构的更快方法。
内联定义 提供了外部定义的替代方案,翻译人员可以使用该定义来实现 在同一翻译单元中对该功能的任何调用。没有具体说明是否打电话给 function使用内联定义或外部定义。
某些功能无法内联
例如,对于GNU编译器,无法内联的函数是:
请注意,函数定义中的某些用法可能使其不适合内联替换。这些用法包括:可变函数,alloca的使用,可变长度数据类型的使用(请参阅可变长度),使用计算goto(请参阅标签作为值),使用非本地goto和嵌套函数(请参阅嵌套函数)。当一个标记为内联的函数无法替换时,使用-Winline会发出警告,并说明失败的原因。
所以即使always_inline
也可能达不到您的期望。
编译器选项
使用C99的内联提示将依赖于您指示编译器您正在寻找的内联行为。
例如,GCC有: -fno-inline
,-finline-small-functions
,-findirect-inlining
,-finline-functions
,-finline-functions-called-once
,-fearly-inlining
,-finline-limit=n
Microsoft编译器还有一些选项可以指示内联的有效性。某些编译器还允许优化以考虑运行配置文件。
我认为值得在更广泛的程序优化环境中进行内联。
防止内联
您提到您不希望内联某些功能。这可以通过在不打开优化器的情况下设置__attribute__((always_inline))
之类的内容来完成。但是你可能会想要优化器。这里的一个选择是提示你不要它:__attribute__ ((noinline))
。但为什么会这样呢?
其他形式的优化
您也可以考虑如何重构循环并避免分支。分支预测可以产生戏剧性的效果。有关此问题的有趣讨论,请参阅:Why is it faster to process a sorted array than an unsorted array?
然后你也可以展开更小的内部循环并查看不变量。
答案 4 :(得分:3)
有一个内核源代码以非常有趣的方式使用#define
来定义几个不同的命名函数同一个主体。 这解决了具有两个不同功能来维护 的问题。 (我忘了它是哪一个......)。我的想法是基于同样的原则。
使用定义的方法是在需要的编译单元上定义内联函数。为了演示方法,我将使用一个简单的函数:
int add(int a, int b);
它的工作方式如下:在头文件中创建函数生成器#define
并声明函数正常版本的函数原型( not inlined )。
然后声明两个单独的函数生成器,一个用于正常函数,另一个用于内联函数。您声明为static __inline__
的内联函数。当您需要在其中一个文件中调用内联函数时,可以使用生成器定义来获取它的源代码。在所有其他文件中,您需要使用普通函数,只需在原型中包含标题。
代码在:
上进行了测试Intel(R) Core(TM) i5-3330 CPU @ 3.00GHz
Kernel Version: 3.16.0-49-generic
GCC 4.8.4
代码价值超过千言万语,所以:
+
| Makefile
| add.h
| add.c
| loop.c
| loop2.c
| loop3.c
| loops.h
| main.c
#define GENERATE_ADD(type, prefix) \
type int prefix##add(int a, int b) { return a + b; }
#define DEFINE_ADD() GENERATE_ADD(,)
#define DEFINE_INLINE_ADD() GENERATE_ADD(static __inline__, inline_)
int add(int, int);
这看起来不太好,但却削减了维护两种不同功能的工作。该函数在GENERATE_ADD(type,prefix)
宏中完全定义,因此如果您需要更改该函数,则更改此宏并更改其他所有内容。
接下来,DEFINE_ADD()
将调用add.c
来生成add
的正常版本。 DEFINE_INLINE_ADD()
将允许您访问名为inline_add
的函数,该函数与正常的add
函数具有相同的签名,但它具有不同的名称(< strong> inline _ 前缀)。
注意:使用__attribute((always_inline))__
标记时我没有使用-O3
- __inline__
完成了这项工作。但是,如果您不想使用-O3
,请使用:
#define DEFINE_INLINE_ADD() GENERATE_ADD(static __inline__ __attribute__((always_inline)), inline_)
#include "add.h"
DEFINE_ADD()
简单调用DEFINE_ADD()
宏生成器。这将声明函数的普通版本(不会内联的版本)。
#include <stdio.h>
#include "add.h"
DEFINE_INLINE_ADD()
int loop(void)
{
register int i;
for (i = 0; i < 100000; i++)
printf("%d\n", inline_add(i + 1, i + 2));
return 0;
}
在loop.c
中,您可以看到对DEFINE_INLINE_ADD()
的调用。这使该函数可以访问inline_add
函数。编译时,所有inline_add
函数都将内联。
#include <stdio.h>
#include "add.h"
int loop2(void)
{
register int i;
for (i = 0; i < 100000; i++)
printf("%d\n", add(i + 1, i + 2));
return 0;
}
这表明您可以正常使用其他文件中的add
正常版本。
#include <stdio.h>
#include "add.h"
DEFINE_INLINE_ADD()
int loop3(void)
{
register int i;
printf ("add: %d\n", add(2,3));
printf ("add: %d\n", add(4,5));
for (i = 0; i < 100000; i++)
printf("%d\n", inline_add(i + 1, i + 2));
return 0;
}
这是为了表明你可以使用两个 相同的编译单元中的函数,但其中一个函数将被内联,而另一个函数将不会被内联(参见< strong> GDB disass 以了解详情。
/* prototypes for main */
int loop (void);
int loop2 (void);
int loop3 (void);
#include <stdio.h>
#include <stdlib.h>
#include "add.h"
#include "loops.h"
int main(void)
{
printf("%d\n", add(1,2));
printf("%d\n", add(2,3));
loop();
loop2();
loop3();
return 0;
}
CC=gcc
CFLAGS=-Wall -pedantic --std=c11
main: add.o loop.o loop2.o loop3.o main.o
${CC} -o $@ $^ ${CFLAGS}
add.o: add.c
${CC} -c $^ ${CFLAGS}
loop.o: loop.c
${CC} -c $^ -O3 ${CFLAGS}
loop2.o: loop2.c
${CC} -c $^ ${CFLAGS}
loop3.o: loop3.c
${CC} -c $^ -O3 ${CFLAGS}
如果您使用__attribute__((always_inline))
,则可以将Makefile
更改为:
CC=gcc
CFLAGS=-Wall -pedantic --std=c11
main: add.o loop.o loop2.o loop3.o main.o
${CC} -o $@ $^ ${CFLAGS}
%.o: %.c
${CC} -c $^ ${CFLAGS}
$ make
gcc -c add.c -Wall -pedantic --std=c11
gcc -c loop.c -O3 -Wall -pedantic --std=c11
gcc -c loop2.c -Wall -pedantic --std=c11
gcc -c loop3.c -O3 -Wall -pedantic --std=c11
gcc -Wall -pedantic --std=c11 -c -o main.o main.c
gcc -o main add.o loop.o loop2.o loop3.o main.o -Wall -pedantic --std=c11
$ gdb main
(gdb) disass add
0x000000000040059d <+0>: push %rbp
0x000000000040059e <+1>: mov %rsp,%rbp
0x00000000004005a1 <+4>: mov %edi,-0x4(%rbp)
0x00000000004005a4 <+7>: mov %esi,-0x8(%rbp)
0x00000000004005a7 <+10>:mov -0x8(%rbp),%eax
0x00000000004005aa <+13>:mov -0x4(%rbp),%edx
0x00000000004005ad <+16>:add %edx,%eax
0x00000000004005af <+18>:pop %rbp
0x00000000004005b0 <+19>:retq
(gdb) disass loop
0x00000000004005c0 <+0>: push %rbx
0x00000000004005c1 <+1>: mov $0x3,%ebx
0x00000000004005c6 <+6>: nopw %cs:0x0(%rax,%rax,1)
0x00000000004005d0 <+16>:mov %ebx,%edx
0x00000000004005d2 <+18>:xor %eax,%eax
0x00000000004005d4 <+20>:mov $0x40079d,%esi
0x00000000004005d9 <+25>:mov $0x1,%edi
0x00000000004005de <+30>:add $0x2,%ebx
0x00000000004005e1 <+33>:callq 0x4004a0 <__printf_chk@plt>
0x00000000004005e6 <+38>:cmp $0x30d43,%ebx
0x00000000004005ec <+44>:jne 0x4005d0 <loop+16>
0x00000000004005ee <+46>:xor %eax,%eax
0x00000000004005f0 <+48>:pop %rbx
0x00000000004005f1 <+49>:retq
(gdb) disass loop2
0x00000000004005f2 <+0>: push %rbp
0x00000000004005f3 <+1>: mov %rsp,%rbp
0x00000000004005f6 <+4>: push %rbx
0x00000000004005f7 <+5>: sub $0x8,%rsp
0x00000000004005fb <+9>: mov $0x0,%ebx
0x0000000000400600 <+14>:jmp 0x400625 <loop2+51>
0x0000000000400602 <+16>:lea 0x2(%rbx),%edx
0x0000000000400605 <+19>:lea 0x1(%rbx),%eax
0x0000000000400608 <+22>:mov %edx,%esi
0x000000000040060a <+24>:mov %eax,%edi
0x000000000040060c <+26>:callq 0x40059d <add>
0x0000000000400611 <+31>:mov %eax,%esi
0x0000000000400613 <+33>:mov $0x400794,%edi
0x0000000000400618 <+38>:mov $0x0,%eax
0x000000000040061d <+43>:callq 0x400470 <printf@plt>
0x0000000000400622 <+48>:add $0x1,%ebx
0x0000000000400625 <+51>:cmp $0x1869f,%ebx
0x000000000040062b <+57>:jle 0x400602 <loop2+16>
0x000000000040062d <+59>:mov $0x0,%eax
0x0000000000400632 <+64>:add $0x8,%rsp
0x0000000000400636 <+68>:pop %rbx
0x0000000000400637 <+69>:pop %rbp
0x0000000000400638 <+70>:retq
(gdb) disass loop3
0x0000000000400640 <+0>: push %rbx
0x0000000000400641 <+1>: mov $0x3,%esi
0x0000000000400646 <+6>: mov $0x2,%edi
0x000000000040064b <+11>:mov $0x3,%ebx
0x0000000000400650 <+16>:callq 0x40059d <add>
0x0000000000400655 <+21>:mov $0x400798,%esi
0x000000000040065a <+26>:mov %eax,%edx
0x000000000040065c <+28>:mov $0x1,%edi
0x0000000000400661 <+33>:xor %eax,%eax
0x0000000000400663 <+35>:callq 0x4004a0 <__printf_chk@plt>
0x0000000000400668 <+40>:mov $0x5,%esi
0x000000000040066d <+45>:mov $0x4,%edi
0x0000000000400672 <+50>:callq 0x40059d <add>
0x0000000000400677 <+55>:mov $0x400798,%esi
0x000000000040067c <+60>:mov %eax,%edx
0x000000000040067e <+62>:mov $0x1,%edi
0x0000000000400683 <+67>:xor %eax,%eax
0x0000000000400685 <+69>:callq 0x4004a0 <__printf_chk@plt>
0x000000000040068a <+74>:nopw 0x0(%rax,%rax,1)
0x0000000000400690 <+80>:mov %ebx,%edx
0x0000000000400692 <+82>:xor %eax,%eax
0x0000000000400694 <+84>:mov $0x40079d,%esi
0x0000000000400699 <+89>:mov $0x1,%edi
0x000000000040069e <+94>:add $0x2,%ebx
0x00000000004006a1 <+97>:callq 0x4004a0 <__printf_chk@plt>
0x00000000004006a6 <+102>:cmp $0x30d43,%ebx
0x00000000004006ac <+108>:jne 0x400690 <loop3+80>
0x00000000004006ae <+110>:xor %eax,%eax
0x00000000004006b0 <+112>:pop %rbx
0x00000000004006b1 <+113>:retq
$ objdump -t main | grep add
0000000000000000 l df *ABS* 0000000000000000 add.c
000000000040059d g F .text 0000000000000014 add
$ objdump -t main | grep loop
0000000000000000 l df *ABS* 0000000000000000 loop.c
0000000000000000 l df *ABS* 0000000000000000 loop2.c
0000000000000000 l df *ABS* 0000000000000000 loop3.c
00000000004005c0 g F .text 0000000000000032 loop
00000000004005f2 g F .text 0000000000000047 loop2
0000000000400640 g F .text 0000000000000072 loop3
$ objdump -t main | grep main
main: file format elf64-x86-64
0000000000000000 l df *ABS* 0000000000000000 main.c
0000000000000000 F *UND* 0000000000000000 __libc_start_main@@GLIBC_2.2.5
00000000004006b2 g F .text 000000000000005a main
$ objdump -t main | grep inline
$
嗯,就是这样。经过3个小时的敲击键盘试图找出它,这是我能想到的最好的。随意指出任何错误,我真的很感激。我对这个特别的内联一个函数调用非常感兴趣。
答案 5 :(得分:2)
如果您不介意为同一个函数设置两个名称,则可以在函数周围创建一个小包装器,以“阻止”always_inline属性影响每个调用。在我的示例中,loop_inlined
将是您在性能关键部分中使用的名称,而普通loop
将在其他任何地方使用。
#include <stdlib.h>
static inline int loop_inlined() __attribute__((always_inline));
int loop();
static inline int loop_inlined() {
int n = 0, i;
for(i = 0; i < 10000; i++)
n += rand();
return n;
}
#include "inline.h"
int loop() {
return loop_inlined();
}
#include "inline.h"
#include <stdio.h>
int main(int argc, char *argv[]) {
printf("%d\n", loop_inlined());
printf("%d\n", loop());
return 0;
}
无论优化级别如何,这都有效。在英特尔上使用gcc inline.c main.c
进行编译会给出:
4011e6: c7 44 24 18 00 00 00 movl $0x0,0x18(%esp)
4011ed: 00
4011ee: eb 0e jmp 4011fe <_main+0x2e>
4011f0: e8 5b 00 00 00 call 401250 <_rand>
4011f5: 01 44 24 1c add %eax,0x1c(%esp)
4011f9: 83 44 24 18 01 addl $0x1,0x18(%esp)
4011fe: 81 7c 24 18 0f 27 00 cmpl $0x270f,0x18(%esp)
401205: 00
401206: 7e e8 jle 4011f0 <_main+0x20>
401208: 8b 44 24 1c mov 0x1c(%esp),%eax
40120c: 89 44 24 04 mov %eax,0x4(%esp)
401210: c7 04 24 60 30 40 00 movl $0x403060,(%esp)
401217: e8 2c 00 00 00 call 401248 <_printf>
40121c: e8 7f ff ff ff call 4011a0 <_loop>
401221: 89 44 24 04 mov %eax,0x4(%esp)
401225: c7 04 24 60 30 40 00 movl $0x403060,(%esp)
40122c: e8 17 00 00 00 call 401248 <_printf>
前7条指令是内联呼叫,常规呼叫稍后发出5条指令。
答案 6 :(得分:1)
这是一个建议,在单独的头文件中写下代码的主体。 将头文件包含在必须内联的位置,并包含在C文件中用于其他调用的正文中。
void demo(void)
{
#include myBody.h
}
importantloop
{
// code
#include myBody.h
// code
}
答案 7 :(得分:-2)
我认为你的功能很少,因为你想内联它,如果是这样的话为什么不用asm写呢?
至于仅内联函数的特定调用,我认为没有什么可以为您执行此任务。一旦函数声明为内联函数,并且编译器将为您内联它,它将在看到对该函数的调用的任何地方执行它。