5 内联
2 分钟阅读
译文 · 基于 The Rust Performance Book
内联
进入和退出未内联的热点函数,往往会占用相当一部分执行时间。内联这些函数可以消除这些进入和退出,并使编译器能够进行额外的底层优化。在最佳情况下,整体效果虽小,却是容易获得的性能提升。
Rust 函数上可以使用四种内联属性。
- 无。编译器将自行决定是否内联该函数。这取决于优化级别、函数大小、函数是否为泛型以及内联是否跨 crate 边界等因素。
#[inline]。建议内联该函数。#[inline(always)]。强烈建议内联该函数。#[inline(never)]。强烈建议不要内联该函数。
内联属性不保证函数一定会或一定不会内联,但在实践中,#[inline(always)] 在几乎所有情况下都会导致内联。
内联不具有传递性。如果函数 f 调用函数 g,而你希望在 f 的调用点将两个函数一起内联,则两个函数都应标记内联属性。
简单情况
最适合内联的候选是:(a) 非常小的函数,或 (b) 只有一个调用点的函数。即使没有内联属性,编译器也经常会自行内联这些函数。但编译器并不总能做出最佳选择,因此有时需要添加属性。 示例 1, 示例 2, 示例 3, 示例 4, 示例 5。
Cachegrind 是判断函数是否被内联的良好分析器。查看 Cachegrind 输出时,当且仅当函数的第一行和最后一行未标记事件计数时,可以判断该函数已被内联。例如:
| |
添加内联属性后应再次测量,因为效果可能难以预测。有时没有效果,因为附近之前被内联的函数不再被内联。有时会减慢代码速度。内联还可能影响编译时间,尤其是跨 crate 内联,涉及复制函数的内部表示。
较难的情况
有时你有一个较大且有多个调用点的函数,但只有一个调用点是热点。你希望对热点调用点进行内联以提高速度,但不对冷调用点内联以避免不必要的代码膨胀。处理方法是将该函数拆分为始终内联和从不内联两个变体,后者调用前者。
例如,这个函数:
| |
将变为这两个函数:
| |
外联
内联的逆操作是外联:将很少执行的代码移入单独的函数。你可以对此类函数添加 #[cold] 属性,告诉编译器该函数很少被调用。这可以改善热路径的代码生成。
示例 1,
示例 2。