Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

内联

进入和退出热门的、未被内联的函数通常占用了不可忽略的执行时间。内联这些函数可以消除 这些进出开销,并允许编译器进行额外的底层优化。最好的情况下,整体效果虽小,但却是 轻松获得的速度提升。

有四种可用于 Rust 函数的内联属性。

  • 。编译器将自行决定函数是否应该被内联。这取决于优化级别、函数大小、函数是否是 泛型,以及内联是否跨越 crate 边界等因素。
  • #[inline]。这建议函数应该被内联。
  • #[inline(always)]。这强烈建议函数应该被内联。
  • #[inline(never)]。这强烈建议函数不应该被内联。

内联属性并不保证函数一定被内联或不被内联,但在实践中,#[inline(always)] 会在除最 特殊情况外的所有情况下导致内联。

内联是不可传递的。如果一个函数 f 调用了一个函数 g,并且你希望这两个函数在 f 的 调用点一起被内联,那么这两个函数都应该用内联属性标记。

简单情况

最适合内联的是 (a) 非常小的函数,或 (b) 只有一个调用点的函数。即使没有内联属性, 编译器通常也会自行内联这些函数。但编译器并不总能做出最佳选择,因此有时需要属性。 示例 1, 示例 2, 示例 3, 示例 4, 示例 5.

Cachegrind 是判断函数是否被内联的好工具。查看 Cachegrind 的输出时,你可以通过函数 的首行和末行是否标记了事件计数来判断函数是否已被内联。例如:

      .  #[inline(always)]
      .  fn inlined(x: u32, y: u32) -> u32 {
700,000      eprintln!("inlined: {} + {}", x, y);
200,000      x + y
      .  }
      .  
      .  #[inline(never)]
400,000  fn not_inlined(x: u32, y: u32) -> u32 {
700,000      eprintln!("not_inlined: {} + {}", x, y);
200,000      x + y
200,000  }

添加内联属性后应再次测量,因为效果可能不可预测。有时它没有效果,因为附近一个原本 被内联的函数不再被内联了。有时它会拖慢代码。内联还会影响编译时间,特别是跨 crate 内联,这涉及复制函数的内部表示。

困难情况

有时你有一个较大的函数,并且有多个调用点,但只有一个调用点是热点。你希望内联热点 调用点以提升速度,但不要内联冷调用点以避免不必要的代码膨胀。处理此问题的方法是将 函数拆分为始终内联和永不内联的变体,后者调用前者。

例如,这个函数:

#![allow(unused)]
fn main() {
fn one() {};
fn two() {};
fn three() {};
fn my_function() {
    one();
    two();
    three();
}
}

将变成这两个函数:

#![allow(unused)]
fn main() {
fn one() {};
fn two() {};
fn three() {};
// 在热点调用点使用此函数。
#[inline(always)]
fn inlined_my_function() {
    one();
    two();
    three();
}

// 在冷调用点使用此函数。
#[inline(never)]
fn uninlined_my_function() {
    inlined_my_function();
}
}

示例 1, 示例 2.

外提

内联的逆操作是外提:将很少执行的代码移到一个单独的函数中。你可以为这类函数添加 #[cold] 属性,告诉编译器该函数很少被调用。这可以为热路径生成更好的代码。 示例 1, 示例 2.