据我了解, INLINE 可以加快代码执行速度,对吗?
我们可以从中获得多少速度?
答案 0 :(得分:4)
从here翻录:
是,不是。有时。也许。
没有简单的答案。内联函数可能会使代码变快,但可能会使代码变慢。它们可能使可执行文件变大,可能使可执行文件变小。它们可能会引起颠簸,它们可能会阻止颠簸。而且它们可能而且经常与速度完全无关。
内联函数可能会使其更快:如图above所示,过程集成可能会删除一堆不必要的指令,从而使事情运行得更快。
内联函数可能会使其变慢:内联过多可能导致代码膨胀,这可能会导致按需分页的虚拟内存系统“崩溃”。换句话说,如果可执行文件的大小太大,则系统可能会花费大部分时间花在磁盘上,以获取下一部分代码。
内联函数可能会使它变大:如上所述,这是代码膨胀的概念。例如,如果一个系统有100个内联函数,每个内联函数都扩展为100字节的可执行代码,并在100个地方被调用,则增加了1MB。那1MB会引起问题吗?谁知道,但是最后1MB可能会导致系统“崩溃”,并可能减慢速度。
内联函数可能会使其更小:与通过内联扩展函数的主体相比,编译器通常生成更多的代码来压入/弹出寄存器/参数。这种情况在很小的函数中就会发生,在大函数中也会发生,当优化器能够通过过程集成删除大量冗余代码时,也就是说,当优化器能够使大函数变小时。
内联函数可能会导致崩溃:内联可能会增加二进制可执行文件的大小,并可能导致崩溃。
内联函数可能会防止抖动:即使可执行文件大小增大,工作集大小(一次需要在内存中的页面数)也可能减小。当f()调用g()时,代码通常位于两个不同的页面上。当编译器按程序将g()的代码集成到f()时,代码通常位于同一页面上。
内联函数可能会增加高速缓存未命中的次数:内联可能导致内部循环跨越内存高速缓存的多行,并可能导致内存高速缓存崩溃。
内联函数可以减少高速缓存未命中的次数:内联通常可以提高二进制代码中引用的局部性,从而可以减少存储内部循环代码所需的高速缓存行数。这最终可能导致与CPU绑定的应用程序运行得更快。
内联函数可能与速度无关:大多数系统不受CPU限制。大多数系统是I / O绑定,数据库绑定或网络绑定的,这意味着系统总体性能的瓶颈是文件系统,数据库或网络。除非您的“ CPU计量表”固定为100%,否则内联功能可能不会使您的系统更快。 (即使在受CPU约束的系统中,内联仅在瓶颈本身内使用时才有帮助,并且瓶颈通常仅占代码的一小部分。)
没有简单的答案:您必须使用它才能看到最好的答案。不要满足诸如“从不使用内联函数”或“始终使用内联函数”或“仅当该函数少于N行代码时才使用内联函数”之类的简单答案。这些一刀切的规则可能很容易写下来,但它们会产生次优的结果。
版权(C)马歇尔·克莱恩(Marshall Cline)
答案 1 :(得分:2)
使用inline
使系统使用substitution model of evaluation,但是不能保证一直使用。如果使用此选项,则生成的代码将更长并且可能更快,但是如果某些优化处于活动状态,则替代模型并非总是会更快。
答案 2 :(得分:1)
我使用inline
函数说明符(特别是static inline
)的原因不是因为“速度”,而是因为
static
部分告诉编译器该功能仅在当前翻译单元(正在编译的当前文件和包含的头文件)中可见
inline
部分告诉编译器,如果需要的话,它可以在调用站点包括函数的实现。
static inline
告诉编译器,如果当前翻译单元中根本不使用该功能,则可以完全跳过该功能。
(特别是,我使用最多的编译器gcc -Wall
会在标记为static
的函数未使用时发出警告;但是如果函数未使用,则不会发出警告标记为static inline
的未使用。)
static inline
告诉我们,该函数是类似于宏的辅助函数,此外还向与宏相同的行为添加了类型检查器。
因此,我认为inline
本身与速度有关的假设是错误的。用直接的答案回答所陈述的问题会产生误导。
在我的代码中,您看到它们与某些数据结构或偶尔与全局变量相关联。
一个典型的例子是当我想在自己的C代码中实现Xorshift pseudorandom number generator时:
#include <inttypes.h>
static uint64_t prng_state = 1; /* Any nonzero uint64_t seed is okay */
static inline uint64_t prng_u64(void)
{
uint64_t state;
state = prng_state;
state ^= state >> 12;
state ^= state << 25;
state ^= state >> 27;
prng_state = state;
return state * UINT64_C(2685821657736338717);
}
static uint64_t prng_state = 1;
意味着prng_state
是uint64_t
类型的变量,仅在当前编译单元中可见,并且已初始化为1。prng_u64()
函数返回无符号64位伪随机整数。但是,如果您不使用prng_u64()
,则编译器也不会为其生成代码。
另一个典型的用例是当我具有数据结构,并且它们需要访问器函数时。例如,
#ifndef GRID_H
#define GRID_H
#include <stdlib.h>
typedef struct {
int rows;
int cols;
unsigned char *cell;
} grid;
#define GRID_INIT { 0, 0, NULL }
#define GRID_OUTSIDE -1
static inline int grid_get(grid *const g, const int row, const int col)
{
if (!g || row < 0 || col < 0 || row >= g->rows || col >= g->cols)
return GRID_OUTSIDE;
return g->cell[row * (size_t)(g->cols) + col];
}
static inline int grid_set(grid *const g, const int row, const int col,
const unsigned char value)
{
if (!g || row < 0 || col < 0 || row >= g->rows || col >= g->cols)
return GRID_OUTSIDE;
return g->cell[row * (size_t)(g->cols) + col] = value;
}
static inline void grid_init(grid *g)
{
g->rows = 0;
g->cols = 0;
g->cell = NULL;
}
static inline void grid_free(grid *g)
{
free(g->cell);
g->rows = 0;
g->cols = 0;
g->cell = NULL;
}
int grid_create(grid *g, const int rows, const int cols,
const unsigned char initial_value);
int grid_load(grid *g, FILE *handle);
int grid_save(grid *g, FILE *handle);
#endif /* GRID_H */
该头文件定义了一些有用的辅助函数,并声明了将在单独的.c文件中实现的函数grid_create()
,grid_load()
和grid_save()
。
(是的,这三个函数 也可以在头文件中实现,但是这会使头文件很大。如果您的项目很大,请分布在许多翻译单元上( .c源文件),每个包含头文件的文件都会获得自己的函数本地副本。上面定义为static inline
的访问器函数简短而琐碎,因此可以将它们复制到此处,在那里。我省略的三个功能要大得多。)