(如何)我可以内联特定的函数调用吗?

时间:2013-01-28 21:29:48

标签: c function inline

假设我有一个在程序的多个部分中调用的函数。我们还要说,我对该函数有一个特殊的调用,该函数位于一个性能极其敏感的代码段中(例如,一个迭代数千万次并且每微秒计数的循环)。有没有办法可以强制编译器(在我的情况下为gcc)内联该单个特定函数调用,而无需内联其他函数?

编辑:让我完全清楚:这个问题不是关于强制 gcc(或任何其他编译器)内联所有对函数的调用;相反,它是关于请求编译器将特定调用内联到函数。

8 个答案:

答案 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

顺便说一句,技巧是used in glibc

答案 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)

答案取决于您的功能,您的要求以及功能的性质。你最好的选择是:

  • 告诉编译器你想要内联
  • 使函数变为静态(小心extern,因为在某些模式下它的语义在gcc中稍有变化)
  • 设置编译器选项以通知您想要内联的优化器,并相应地设置内联限制
  • 打开任何无法编译的内联警告
  • 验证输出(您可以检查生成的汇编程序)该函数是否已内联。

编译器提示

这里的答案仅涵盖内联的一面,语言提示编译器。当标准说:

  

使函数成为内联函数表明对函数的调用为as   尽可能快。这些建议有效的程度如下   实现定义

其他更强的提示可能就是这种情况,例如:

  • GNU的__attribute__((always_inline)):通常,除非指定了优化,否则不会内联函数。对于内联声明的函数,即使未指定优化级别,此属性也会内联函数。
  • Microsoft的__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

add.h

#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_)

add.c

#include "add.h"

DEFINE_ADD()

简单调用DEFINE_ADD()宏生成器。这将声明函数的普通版本(不会内联的版本)。

loop.c中

#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函数都将内联。

loop2.c

#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正常版本。

loop3.c

#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 以了解详情。

loops.h

/* prototypes for main */
int loop (void);
int loop2 (void);
int loop3 (void);

的main.c

#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将在其他任何地方使用。

inline.h

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

inline.c

#include "inline.h"

int loop() {
    return loop_inlined();
}

的main.c

#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写呢?

至于仅内联函数的特定调用,我认为没有什么可以为您执行此任务。一旦函数声明为内联函数,并且编译器将为您内联它,它将在看到对该函数的调用的任何地方执行它。