何时使用静态内联而不是常规函数
When to use static inline instead of regular functions
当我检查其他人的代码时,我有时会遇到头文件中实现的静态内联函数,而不是 C 文件中的常规函数实现。
例如,git
的cache.h
头文件(https://github.com/git/git/blob/master/cache.h)包含很多这样的函数。下面复制其中一张;
static inline void copy_cache_entry(struct cache_entry *dst,
const struct cache_entry *src)
{
unsigned int state = dst->ce_flags & CE_HASHED;
/* Don't copy hash chain and name */
memcpy(&dst->ce_stat_data, &src->ce_stat_data,
offsetof(struct cache_entry, name) -
offsetof(struct cache_entry, ce_stat_data));
/* Restore the hash state */
dst->ce_flags = (dst->ce_flags & ~CE_HASHED) | state;
}
我想知道与常规函数相比,使用静态内联函数有什么优势。有没有什么准则可以用来选择要适应的风格?
主要原因是性能:如果使用得当,内联函数可以使编译器生成更高效的代码。
识别性能瓶颈的一个好策略是分析代码。一旦完成,提高性能的最有效方法就是关注瓶颈。有很多策略,比如算法改进等。其中一种策略是将常用函数缩短内联。
与任何其他提高性能的尝试一样,需要测试结果以确保更改确实有益。
inline
允许您在 header.
中定义函数
static
使该功能仅在当前翻译单元中可用。
内联的潜在优势在于,可以避免函数调用,这可以节省一些执行时间和堆栈内存,为可执行文件牺牲一些 space(如果多次使用该函数) .它还可以通过消除死代码来进一步优化(例如,函数返回无效参数的错误代码,调用函数执行相同的检查,其中第二个相同的检查可以在内联时删除)。请注意,作为优化技术的内联和内联定义(由 C 标准定义)是两种不同的东西:编译器可以内联它看到定义的每个函数,并且可以决定对 inline
函数。
在严格符合程序中声明 static
的每个函数都可以声明 inline
。这只是对编译器的提示,没有任何语义意义(注意,对于具有外部链接的函数,有区别)。
有时,static inline
被视为宏函数的 type-checking 替代品,因此可以被视为用于某些文档目的。
重要的是将函数记录为 static
并在 header 中定义(或者至少可能是 static
并在 header 中定义),因为这样 header 的用户不能假设在不同的翻译单元中获取函数的地址会产生相同的结果。
如果一个定义应该在 header 中(允许内联),我个人更喜欢带有外部链接的内联函数,因为地址比较相等,如果编译器认为值得,它仍然可以内联。
内联是为了优化。然而,一个鲜为人知的事实是 inline
也会损害性能:您的 CPU 有一个固定大小的指令缓存,而内联有在多个地方复制函数的缺点,这使得指令缓存效率较低。
因此,从性能的角度来看,通常不建议声明函数 inline
除非它们太短以至于它们的调用比它们的执行更昂贵 .
相对而言:一个函数调用大约需要 10 到 30 个周期的 CPU 时间(取决于参数的数量)。算术运算通常需要一个周期,但是,从一级缓存加载内存需要大约三到四个周期。所以,如果你的函数比最多三个内存访问和一些算术的简单序列更复杂,那么内联它就没有什么意义了。
我通常采用这种方法:
如果一个函数就像递增一个计数器一样简单,并且在所有地方都使用它,我会内联它。这样的例子很少见,但一个有效的例子是引用计数。
如果一个函数仅在单个文件中使用,我将其声明为static
,而不是inline
。这样的效果是编译器可以 看到 当这样的函数被精确使用一次时。如果它看到了,它很可能会内联它,不管它有多复杂,因为它可以证明内联没有缺点。
所有其他函数既不是static
也不是inline
。
你问题中的例子是一个边缘性的例子:它包含一个函数调用,因此乍一看内联似乎太复杂了。
但是,memcpy()
函数很特殊:它更多地被视为语言的一部分,而不是库函数。大多数编译器会内联它,并在大小是一个小的编译时间常量时对其进行大量优化,问题代码就是这种情况。
通过优化,函数确实被简化为一个简短的序列。我不能说它是否触及了很多内存,因为我不知道复制的结构。如果该结构很小,在这种情况下添加 inline
关键字似乎是个好主意。
当我检查其他人的代码时,我有时会遇到头文件中实现的静态内联函数,而不是 C 文件中的常规函数实现。
例如,git
的cache.h
头文件(https://github.com/git/git/blob/master/cache.h)包含很多这样的函数。下面复制其中一张;
static inline void copy_cache_entry(struct cache_entry *dst,
const struct cache_entry *src)
{
unsigned int state = dst->ce_flags & CE_HASHED;
/* Don't copy hash chain and name */
memcpy(&dst->ce_stat_data, &src->ce_stat_data,
offsetof(struct cache_entry, name) -
offsetof(struct cache_entry, ce_stat_data));
/* Restore the hash state */
dst->ce_flags = (dst->ce_flags & ~CE_HASHED) | state;
}
我想知道与常规函数相比,使用静态内联函数有什么优势。有没有什么准则可以用来选择要适应的风格?
主要原因是性能:如果使用得当,内联函数可以使编译器生成更高效的代码。
识别性能瓶颈的一个好策略是分析代码。一旦完成,提高性能的最有效方法就是关注瓶颈。有很多策略,比如算法改进等。其中一种策略是将常用函数缩短内联。
与任何其他提高性能的尝试一样,需要测试结果以确保更改确实有益。
inline
允许您在 header.
static
使该功能仅在当前翻译单元中可用。
内联的潜在优势在于,可以避免函数调用,这可以节省一些执行时间和堆栈内存,为可执行文件牺牲一些 space(如果多次使用该函数) .它还可以通过消除死代码来进一步优化(例如,函数返回无效参数的错误代码,调用函数执行相同的检查,其中第二个相同的检查可以在内联时删除)。请注意,作为优化技术的内联和内联定义(由 C 标准定义)是两种不同的东西:编译器可以内联它看到定义的每个函数,并且可以决定对 inline
函数。
在严格符合程序中声明 static
的每个函数都可以声明 inline
。这只是对编译器的提示,没有任何语义意义(注意,对于具有外部链接的函数,有区别)。
有时,static inline
被视为宏函数的 type-checking 替代品,因此可以被视为用于某些文档目的。
重要的是将函数记录为 static
并在 header 中定义(或者至少可能是 static
并在 header 中定义),因为这样 header 的用户不能假设在不同的翻译单元中获取函数的地址会产生相同的结果。
如果一个定义应该在 header 中(允许内联),我个人更喜欢带有外部链接的内联函数,因为地址比较相等,如果编译器认为值得,它仍然可以内联。
内联是为了优化。然而,一个鲜为人知的事实是 inline
也会损害性能:您的 CPU 有一个固定大小的指令缓存,而内联有在多个地方复制函数的缺点,这使得指令缓存效率较低。
因此,从性能的角度来看,通常不建议声明函数 inline
除非它们太短以至于它们的调用比它们的执行更昂贵 .
相对而言:一个函数调用大约需要 10 到 30 个周期的 CPU 时间(取决于参数的数量)。算术运算通常需要一个周期,但是,从一级缓存加载内存需要大约三到四个周期。所以,如果你的函数比最多三个内存访问和一些算术的简单序列更复杂,那么内联它就没有什么意义了。
我通常采用这种方法:
如果一个函数就像递增一个计数器一样简单,并且在所有地方都使用它,我会内联它。这样的例子很少见,但一个有效的例子是引用计数。
如果一个函数仅在单个文件中使用,我将其声明为
static
,而不是inline
。这样的效果是编译器可以 看到 当这样的函数被精确使用一次时。如果它看到了,它很可能会内联它,不管它有多复杂,因为它可以证明内联没有缺点。所有其他函数既不是
static
也不是inline
。
你问题中的例子是一个边缘性的例子:它包含一个函数调用,因此乍一看内联似乎太复杂了。
但是,memcpy()
函数很特殊:它更多地被视为语言的一部分,而不是库函数。大多数编译器会内联它,并在大小是一个小的编译时间常量时对其进行大量优化,问题代码就是这种情况。
通过优化,函数确实被简化为一个简短的序列。我不能说它是否触及了很多内存,因为我不知道复制的结构。如果该结构很小,在这种情况下添加 inline
关键字似乎是个好主意。