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 の関数で使用できるインライン属性は 4 つあります。

  • なし。その関数をインライン化すべきかどうかは、コンパイラが自ら判断します。 これは、最適化レベル、関数のサイズ、関数がジェネリックかどうか、インライン化が クレート境界をまたぐかどうか、といった要因に依存します。
  • #[inline]。これは、その関数をインライン化すべきであることを示唆します。
  • #[inline(always)]。これは、その関数をインライン化すべきであることを強く示唆します。
  • #[inline(never)]。これは、その関数をインライン化すべきでないことを強く示唆します。

インライン属性は、関数がインライン化されること、またはインライン化されないことを保証するものではありませんが、実際には #[inline(always)] は、ごく例外的な場合を除いてインライン化を引き起こします。

インライン化は推移的ではありません。関数 f が関数 g を呼び出しており、f の呼び出し箇所で両方の関数をまとめてインライン化したい場合は、両方の関数にインライン属性を付けるべきです。

単純なケース

インライン化に最適な候補は、(a) 非常に小さい関数、または (b) 呼び出し箇所が 1 つしかない関数です。コンパイラは、インライン属性がなくても、こうした関数を自らインライン化することがよくあります。しかし、コンパイラが常に最善の選択をできるとは限らないため、属性が必要になる場合があります。 例 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  }

インライン属性を追加した後は、再度測定すべきです。効果は予測しにくいことがあるためです。以前はインライン化されていた近くの関数がインライン化されなくなり、効果がなくなる場合があります。コードが遅くなる場合もあります。インライン化はコンパイル時間にも影響することがあります。特に、関数の内部表現の複製を伴うクレート間インライン化ではその傾向があります。

より難しいケース

関数が大きく、複数の呼び出し箇所を持つものの、ホットな呼び出し箇所が 1 つだけである場合があります。速度のためにホットな呼び出し箇所ではインライン化したい一方で、不要なコード肥大化を避けるためにコールドな呼び出し箇所ではインライン化したくない、という場合です。これを扱う方法は、その関数を常にインライン化されるバリアントと決してインライン化されないバリアントに分割し、後者が前者を呼び出すようにすることです。

たとえば、この関数は次のようになります。

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

これは次の 2 つの関数になります。

#![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.